diff options
Diffstat (limited to 'kexi/kexidb')
157 files changed, 39398 insertions, 0 deletions
diff --git a/kexi/kexidb/Makefile.am b/kexi/kexidb/Makefile.am new file mode 100644 index 00000000..8b4195e2 --- /dev/null +++ b/kexi/kexidb/Makefile.am @@ -0,0 +1,64 @@ +include $(top_srcdir)/kexi/Makefile.global + +lib_LTLIBRARIES = libkexidb.la + +INCLUDES = -I$(top_srcdir)/kexi $(all_includes) + +SUBDIRS = . parser drivers + +libkexidb_la_METASOURCES = AUTO + +libkexidb_la_SOURCES = drivermanager.cpp driver.cpp driver_p.cpp connection.cpp \ + keywords.cpp object.cpp field.cpp utils.cpp expression.cpp \ + connectiondata.cpp fieldlist.cpp tableschema.cpp cursor.cpp transaction.cpp \ + indexschema.cpp queryschemaparameter.cpp queryschema.cpp schemadata.cpp global.cpp \ + relationship.cpp roweditbuffer.cpp msghandler.cpp \ + dbobjectnamevalidator.cpp fieldvalidator.cpp preparedstatement.cpp \ + dbproperties.cpp admin.cpp alter.cpp lookupfieldschema.cpp simplecommandlineapp.cpp + +noinst_HEADERS = drivermanager_p.h utils_p.h + +kexidbincludedir=$(includedir)/kexidb +kexidbinclude_HEADERS= \ +admin.h \ +alter.h \ +connectiondata.h \ +connection.h \ +cursor.h \ +dbobjectnamevalidator.h \ +dbproperties.h \ +driver.h \ +drivermanager.h \ +driver_p.h \ +error.h \ +expression.h \ +field.h \ +fieldlist.h \ +fieldvalidator.h \ +global.h \ +indexschema.h \ +kexidb_export.h \ +lookupfieldschema.h \ +msghandler.h \ +object.h \ +preparedstatement.h \ +queryschema.h \ +queryschemaparameter.h \ +relationship.h \ +roweditbuffer.h \ +schemadata.h \ +simplecommandlineapp.h \ +tableschema.h \ +transaction.h \ +utils.h \ +parser/parser.h + +libkexidb_la_LIBADD = $(LIB_QT) $(LIB_KDECORE) $(LIB_KIO) \ + $(top_builddir)/kexi/kexiutils/libkexiutils.la +libkexidb_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(VER_INFO) + +kde_servicetypes_DATA = kexidb_driver.desktop + + +KDE_CXXFLAGS += -D__KEXIDB__= -include $(top_srcdir)/kexi/kexidb/global.h + diff --git a/kexi/kexidb/admin.cpp b/kexi/kexidb/admin.cpp new file mode 100644 index 00000000..5d05af9e --- /dev/null +++ b/kexi/kexidb/admin.cpp @@ -0,0 +1,42 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "admin.h" +#include "driver_p.h" + +using namespace KexiDB; + +AdminTools::AdminTools() + : Object() + , d( new Private() ) +{ +} + +AdminTools::~AdminTools() +{ + delete d; +} + +bool AdminTools::vacuum(const ConnectionData& data, const QString& databaseName) +{ + Q_UNUSED(data); + Q_UNUSED(databaseName); + clearError(); + return false; +} diff --git a/kexi/kexidb/admin.h b/kexi/kexidb/admin.h new file mode 100644 index 00000000..0a7a0d46 --- /dev/null +++ b/kexi/kexidb/admin.h @@ -0,0 +1,56 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_ADMIN_H +#define KEXIDB_ADMIN_H + +#include "object.h" + +namespace KexiDB +{ +class Connection; +class ConnectionData; + +//! @short An interface containing a set of tools for database administration +/*! Can be implemented in database drivers. @see Driver::adminTools +*/ +class KEXI_DB_EXPORT AdminTools : public Object +{ + public: + AdminTools(); + virtual ~AdminTools(); + + /*! Performs vacuum (compacting) for connection \a data. + Can be implemented for your driver. + Note: in most cases the database should not be opened. + + Currently it is implemented for SQLite drivers. + + \return true on success, false on failure + (then you can get error status from the AdminTools object). */ + virtual bool vacuum(const ConnectionData& data, const QString& databaseName); + //virtual bool vacuum(Connection& conn); + + protected: + class Private; + Private *d; +}; +} + +#endif diff --git a/kexi/kexidb/alter.cpp b/kexi/kexidb/alter.cpp new file mode 100644 index 00000000..f894b299 --- /dev/null +++ b/kexi/kexidb/alter.cpp @@ -0,0 +1,1115 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "alter.h" +#include "utils.h" +#include <kexiutils/utils.h> + +#include <qmap.h> + +#include <kstaticdeleter.h> + +#include <stdlib.h> + +namespace KexiDB { +class AlterTableHandler::Private +{ + public: + Private() + {} + ~Private() + {} + ActionList actions; + QGuardedPtr<Connection> conn; +}; +} + +using namespace KexiDB; + +//! a global instance used to when returning null is needed +AlterTableHandler::ChangeFieldPropertyAction nullChangeFieldPropertyAction(true); +AlterTableHandler::RemoveFieldAction nullRemoveFieldAction(true); +AlterTableHandler::InsertFieldAction nullInsertFieldAction(true); +AlterTableHandler::MoveFieldPositionAction nullMoveFieldPositionAction(true); + +//-------------------------------------------------------- + +AlterTableHandler::ActionBase::ActionBase(bool null) + : m_alteringRequirements(0) + , m_order(-1) + , m_null(null) +{ +} + +AlterTableHandler::ActionBase::~ActionBase() +{ +} + +AlterTableHandler::ChangeFieldPropertyAction& AlterTableHandler::ActionBase::toChangeFieldPropertyAction() +{ + if (dynamic_cast<ChangeFieldPropertyAction*>(this)) + return *dynamic_cast<ChangeFieldPropertyAction*>(this); + return nullChangeFieldPropertyAction; +} + +AlterTableHandler::RemoveFieldAction& AlterTableHandler::ActionBase::toRemoveFieldAction() +{ + if (dynamic_cast<RemoveFieldAction*>(this)) + return *dynamic_cast<RemoveFieldAction*>(this); + return nullRemoveFieldAction; +} + +AlterTableHandler::InsertFieldAction& AlterTableHandler::ActionBase::toInsertFieldAction() +{ + if (dynamic_cast<InsertFieldAction*>(this)) + return *dynamic_cast<InsertFieldAction*>(this); + return nullInsertFieldAction; +} + +AlterTableHandler::MoveFieldPositionAction& AlterTableHandler::ActionBase::toMoveFieldPositionAction() +{ + if (dynamic_cast<MoveFieldPositionAction*>(this)) + return *dynamic_cast<MoveFieldPositionAction*>(this); + return nullMoveFieldPositionAction; +} + +//-------------------------------------------------------- + +AlterTableHandler::FieldActionBase::FieldActionBase(const QString& fieldName, int uid) + : ActionBase() + , m_fieldUID(uid) + , m_fieldName(fieldName) +{ +} + +AlterTableHandler::FieldActionBase::FieldActionBase(bool) + : ActionBase(true) + , m_fieldUID(-1) +{ +} + +AlterTableHandler::FieldActionBase::~FieldActionBase() +{ +} + +//-------------------------------------------------------- + +static KStaticDeleter< QMap<QCString,int> > KexiDB_alteringTypeForProperty_deleter; +QMap<QCString,int> *KexiDB_alteringTypeForProperty = 0; + +int AlterTableHandler::alteringTypeForProperty(const QCString& propertyName) +{ + if (!KexiDB_alteringTypeForProperty) { + KexiDB_alteringTypeForProperty_deleter.setObject( KexiDB_alteringTypeForProperty, + new QMap<QCString,int>() ); +#define I(name, type) \ + KexiDB_alteringTypeForProperty->insert(QCString(name).lower(), (int)AlterTableHandler::type) +#define I2(name, type1, type2) \ + flag = (int)AlterTableHandler::type1|(int)AlterTableHandler::type2; \ + if (flag & AlterTableHandler::PhysicalAlteringRequired) \ + flag |= AlterTableHandler::MainSchemaAlteringRequired; \ + KexiDB_alteringTypeForProperty->insert(QCString(name).lower(), flag) + + /* useful links: + http://dev.mysql.com/doc/refman/5.0/en/create-table.html + */ + // ExtendedSchemaAlteringRequired is here because when the field is renamed, + // we need to do the same rename in extended table schema: <field name="..."> + int flag; + I2("name", PhysicalAlteringRequired, MainSchemaAlteringRequired); + I2("type", PhysicalAlteringRequired, DataConversionRequired); + I("caption", MainSchemaAlteringRequired); + I("description", MainSchemaAlteringRequired); + I2("unsigned", PhysicalAlteringRequired, DataConversionRequired); // always? + I2("length", PhysicalAlteringRequired, DataConversionRequired); // always? + I2("precision", PhysicalAlteringRequired, DataConversionRequired); // always? + I("width", MainSchemaAlteringRequired); + // defaultValue: depends on backend, for mysql it can only by a constant or now()... + // -- should we look at Driver here? +#ifdef KEXI_NO_UNFINISHED +//! @todo reenable + I("defaultValue", MainSchemaAlteringRequired); +#else + I2("defaultValue", PhysicalAlteringRequired, MainSchemaAlteringRequired); +#endif + I2("primaryKey", PhysicalAlteringRequired, DataConversionRequired); + I2("unique", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here + I2("notNull", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here + // allowEmpty: only support it just at kexi level? maybe there is a backend that supports this? + I2("allowEmpty", PhysicalAlteringRequired, MainSchemaAlteringRequired); + I2("autoIncrement", PhysicalAlteringRequired, DataConversionRequired); // data conversion may be hard here + I2("indexed", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here + + // easier cases follow... + I("visibleDecimalPlaces", ExtendedSchemaAlteringRequired); + + // lookup-field-related properties... +/*moved to KexiDB::isExtendedTableFieldProperty() + I("boundColumn", ExtendedSchemaAlteringRequired); + I("rowSource", ExtendedSchemaAlteringRequired); + I("rowSourceType", ExtendedSchemaAlteringRequired); + I("rowSourceValues", ExtendedSchemaAlteringRequired); + I("visibleColumn", ExtendedSchemaAlteringRequired); + I("columnWidths", ExtendedSchemaAlteringRequired); + I("showColumnHeaders", ExtendedSchemaAlteringRequired); + I("listRows", ExtendedSchemaAlteringRequired); + I("limitToList", ExtendedSchemaAlteringRequired); + I("displayWidget", ExtendedSchemaAlteringRequired);*/ + + //more to come... +#undef I +#undef I2 + } + const int res = (*KexiDB_alteringTypeForProperty)[propertyName.lower()]; + if (res == 0) { + if (KexiDB::isExtendedTableFieldProperty(propertyName)) + return (int)ExtendedSchemaAlteringRequired; + KexiDBWarn << QString("AlterTableHandler::alteringTypeForProperty(): property \"%1\" not found!") + .arg(propertyName) << endl; + } + return res; +} + +//--- + +AlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction( + const QString& fieldName, const QString& propertyName, const QVariant& newValue, int uid) + : FieldActionBase(fieldName, uid) + , m_propertyName(propertyName) + , m_newValue(newValue) +{ +} + +AlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction(bool) + : FieldActionBase(true) +{ +} + +AlterTableHandler::ChangeFieldPropertyAction::~ChangeFieldPropertyAction() +{ +} + +void AlterTableHandler::ChangeFieldPropertyAction::updateAlteringRequirements() +{ +// m_alteringRequirements = ???; + setAlteringRequirements( alteringTypeForProperty( m_propertyName.latin1() ) ); +} + +QString AlterTableHandler::ChangeFieldPropertyAction::debugString(const DebugOptions& debugOptions) +{ + QString s = QString("Set \"%1\" property for table field \"%2\" to \"%3\"") + .arg(m_propertyName).arg(fieldName()).arg(m_newValue.toString()); + if (debugOptions.showUID) + s.append(QString(" (UID=%1)").arg(m_fieldUID)); + return s; +} + +static AlterTableHandler::ActionDict* createActionDict( + AlterTableHandler::ActionDictDict &fieldActions, int forFieldUID ) +{ + AlterTableHandler::ActionDict* dict = new AlterTableHandler::ActionDict(101, false); + dict->setAutoDelete(true); + fieldActions.insert( forFieldUID, dict ); + return dict; +} + +static void debugAction(AlterTableHandler::ActionBase *action, int nestingLevel, + bool simulate, const QString& prependString = QString::null, QString* debugTarget = 0) +{ + QString debugString; + if (!debugTarget) + debugString = prependString; + if (action) { + AlterTableHandler::ActionBase::DebugOptions debugOptions; + debugOptions.showUID = debugTarget==0; + debugOptions.showFieldDebug = debugTarget!=0; + debugString += action->debugString( debugOptions ); + } + else { + if (!debugTarget) + debugString += "[No action]"; //hmm + } + if (debugTarget) { + if (!debugString.isEmpty()) + *debugTarget += debugString + '\n'; + } + else { + KexiDBDbg << debugString << endl; +#ifdef KEXI_DEBUG_GUI + if (simulate) + KexiUtils::addAlterTableActionDebug(debugString, nestingLevel); +#endif + } +} + +static void debugActionDict(AlterTableHandler::ActionDict *dict, int fieldUID, bool simulate) +{ + QString fieldName; + AlterTableHandler::ActionDictIterator it(*dict); + if (dynamic_cast<AlterTableHandler::FieldActionBase*>(it.current())) //retrieve field name from the 1st related action + fieldName = dynamic_cast<AlterTableHandler::FieldActionBase*>(it.current())->fieldName(); + else + fieldName = "??"; + QString dbg = QString("Action dict for field \"%1\" (%2, UID=%3):") + .arg(fieldName).arg(dict->count()).arg(fieldUID); + KexiDBDbg << dbg << endl; +#ifdef KEXI_DEBUG_GUI + if (simulate) + KexiUtils::addAlterTableActionDebug(dbg, 1); +#endif + for (;it.current(); ++it) { + debugAction(it.current(), 2, simulate); + } +} + +static void debugFieldActions(const AlterTableHandler::ActionDictDict &fieldActions, bool simulate) +{ +#ifdef KEXI_DEBUG_GUI + if (simulate) + KexiUtils::addAlterTableActionDebug("** Simplified Field Actions:"); +#endif + for (AlterTableHandler::ActionDictDictIterator it(fieldActions); it.current(); ++it) { + debugActionDict(it.current(), it.currentKey(), simulate); + } +} + +/*! + Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. + Case 1. (special) + when new action=[rename A to B] + and exists=[rename B to C] + => + remove [rename B to C] + and set result to new [rename A to C] + and go to 1b. + Case 1b. when new action=[rename A to B] + and actions exist like [set property P to C in field B] + or like [delete field B] + or like [move field B] + => + change B to A for all these actions + Case 2. when new action=[change property in field A] (property != name) + and exists=[remove A] or exists=[change property in field A] + => + do not add [change property in field A] because it will be removed anyway or the property will change +*/ +void AlterTableHandler::ChangeFieldPropertyAction::simplifyActions(ActionDictDict &fieldActions) +{ + ActionDict *actionsLikeThis = fieldActions[ uid() ]; + if (m_propertyName=="name") { + // Case 1. special: name1 -> name2, i.e. rename action + QString newName( newValue().toString() ); + // try to find rename(newName, otherName) action + ActionBase *renameActionLikeThis = actionsLikeThis ? actionsLikeThis->find( "name" ) : 0; + if (dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)) { + // 1. instead of having rename(fieldName(), newValue()) action, + // let's have rename(fieldName(), otherName) action + dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue + = dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue; +/* AlterTableHandler::ChangeFieldPropertyAction* newRenameAction + = new AlterTableHandler::ChangeFieldPropertyAction( *this ); + newRenameAction->m_newValue = dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue; + // (m_order is the same as in newAction) + // replace prev. rename action (if any) + actionsLikeThis->remove( "name" ); + ActionDict *adict = fieldActions[ fieldName().latin1() ]; + if (!adict) + adict = createActionDict( fieldActions, fieldName() ); + adict->insert(m_propertyName.latin1(), newRenameAction);*/ + } + else { + ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->find( ":remove:" ) : 0; + if (removeActionForThisField) { + //if this field is going to be removed, jsut change the action's field name + // and do not add a new action + } + else { + //just insert a copy of the rename action + if (!actionsLikeThis) + actionsLikeThis = createActionDict( fieldActions, uid() ); //fieldName() ); + AlterTableHandler::ChangeFieldPropertyAction* newRenameAction + = new AlterTableHandler::ChangeFieldPropertyAction( *this ); + KexiDBDbg << "ChangeFieldPropertyAction::simplifyActions(): insert into '" + << fieldName() << "' dict:" << newRenameAction->debugString() << endl; + actionsLikeThis->insert( m_propertyName.latin1(), newRenameAction ); + return; + } + } + if (actionsLikeThis) { + // Case 1b. change "field name" information to fieldName() in any action that + // is related to newName + // e.g. if there is setCaption("B", "captionA") action after rename("A","B"), + // replace setCaption action with setCaption("A", "captionA") + foreach_dict (ActionDictIterator, it, *actionsLikeThis) { + dynamic_cast<FieldActionBase*>(it.current())->setFieldName( fieldName() ); + } + } + return; + } + ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->find( ":remove:" ) : 0; + if (removeActionForThisField) { + //if this field is going to be removed, do not add a new action + return; + } + // Case 2. other cases: just give up with adding this "intermediate" action + // so, e.g. [ setCaption(A, "captionA"), setCaption(A, "captionB") ] + // becomes: [ setCaption(A, "captionB") ] + // because adding this action does nothing + ActionDict *nextActionsLikeThis = fieldActions[ uid() ]; //fieldName().latin1() ]; + if (!nextActionsLikeThis || !nextActionsLikeThis->find( m_propertyName.latin1() )) { + //no such action, add this + AlterTableHandler::ChangeFieldPropertyAction* newAction + = new AlterTableHandler::ChangeFieldPropertyAction( *this ); + if (!nextActionsLikeThis) + nextActionsLikeThis = createActionDict( fieldActions, uid() );//fieldName() ); + nextActionsLikeThis->insert( m_propertyName.latin1(), newAction ); + } +} + +bool AlterTableHandler::ChangeFieldPropertyAction::shouldBeRemoved(ActionDictDict &fieldActions) +{ + Q_UNUSED(fieldActions); + return fieldName().lower() == m_newValue.toString().lower(); +} + +tristate AlterTableHandler::ChangeFieldPropertyAction::updateTableSchema(TableSchema &table, Field* field, + QMap<QString, QString>& fieldMap) +{ + //1. Simpler cases first: changes that do not affect table schema at all + // "caption", "description", "width", "visibleDecimalPlaces" + if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.latin1())) { + bool result = KexiDB::setFieldProperty(*field, m_propertyName.latin1(), newValue()); + return result; + } + + if (m_propertyName=="name") { + if (fieldMap[ field->name() ] == field->name()) + fieldMap.remove( field->name() ); + fieldMap.insert( newValue().toString(), field->name() ); + table.renameField(field, newValue().toString()); + return true; + } + return cancelled; +} + +/*! Many of the properties must be applied using a separate algorithm. +*/ +tristate AlterTableHandler::ChangeFieldPropertyAction::execute(Connection &conn, TableSchema &table) +{ + Q_UNUSED(conn); + Field *field = table.field( fieldName() ); + if (!field) { + //! @todo errmsg + return false; + } + bool result; + //1. Simpler cases first: changes that do not affect table schema at all + // "caption", "description", "width", "visibleDecimalPlaces" + if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.latin1())) { + result = KexiDB::setFieldProperty(*field, m_propertyName.latin1(), newValue()); + return result; + } + +//todo +return true; + + //2. Harder cases, that often require special care + if (m_propertyName=="name") { + /*mysql: + A. Get real field type (it's safer): + let <TYPE> be the 2nd "Type" column from result of "DESCRIBE tablename oldfieldname" + ( http://dev.mysql.com/doc/refman/5.0/en/describe.html ) + B. Run "ALTER TABLE tablename CHANGE oldfieldname newfieldname <TYPE>"; + ( http://dev.mysql.com/doc/refman/5.0/en/alter-table.html ) + */ + } + if (m_propertyName=="type") { + /*mysql: + A. Like A. for "name" property above + B. Construct <TYPE> string, eg. "varchar(50)" using the driver + C. Like B. for "name" property above + (mysql then truncate the values for changes like varchar -> integer, + and properly convert the values for changes like integer -> varchar) + + TODO: more cases to check + */ + } + if (m_propertyName=="length") { + //use "select max( length(o_name) ) from kexi__Objects" + + } + if (m_propertyName=="primaryKey") { +//! @todo + } + +/* + "name", "unsigned", "precision", + "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty", + "autoIncrement", "indexed", + + + bool result = KexiDB::setFieldProperty(*field, m_propertyName.latin1(), newValue()); +*/ + return result; +} + +//-------------------------------------------------------- + +AlterTableHandler::RemoveFieldAction::RemoveFieldAction(const QString& fieldName, int uid) + : FieldActionBase(fieldName, uid) +{ +} + +AlterTableHandler::RemoveFieldAction::RemoveFieldAction(bool) + : FieldActionBase(true) +{ +} + +AlterTableHandler::RemoveFieldAction::~RemoveFieldAction() +{ +} + +void AlterTableHandler::RemoveFieldAction::updateAlteringRequirements() +{ +//! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ? + + setAlteringRequirements( PhysicalAlteringRequired ); + //! @todo +} + +QString AlterTableHandler::RemoveFieldAction::debugString(const DebugOptions& debugOptions) +{ + QString s = QString("Remove table field \"%1\"").arg(fieldName()); + if (debugOptions.showUID) + s.append(QString(" (UID=%1)").arg(uid())); + return s; +} + +/*! + Legend: A,B==objects, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. + Preconditions: we assume there cannot be such case encountered: ([remove A], [do something related on A]) + (except for [remove A], [insert A]) + General Case: it's safe to always insert a [remove A] action. +*/ +void AlterTableHandler::RemoveFieldAction::simplifyActions(ActionDictDict &fieldActions) +{ + //! @todo not checked + AlterTableHandler::RemoveFieldAction* newAction + = new AlterTableHandler::RemoveFieldAction( *this ); + ActionDict *actionsLikeThis = fieldActions[ uid() ]; //fieldName().latin1() ]; + if (!actionsLikeThis) + actionsLikeThis = createActionDict( fieldActions, uid() ); //fieldName() ); + actionsLikeThis->insert( ":remove:", newAction ); //special +} + +tristate AlterTableHandler::RemoveFieldAction::updateTableSchema(TableSchema &table, Field* field, + QMap<QString, QString>& fieldMap) +{ + fieldMap.remove( field->name() ); + table.removeField(field); + return true; +} + +tristate AlterTableHandler::RemoveFieldAction::execute(Connection& conn, TableSchema& table) +{ + Q_UNUSED(conn); + Q_UNUSED(table); + //! @todo + return true; +} + +//-------------------------------------------------------- + +AlterTableHandler::InsertFieldAction::InsertFieldAction(int fieldIndex, KexiDB::Field *field, int uid) + : FieldActionBase(field->name(), uid) + , m_index(fieldIndex) + , m_field(0) +{ + Q_ASSERT(field); + setField(field); +} + +AlterTableHandler::InsertFieldAction::InsertFieldAction(const InsertFieldAction& action) + : FieldActionBase(action) //action.fieldName(), action.uid()) + , m_index(action.index()) +{ + m_field = new KexiDB::Field( action.field() ); +} + +AlterTableHandler::InsertFieldAction::InsertFieldAction(bool) + : FieldActionBase(true) + , m_index(0) + , m_field(0) +{ +} + +AlterTableHandler::InsertFieldAction::~InsertFieldAction() +{ + delete m_field; +} + +void AlterTableHandler::InsertFieldAction::setField(KexiDB::Field* field) +{ + if (m_field) + delete m_field; + m_field = field; + setFieldName(m_field ? m_field->name() : QString::null); +} + +void AlterTableHandler::InsertFieldAction::updateAlteringRequirements() +{ +//! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ? + + setAlteringRequirements( PhysicalAlteringRequired ); + //! @todo +} + +QString AlterTableHandler::InsertFieldAction::debugString(const DebugOptions& debugOptions) +{ + QString s = QString("Insert table field \"%1\" at position %2") + .arg(m_field->name()).arg(m_index); + if (debugOptions.showUID) + s.append(QString(" (UID=%1)").arg(m_fieldUID)); + if (debugOptions.showFieldDebug) + s.append(QString(" (%1)").arg(m_field->debugString())); + return s; +} + +/*! + Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. + + + Case 1: there are "change property" actions after the Insert action. + -> change the properties in the Insert action itself and remove the "change property" actions. + Examples: + [Insert A] && [rename A to B] => [Insert B] + [Insert A] && [change property P in field A] => [Insert A with P altered] + Comment: we need to do this reduction because otherwise we'd need to do psyhical altering + right after [Insert A] if [rename A to B] follows. +*/ +void AlterTableHandler::InsertFieldAction::simplifyActions(ActionDictDict &fieldActions) +{ + // Try to find actions related to this action + ActionDict *actionsForThisField = fieldActions[ uid() ]; //m_field->name().latin1() ]; + + ActionBase *removeActionForThisField = actionsForThisField ? actionsForThisField->find( ":remove:" ) : 0; + if (removeActionForThisField) { + //if this field is going to be removed, do not add a new action + //and remove the "Remove" action + actionsForThisField->remove(":remove:"); + return; + } + if (actionsForThisField) { + //collect property values that have to be changed in this field + QMap<QCString, QVariant> values; + for (ActionDictIterator it(*actionsForThisField); it.current();) { + ChangeFieldPropertyAction* changePropertyAction = dynamic_cast<ChangeFieldPropertyAction*>(it.current()); + if (changePropertyAction) { + //if this field is going to be renamed, also update fieldName() + if (changePropertyAction->propertyName()=="name") { + setFieldName(changePropertyAction->newValue().toString()); + } + values.insert( changePropertyAction->propertyName().latin1(), changePropertyAction->newValue() ); + //the subsequent "change property" action is no longer needed + actionsForThisField->remove(changePropertyAction->propertyName().latin1()); + } + else { + ++it; + } + } + if (!values.isEmpty()) { + //update field, so it will be created as one step + KexiDB::Field *f = new KexiDB::Field( field() ); + if (KexiDB::setFieldProperties( *f, values )) { + //field() = f; + setField( f ); + field().debug(); +#ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug( + QString("** Property-set actions moved to field definition itself:\n")+field().debugString(), 0); +#endif + } + else { +#ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug( + QString("** Failed to set properties for field ")+field().debugString(), 0); +#endif + KexiDBWarn << "AlterTableHandler::InsertFieldAction::simplifyActions(): KexiDB::setFieldProperties() failed!" << endl; + delete f; + } + } + } + //ok, insert this action + //! @todo not checked + AlterTableHandler::InsertFieldAction* newAction + = new AlterTableHandler::InsertFieldAction( *this ); + if (!actionsForThisField) + actionsForThisField = createActionDict( fieldActions, uid() ); + actionsForThisField->insert( ":insert:", newAction ); //special +} + +tristate AlterTableHandler::InsertFieldAction::updateTableSchema(TableSchema &table, Field* field, + QMap<QString, QString>& fieldMap) +{ + //in most cases we won't add the field to fieldMap + Q_UNUSED(field); +//! @todo add it only when there should be fixed value (e.g. default) set for this new field... + fieldMap.remove( this->field().name() ); + table.insertField(index(), new Field(this->field())); + return true; +} + +tristate AlterTableHandler::InsertFieldAction::execute(Connection& conn, TableSchema& table) +{ + Q_UNUSED(conn); + Q_UNUSED(table); + //! @todo + return true; +} + +//-------------------------------------------------------- + +AlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction( + int fieldIndex, const QString& fieldName, int uid) + : FieldActionBase(fieldName, uid) + , m_index(fieldIndex) +{ +} + +AlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction(bool) + : FieldActionBase(true) +{ +} + +AlterTableHandler::MoveFieldPositionAction::~MoveFieldPositionAction() +{ +} + +void AlterTableHandler::MoveFieldPositionAction::updateAlteringRequirements() +{ + setAlteringRequirements( MainSchemaAlteringRequired ); + //! @todo +} + +QString AlterTableHandler::MoveFieldPositionAction::debugString(const DebugOptions& debugOptions) +{ + QString s = QString("Move table field \"%1\" to position %2") + .arg(fieldName()).arg(m_index); + if (debugOptions.showUID) + s.append(QString(" (UID=%1)").arg(uid())); + return s; +} + +void AlterTableHandler::MoveFieldPositionAction::simplifyActions(ActionDictDict &fieldActions) +{ + Q_UNUSED(fieldActions); + //! @todo +} + +tristate AlterTableHandler::MoveFieldPositionAction::execute(Connection& conn, TableSchema& table) +{ + Q_UNUSED(conn); + Q_UNUSED(table); + //! @todo + return true; +} + +//-------------------------------------------------------- + +AlterTableHandler::AlterTableHandler(Connection &conn) + : Object() + , d( new Private() ) +{ + d->conn = &conn; +} + +AlterTableHandler::~AlterTableHandler() +{ + delete d; +} + +void AlterTableHandler::addAction(ActionBase* action) +{ + d->actions.append(action); +} + +AlterTableHandler& AlterTableHandler::operator<< ( ActionBase* action ) +{ + d->actions.append(action); + return *this; +} + +const AlterTableHandler::ActionList& AlterTableHandler::actions() const +{ + return d->actions; +} + +void AlterTableHandler::removeAction(int index) +{ + d->actions.remove( d->actions.at(index) ); +} + +void AlterTableHandler::clear() +{ + d->actions.clear(); +} + +void AlterTableHandler::setActions(const ActionList& actions) +{ + d->actions = actions; +} + +void AlterTableHandler::debug() +{ + KexiDBDbg << "AlterTableHandler's actions:" << endl; + foreach_list (ActionListIterator, it, d->actions) + it.current()->debug(); +} + +TableSchema* AlterTableHandler::execute(const QString& tableName, ExecutionArguments& args) +{ + args.result = false; + if (!d->conn) { +//! @todo err msg? + return 0; + } + if (d->conn->isReadOnly()) { +//! @todo err msg? + return 0; + } + if (!d->conn->isDatabaseUsed()) { +//! @todo err msg? + return 0; + } + TableSchema *oldTable = d->conn->tableSchema(tableName); + if (!oldTable) { +//! @todo err msg? + return 0; + } + + if (!args.debugString) + debug(); + + // Find a sum of requirements... + int allActionsCount = 0; + for(ActionListIterator it(d->actions); it.current(); ++it, allActionsCount++) { + it.current()->updateAlteringRequirements(); + it.current()->m_order = allActionsCount; + } + + /* Simplify actions list if possible and check for errors + + How to do it? + - track property changes/deletions in reversed order + - reduce intermediate actions + + Trivial example 1: + *action1: "rename field a to b" + *action2: "rename field b to c" + *action3: "rename field c to d" + + After reduction: + *action1: "rename field a to d" + Summing up: we have tracked what happens to field curently named "d" + and eventually discovered that it was originally named "a". + + Trivial example 2: + *action1: "rename field a to b" + *action2: "rename field b to c" + *action3: "remove field b" + After reduction: + *action3: "remove field b" + Summing up: we have noticed that field "b" has beed eventually removed + so we needed to find all actions related to this field and remove them. + This is good optimization, as some of the eventually removed actions would + be difficult to perform and/or costly, what would be a waste of resources + and a source of unwanted questions sent to the user. + */ + + ActionListIterator it(d->actions); + + // Fields-related actions. + ActionDictDict fieldActions(3001); + fieldActions.setAutoDelete(true); + ActionBase* action; + for(it.toLast(); (action = it.current()); --it) { + action->simplifyActions( fieldActions ); + } + + if (!args.debugString) + debugFieldActions(fieldActions, args.simulate); + + // Prepare actions for execution ---- + // - Sort actions by order + ActionVector actionsVector(allActionsCount); + int currentActionsCount = 0; //some actions may be removed + args.requirements = 0; + QDict<char> fieldsWithChangedMainSchema(997); // Used to collect fields with changed main schema. + // This will be used when recreateTable is false to update kexi__fields + for (ActionDictDictIterator it(fieldActions); it.current(); ++it) { + for (AlterTableHandler::ActionDictIterator it2(*it.current());it2.current(); ++it2, currentActionsCount++) { + if (it2.current()->shouldBeRemoved(fieldActions)) + continue; + actionsVector.insert( it2.current()->m_order, it2.current() ); + // a sum of requirements... + const int r = it2.current()->alteringRequirements(); + args.requirements |= r; + if (r & MainSchemaAlteringRequired && dynamic_cast<ChangeFieldPropertyAction*>(it2.current())) { + // Remember, this will be used when recreateTable is false to update kexi__fields, below. + fieldsWithChangedMainSchema.insert( + dynamic_cast<ChangeFieldPropertyAction*>(it2.current())->fieldName(), (char*)1 ); + } + } + } + // - Debug + QString dbg = QString("** Overall altering requirements: %1").arg(args.requirements); + KexiDBDbg << dbg << endl; + + if (args.onlyComputeRequirements) { + args.result = true; + return 0; + } + + const bool recreateTable = (args.requirements & PhysicalAlteringRequired); + +#ifdef KEXI_DEBUG_GUI + if (args.simulate) + KexiUtils::addAlterTableActionDebug(dbg, 0); +#endif + dbg = QString("** Ordered, simplified actions (%1, was %2):").arg(currentActionsCount).arg(allActionsCount); + KexiDBDbg << dbg << endl; +#ifdef KEXI_DEBUG_GUI + if (args.simulate) + KexiUtils::addAlterTableActionDebug(dbg, 0); +#endif + for (int i=0; i<allActionsCount; i++) { + debugAction(actionsVector[i], 1, args.simulate, QString("%1: ").arg(i+1), args.debugString); + } + + if (args.requirements == 0) {//nothing to do + args.result = true; + return oldTable; + } + if (args.simulate) {//do not execute + args.result = true; + return oldTable; + } +// @todo transaction! + + // Create new TableSchema + TableSchema *newTable = recreateTable ? new TableSchema(*oldTable, false/*!copy id*/) : oldTable; + // find nonexisting temp name for new table schema + if (recreateTable) { + QString tempDestTableName; + while (true) { + tempDestTableName = QString("%1_temp%2%3").arg(newTable->name()).arg(QString::number(rand(), 16)).arg(QString::number(rand(), 16)); + if (!d->conn->tableSchema(tempDestTableName)) + break; + } + newTable->setName( tempDestTableName ); + } + oldTable->debug(); + if (recreateTable && !args.debugString) + newTable->debug(); + + // Update table schema in memory ---- + int lastUID = -1; + Field *currentField = 0; + QMap<QString, QString> fieldMap; // a map from new value to old value + foreach_list( Field::ListIterator, it, newTable->fieldsIterator() ) { + fieldMap.insert( it.current()->name(), it.current()->name() ); + } + for (int i=0; i<allActionsCount; i++) { + action = actionsVector[i]; + if (!action) + continue; + //remember the current Field object because soon we may be unable to find it by name: + FieldActionBase *fieldAction = dynamic_cast<FieldActionBase*>(action); + if (!fieldAction) { + currentField = 0; + } + else { + if (lastUID != fieldAction->uid()) { + currentField = newTable->field( fieldAction->fieldName() ); + lastUID = currentField ? fieldAction->uid() : -1; + } + InsertFieldAction *insertFieldAction = dynamic_cast<InsertFieldAction*>(action); + if (insertFieldAction && insertFieldAction->index()>(int)newTable->fieldCount()) { + //update index: there can be empty rows + insertFieldAction->setIndex(newTable->fieldCount()); + } + } + //if (!currentField) + // continue; + args.result = action->updateTableSchema(*newTable, currentField, fieldMap); + if (args.result!=true) { + if (recreateTable) + delete newTable; + return 0; + } + } + + if (recreateTable) { + // Create the destination table with temporary name + if (!d->conn->createTable( newTable, false )) { + setError(d->conn); + delete newTable; + args.result = false; + return 0; + } + } + +#if 0//todo + // Execute actions ---- + for (int i=0; i<allActionsCount; i++) { + action = actionsVector[i]; + if (!action) + continue; + args.result = action->execute(*d->conn, *newTable); + if (!args.result || ~args.result) { +//! @todo delete newTable... + args.result = false; + return 0; + } + } +#endif + + // update extended table schema after executing the actions + if (!d->conn->storeExtendedTableSchemaData(*newTable)) { +//! @todo better errmsg? + setError(d->conn); +//! @todo delete newTable... + args.result = false; + return 0; + } + + if (recreateTable) { + // Copy the data: + // Build "INSERT INTO ... SELECT FROM ..." SQL statement + // The order is based on the order of the source table fields. + // Notes: + // -Some source fields can be skipped in case when there are deleted fields. + // -Some destination fields can be skipped in case when there + // are new empty fields without fixed/default value. + QString sql = QString("INSERT INTO %1 (").arg(d->conn->escapeIdentifier(newTable->name())); + //insert list of dest. fields + bool first = true; + QString sourceFields; + foreach_list( Field::ListIterator, it, newTable->fieldsIterator() ) { + Field * const f = it.current(); + QString renamedFieldName( fieldMap[ f->name() ] ); + QString sourceSQLString; + if (!renamedFieldName.isEmpty()) { + //this field should be renamed + sourceSQLString = d->conn->escapeIdentifier(renamedFieldName); + } + else if (!f->defaultValue().isNull()) { + //this field has a default value defined +//! @todo support expressions (eg. TODAY()) as a default value +//! @todo this field can be notNull or notEmpty - check whether the default is ok +//! (or do this checking also in the Table Designer?) + sourceSQLString = d->conn->driver()->valueToSQL( f->type(), f->defaultValue() ); + } + else if (f->isNotNull()) { + //this field cannot be null + sourceSQLString = d->conn->driver()->valueToSQL( + f->type(), KexiDB::emptyValueForType( f->type() ) ); + } + else if (f->isNotEmpty()) { + //this field cannot be empty - use any nonempty value..., e.g. " " for text or 0 for number + sourceSQLString = d->conn->driver()->valueToSQL( + f->type(), KexiDB::notEmptyValueForType( f->type() ) ); + } +//! @todo support unique, validatationRule, unsigned flags... +//! @todo check for foreignKey values... + + if (!sourceSQLString.isEmpty()) { + if (first) { + first = false; + } + else { + sql.append( ", " ); + sourceFields.append( ", " ); + } + sql.append( d->conn->escapeIdentifier( f->name() ) ); + sourceFields.append( sourceSQLString ); + } + } + sql.append(QString(") SELECT ") + sourceFields + " FROM " + oldTable->name()); + KexiDBDbg << " ** " << sql << endl; + if (!d->conn->executeSQL( sql )) { + setError(d->conn); +//! @todo delete newTable... + args.result = false; + return 0; + } + + const QString oldTableName = oldTable->name(); +/* args.result = d->conn->dropTable( oldTable ); + if (!args.result || ~args.result) { + setError(d->conn); +//! @todo delete newTable... + return 0; + } + oldTable = 0;*/ + + // Replace the old table with the new one (oldTable will be destroyed) + if (!d->conn->alterTableName(*newTable, oldTableName, true /*replace*/)) { + setError(d->conn); +//! @todo delete newTable... + args.result = false; + return 0; + } + oldTable = 0; + } + + if (!recreateTable) { + if ((MainSchemaAlteringRequired & args.requirements) && !fieldsWithChangedMainSchema.isEmpty()) { + //update main schema (kexi__fields) for changed fields + foreach_list(QDictIterator<char>, it, fieldsWithChangedMainSchema) { + Field *f = newTable->field( it.currentKey() ); + if (f) { + if (!d->conn->storeMainFieldSchema(f)) { + setError(d->conn); + //! @todo delete newTable... + args.result = false; + return 0; + } + } + } + } + } + + args.result = true; + return newTable; +} + +/*TableSchema* AlterTableHandler::execute(const QString& tableName, tristate &result, bool simulate) +{ + return executeInternal( tableName, result, simulate, 0 ); +} + +tristate AlterTableHandler::simulateExecution(const QString& tableName, QString& debugString) +{ + tristate result; + (void)executeInternal( tableName, result, true//simulate + , &debugString ); + return result; +} +*/ diff --git a/kexi/kexidb/alter.h b/kexi/kexidb/alter.h new file mode 100644 index 00000000..1e6d8e87 --- /dev/null +++ b/kexi/kexidb/alter.h @@ -0,0 +1,468 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_ALTER_H +#define KEXIDB_ALTER_H + +#include "connection.h" + +#include <qvaluelist.h> +#include <qasciidict.h> + +#include <kdebug.h> + +namespace KexiDB +{ +class Connection; +class ConnectionData; + +//! @short A tool for handling altering database table schema. +/*! In relational (and other) databases, table schema altering is not an easy task. + It may be considered as easy if there is no data that user wants to keep while + the table schema is altered. Otherwise, if the table is alredy filled with data, + there could be no easy algorithm like: + 1. Drop existing table + 2. Create new one with altered schema. + + Instead, more complex algorithm is needed. To perform the table schema alteration, + a list of well defined atomic operations is used as a "recipe". + + 1. Look at the current data, and: + 1.1. analyze what values will be removed (in case of impossible conversion + or table field removal); + 1.2. analyze what values can be converted (e.g. from numeric types to text), and so on. + 2. Optimize the atomic actions knowing that sometimes a compilation of one action + and another that's opposite to the first means "do nothing". The optimization + is a simulating of actions' execution. + For example, when both action A="change field name from 'city' to 'town'" + and action B="change field name from 'town' to 'city'" is specified, the compilation + of the actions means "change field name from 'city' to 'city'", what is a NULL action. + On the other hand, we need to execute all the actions on the destination table + in proper order, and not just drop them. For the mentioned example, between actions + A and B there can be an action like C="change the type of field 'city' to LongText". + If A and B were simply removed, C would become invalid (there is no 'city' field). + 3. Ask user whether she agrees with the results of analysis mentioned in 1. + 3.2. Additionally, it may be possible to get some hints from the user, as humans usually + know more about logic behind the altered table schema than any machine. + If the user provided hints about the altering, apply them to the actions list. + 4. Create (empty) destination table schema with temporary name, using + the information collected so far. + 5. Copy the data from the source to destionation table. Convert values, + move them between fields, using the information collected. + 6. Remove the source table. + 7. Rename the destination table to the name previously assigned for the source table. + + Notes: + * The actions 4 to 7 should be performed within a database transaction. + * [todo] We want to take care about database relationships as well. + For example, is a table field is removed, relationships related to this field should + be also removed (similar rules as in the Query Designer). + * Especially, care about primary keys and uniquess (indices). Recreate them when needed. + The problem could be if such analysis may require to fetch the entire table data + to the client side. Use "SELECT INTO" statments if possible to avoid such a treat. + + The AlterTableHandler is used in Kexi's Table Designer. + Already opened Connection object is needed. + + Use case: + \code + Connection *conn = ... + + // add some actions (in reality this is performed by tracking user's actions) + // Actions 1, 2 will require physical table altering PhysicalAltering + // Action 3 will only require changes in kexi__fields + // Action 4 will only require changes in extended table schema written in kexi__objectdata + AlterTable::ActionList list; + + // 1. rename the "city" field to "town" + list << new ChangeFieldPropertyAction("city", "name", "town") + + // 2. change type of "town" field to "LongText" + << new ChangeFieldPropertyAction("town", "type", "LongText") + + // 3. set caption of "town" field to "Town" + << new ChangeFieldPropertyAction("town", "caption", "Town") + + // 4. set visible decimal places to 4 for "cost" field + << new ChangeFieldPropertyAction("cost", "visibleDecimalPlaces", 4) + + AlterTableHandler::execute( *conn ); + + \endcode + + Actions for Alter +*/ +class KEXI_DB_EXPORT AlterTableHandler : public Object +{ + public: + class ChangeFieldPropertyAction; + class RemoveFieldAction; + class InsertFieldAction; + class MoveFieldPositionAction; + + //! Defines flags for possible altering requirements; can be combined. + enum AlteringRequirements { + /*! Physical table altering is required; e.g. ALTER TABLE ADD COLUMN. */ + PhysicalAlteringRequired = 1, + + /*! Data conversion is required; e.g. converting integer + values to string after changing column type from integer to text. */ + DataConversionRequired = 2, + + /* Changes to the main table schema (in kexi__fields) required, + this does not require physical changes for the table; + e.g. changing value of the "caption" or "description" property. */ + MainSchemaAlteringRequired = 4, + + /* Only changes to extended table schema required, + this does not require physical changes for the table; + e.g. changing value of the "visibleDecimalPlaces" property + or any of the custom properties. */ + ExtendedSchemaAlteringRequired = 8, + + /*! Convenience flag, changes to the main or extended schema is required. */ + SchemaAlteringRequired = ExtendedSchemaAlteringRequired | MainSchemaAlteringRequired + }; + + class ActionBase; + typedef QAsciiDict<ActionBase> ActionDict; //!< for collecting actions related to a single field + typedef QIntDict<ActionDict> ActionDictDict; //!< for collecting groups of actions by field UID + typedef QAsciiDictIterator<ActionBase> ActionDictIterator; + typedef QIntDictIterator<ActionDict> ActionDictDictIterator; + typedef QPtrVector<ActionBase> ActionVector; //!< for collecting actions related to a single field + + //! Defines a type for action list. + typedef QPtrList<ActionBase> ActionList; + + //! Defines a type for action list's iterator. + typedef QPtrListIterator<ActionBase> ActionListIterator; + + //! Abstract base class used for implementing all the AlterTable actions. + class KEXI_DB_EXPORT ActionBase { + public: + ActionBase(bool null = false); + virtual ~ActionBase(); + + ChangeFieldPropertyAction& toChangeFieldPropertyAction(); + RemoveFieldAction& toRemoveFieldAction(); + InsertFieldAction& toInsertFieldAction(); + MoveFieldPositionAction& toMoveFieldPositionAction(); + + //! \return true if the action is NULL; used in the Table Designer + //! for temporarily collecting actions that have no effect at all. + bool isNull() const { return m_null; } + + //! Controls debug options for actions. Used in debugString() and debug(). + class DebugOptions + { + public: + DebugOptions() : showUID(true), showFieldDebug(false) {} + + //! true if UID should be added to the action debug string (the default) + bool showUID : 1; + + //! true if the field associated with the action (if exists) should + //! be appended to the debug string (default is false) + bool showFieldDebug : 1; + }; + + virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()) { + Q_UNUSED(debugOptions); return "ActionBase"; } + void debug(const DebugOptions& debugOptions = DebugOptions()) { + KexiDBDbg << debugString(debugOptions) + << " (req = " << alteringRequirements() << ")" << endl; } + + protected: + //! Sets requirements for altering; used internally by AlterTableHandler object + void setAlteringRequirements( int alteringRequirements ) + { m_alteringRequirements = alteringRequirements; } + + int alteringRequirements() const { return m_alteringRequirements; } + + virtual void updateAlteringRequirements() {}; + + /*! Simplifies \a fieldActions dictionary. If this action has to be inserted + Into the dictionary, an ActionDict is created first and then a copy of this action + is inserted into it. */ + virtual void simplifyActions(ActionDictDict &fieldActions) { Q_UNUSED(fieldActions); } + + /*! After calling simplifyActions() for each action, + shouldBeRemoved() is called for them as an additional step. + This is used for ChangeFieldPropertyAction items so actions + that do not change property values are removed. */ + virtual bool shouldBeRemoved(ActionDictDict &fieldActions) { + Q_UNUSED(fieldActions); return false; } + + virtual tristate updateTableSchema(TableSchema &table, Field* field, + QMap<QString, QString>& fieldMap) + { Q_UNUSED(table); Q_UNUSED(field); Q_UNUSED(fieldMap); return true; } + + private: + //! Performs physical execution of this action. + virtual tristate execute(Connection& /*conn*/, TableSchema& /*table*/) { return true; } + + //! requirements for altering; used internally by AlterTableHandler object + int m_alteringRequirements; + + //! @internal used for "simplify" algorithm + int m_order; + + bool m_null : 1; + + friend class AlterTableHandler; + }; + + //! Abstract base class used for implementing table field-related actions. + class KEXI_DB_EXPORT FieldActionBase : public ActionBase { + public: + FieldActionBase(const QString& fieldName, int uid); + FieldActionBase(bool); + virtual ~FieldActionBase(); + + //! \return field name for this action + QString fieldName() const { return m_fieldName; } + + /*! \return field's unique identifier + This id is needed because in the meantime there can be more than one + field sharing the same name, so we need to identify them unambiguously. + After the (valid) altering is completed all the names will be unique. + + Example scenario when user exchanged the field names: + 1. At the beginning: [field A], [field B] + 2. Rename the 1st field to B: [field B], [field B] + 3. Rename the 2nd field to A: [field B], [field A] */ + int uid() const { return m_fieldUID; } + + //! Sets field name for this action + void setFieldName(const QString& fieldName) { m_fieldName = fieldName; } + + protected: + + //! field's unique identifier, @see uid() + int m_fieldUID; + private: + QString m_fieldName; + }; + + /*! Defines an action for changing a single property value of a table field. + Supported properties are currently: + "name", "type", "caption", "description", "unsigned", "length", "precision", + "width", "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty", + "autoIncrement", "indexed", "visibleDecimalPlaces" + + More to come. + */ + class KEXI_DB_EXPORT ChangeFieldPropertyAction : public FieldActionBase { + public: + ChangeFieldPropertyAction(const QString& fieldName, + const QString& propertyName, const QVariant& newValue, int uid); + //! @internal, used for constructing null action + ChangeFieldPropertyAction(bool null); + virtual ~ChangeFieldPropertyAction(); + + QString propertyName() const { return m_propertyName; } + QVariant newValue() const { return m_newValue; } + virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()); + + virtual void simplifyActions(ActionDictDict &fieldActions); + + virtual bool shouldBeRemoved(ActionDictDict &fieldActions); + + virtual tristate updateTableSchema(TableSchema &table, Field* field, + QMap<QString, QString>& fieldMap); + + protected: + virtual void updateAlteringRequirements(); + + //! Performs physical execution of this action. + virtual tristate execute(Connection &conn, TableSchema &table); + + QString m_propertyName; + QVariant m_newValue; + }; + + //! Defines an action for removing a single table field. + class KEXI_DB_EXPORT RemoveFieldAction : public FieldActionBase { + public: + RemoveFieldAction(const QString& fieldName, int uid); + RemoveFieldAction(bool); + virtual ~RemoveFieldAction(); + + virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()); + + virtual void simplifyActions(ActionDictDict &fieldActions); + + virtual tristate updateTableSchema(TableSchema &table, Field* field, + QMap<QString, QString>& fieldMap); + + protected: + virtual void updateAlteringRequirements(); + + //! Performs physical execution of this action. + virtual tristate execute(Connection &conn, TableSchema &table); + }; + + //! Defines an action for inserting a single table field. + class KEXI_DB_EXPORT InsertFieldAction : public FieldActionBase { + public: + InsertFieldAction(int fieldIndex, KexiDB::Field *newField, int uid); + //copy ctor + InsertFieldAction(const InsertFieldAction& action); + InsertFieldAction(bool); + virtual ~InsertFieldAction(); + + int index() const { return m_index; } + void setIndex( int index ) { m_index = index; } + KexiDB::Field& field() const { return *m_field; } + void setField(KexiDB::Field* field); + virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()); + + virtual void simplifyActions(ActionDictDict &fieldActions); + + virtual tristate updateTableSchema(TableSchema &table, Field* field, + QMap<QString, QString>& fieldMap); + + protected: + virtual void updateAlteringRequirements(); + + //! Performs physical execution of this action. + virtual tristate execute(Connection &conn, TableSchema &table); + + int m_index; + + private: + KexiDB::Field *m_field; + }; + + /*! Defines an action for moving a single table field to a different + position within table schema. */ + class KEXI_DB_EXPORT MoveFieldPositionAction : public FieldActionBase { + public: + MoveFieldPositionAction(int fieldIndex, const QString& fieldName, int uid); + MoveFieldPositionAction(bool); + virtual ~MoveFieldPositionAction(); + + int index() const { return m_index; } + virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()); + + virtual void simplifyActions(ActionDictDict &fieldActions); + + protected: + virtual void updateAlteringRequirements(); + + //! Performs physical execution of this action. + virtual tristate execute(Connection &conn, TableSchema &table); + + int m_index; + }; + + AlterTableHandler(Connection &conn); + + virtual ~AlterTableHandler(); + + /*! Appends \a action for the alter table tool. */ + void addAction(ActionBase* action); + + /*! Provided for convenience, @see addAction(const ActionBase& action). */ + AlterTableHandler& operator<< ( ActionBase* action ); + + /*! Removes an action from the alter table tool at index \a index. */ + void removeAction(int index); + + /*! Removes all actions from the alter table tool. */ + void clear(); + + /*! Sets \a actions for the alter table tool. Previous actions are cleared. + \a actions will be owned by the AlterTableHandler object. */ + void setActions(const ActionList& actions); + + /*! \return a list of actions for this AlterTable object. + Use ActionBase::ListIterator to iterate over the list items. */ + const ActionList& actions() const; + + //! Arguments for AlterTableHandler::execute(). + class ExecutionArguments { + public: + ExecutionArguments() + : debugString(0) + , requirements(0) + , result(false) + , simulate(false) + , onlyComputeRequirements(false) + { + } + /*! If not 0, debug is directed here. Used only in the alter table test suite. */ + QString* debugString; + /*! Requrements computed, a combination of AlteringRequirements values. */ + int requirements; + /*! Set to true on success, to false on failure. */ + tristate result; + /*! Used only in the alter table test suite. */ + bool simulate : 1; + /*! Set to true if requirements should be computed + and the execute() method should return afterwards. */ + bool onlyComputeRequirements; + }; + + /*! Performs table alteration using predefined actions for table named \a tableName, + assuming it already exists. The Connection object passed to the constructor must exist, + must be connected and a database must be used. The connection must not be read-only. + + If args.simulate is true, the execution is only simulated, i.e. al lactions are processed + like for regular execution but no changes are performed physically. + This mode is used only for debugging purposes. + + @todo For some cases, table schema can completely change, so it will be needed + to refresh all objects depending on it. + Implement this! + + Sets args.result to true on success, to false on failure or when the above requirements are not met + (then, you can get a detailed error message from KexiDB::Object). + When the action has been cancelled (stopped), args.result is set to cancelled value. + If args.debugString is not 0, it will be filled with debugging output. + \return the new table schema object created as a result of schema altering. + The old table is returned if recreating table schema was not necessary or args.simulate is true. + 0 is returned if args.result is not true. */ + TableSchema* execute(const QString& tableName, ExecutionArguments & args); + + //! Displays debug information about all actions collected by the handler. + void debug(); + + /*! Like execute() with simulate set to true, but debug is directed to debugString. + This function is used only in the alter table test suite. */ +// tristate simulateExecution(const QString& tableName, QString& debugString); + + /*! Helper. \return a combination of AlteringRequirements values decribing altering type required + when a given property field's \a propertyName is altered. + Used internally AlterTableHandler. Moreover it can be also used in the Table Designer's code + as a temporary replacement before AlterTableHandler is fully implemented. + Thus, it is possible to identify properties that have no PhysicalAlteringRequired flag set + (e.g. caption or extended properties like visibleDecimalPlaces. */ + static int alteringTypeForProperty(const QCString& propertyName); + + protected: +// TableSchema* executeInternal(const QString& tableName, tristate& result, bool simulate = false, +// QString* debugString = 0); + + class Private; + Private *d; +}; +} + +#endif diff --git a/kexi/kexidb/common.pro b/kexi/kexidb/common.pro new file mode 100644 index 00000000..18235e2f --- /dev/null +++ b/kexi/kexidb/common.pro @@ -0,0 +1,8 @@ +# kexidb global rules + +include( $(KEXI)/common.pro ) + +win32:DEFINES += __KEXIDB__ + +win32:QMAKE_CXXFLAGS += /FI$(KEXI)/kexidb/global.h +win32:QMAKE_CFLAGS += /FI$(KEXI)/kexidb/global.h diff --git a/kexi/kexidb/connection.cpp b/kexi/kexidb/connection.cpp new file mode 100644 index 00000000..1a401a8a --- /dev/null +++ b/kexi/kexidb/connection.cpp @@ -0,0 +1,3552 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2007 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/connection.h> + +#include "error.h" +#include "connection_p.h" +#include "connectiondata.h" +#include "driver.h" +#include "driver_p.h" +#include "schemadata.h" +#include "tableschema.h" +#include "relationship.h" +#include "transaction.h" +#include "cursor.h" +#include "global.h" +#include "roweditbuffer.h" +#include "utils.h" +#include "dbproperties.h" +#include "lookupfieldschema.h" +#include "parser/parser.h" + +#include <kexiutils/utils.h> +#include <kexiutils/identifier.h> + +#include <qdir.h> +#include <qfileinfo.h> +#include <qguardedptr.h> +#include <qdom.h> + +#include <klocale.h> +#include <kdebug.h> + +#define KEXIDB_EXTENDED_TABLE_SCHEMA_VERSION 1 + +//#define KEXIDB_LOOKUP_FIELD_TEST + +namespace KexiDB { + +Connection::SelectStatementOptions::SelectStatementOptions() + : identifierEscaping(Driver::EscapeDriver|Driver::EscapeAsNecessary) + , alsoRetrieveROWID(false) + , addVisibleLookupColumns(true) +{ +} + +Connection::SelectStatementOptions::~SelectStatementOptions() +{ +} + +//================================================ + +ConnectionInternal::ConnectionInternal(Connection *conn) + : connection(conn) +{ +} + +ConnectionInternal::~ConnectionInternal() +{ +} + +//================================================ +//! @internal +class ConnectionPrivate +{ + public: + ConnectionPrivate(Connection* const conn, ConnectionData &conn_data) + : conn(conn) + , conn_data(&conn_data) + , tableSchemaChangeListeners(101) + , m_parser(0) + , tables_byname(101, false) + , queries_byname(101, false) + , kexiDBSystemTables(101) + , dont_remove_transactions(false) + , skip_databaseExists_check_in_useDatabase(false) + , default_trans_started_inside(false) + , isConnected(false) + , autoCommit(true) + { + tableSchemaChangeListeners.setAutoDelete(true); + obsoleteQueries.setAutoDelete(true); + + tables.setAutoDelete(true); + tables_byname.setAutoDelete(false);//tables is owner, not me + kexiDBSystemTables.setAutoDelete(true);//only system tables + queries.setAutoDelete(true); + queries_byname.setAutoDelete(false);//queries is owner, not me + + //reasonable sizes: TODO + tables.resize(101); + queries.resize(101); + } + ~ConnectionPrivate() + { + delete m_parser; + } + + void errorInvalidDBContents(const QString& details) { + conn->setError( ERR_INVALID_DATABASE_CONTENTS, i18n("Invalid database contents. ")+details); + } + + QString strItIsASystemObject() const { + return i18n("It is a system object."); + } + + inline Parser *parser() { return m_parser ? m_parser : (m_parser = new Parser(conn)); } + + Connection* const conn; //!< The \a Connection instance this \a ConnectionPrivate belongs to. + QGuardedPtr<ConnectionData> conn_data; //!< the \a ConnectionData used within that connection. + + /*! Default transaction handle. + If transactions are supported: Any operation on database (e.g. inserts) + that is started without specifying transaction context, will be performed + in the context of this transaction. */ + Transaction default_trans; + QValueList<Transaction> transactions; + + QPtrDict< QPtrList<Connection::TableSchemaChangeListenerInterface> > tableSchemaChangeListeners; + + //! Used in Connection::setQuerySchemaObsolete( const QString& queryName ) + //! to collect obsolete queries. THese are deleted on connection deleting. + QPtrList<QuerySchema> obsoleteQueries; + + + //! server version information for this connection. + KexiDB::ServerVersionInfo serverVersion; + + //! Daabase version information for this connection. + KexiDB::DatabaseVersionInfo databaseVersion; + + Parser *m_parser; + + //! Table schemas retrieved on demand with tableSchema() + QIntDict<TableSchema> tables; + QDict<TableSchema> tables_byname; + QIntDict<QuerySchema> queries; + QDict<QuerySchema> queries_byname; + + //! used just for removing system TableSchema objects on db close. + QPtrDict<TableSchema> kexiDBSystemTables; + + //! Database properties + DatabaseProperties* dbProperties; + + QString availableDatabaseName; //!< used by anyAvailableDatabaseName() + QString usedDatabase; //!< database name that is opened now (the currentDatabase() name) + + //! true if rollbackTransaction() and commitTransaction() shouldn't remove + //! the transaction object from 'transactions' list; used by closeDatabase() + bool dont_remove_transactions : 1; + + //! used to avoid endless recursion between useDatabase() and databaseExists() + //! when useTemporaryDatabaseIfNeeded() works + bool skip_databaseExists_check_in_useDatabase : 1; + + /*! Used when single transactions are only supported (Driver::SingleTransactions). + True value means default transaction has been started inside connection object + (by beginAutoCommitTransaction()), otherwise default transaction has been started outside + of the object (e.g. before createTable()), so we shouldn't autocommit the transaction + in commitAutoCommitTransaction(). Also, beginAutoCommitTransaction() doesn't restarts + transaction if default_trans_started_inside is false. Such behaviour allows user to + execute a sequence of actions like CREATE TABLE...; INSERT DATA...; within a single transaction + and commit it or rollback by hand. */ + bool default_trans_started_inside : 1; + + bool isConnected : 1; + + bool autoCommit : 1; + + /*! True for read only connection. Used especially for file-based drivers. */ + bool readOnly : 1; +}; + +}//namespace KexiDB + +//================================================ +using namespace KexiDB; + +//! static: list of internal KexiDB system table names +QStringList KexiDB_kexiDBSystemTableNames; + +Connection::Connection( Driver *driver, ConnectionData &conn_data ) + : QObject() + ,KexiDB::Object() + ,d(new ConnectionPrivate(this, conn_data)) + ,m_driver(driver) + ,m_destructor_started(false) +{ + d->dbProperties = new DatabaseProperties(this); + m_cursors.setAutoDelete(true); +// d->transactions.setAutoDelete(true); + //reasonable sizes: TODO + m_cursors.resize(101); +// d->transactions.resize(101);//woohoo! so many transactions? + m_sql.reserve(0x4000); +} + +void Connection::destroy() +{ + disconnect(); + //do not allow the driver to touch me: I will kill myself. + m_driver->d->connections.take( this ); +} + +Connection::~Connection() +{ + m_destructor_started = true; +// KexiDBDbg << "Connection::~Connection()" << endl; + delete d->dbProperties; + delete d; + d = 0; +/* if (m_driver) { + if (m_is_connected) { + //delete own table schemas + d->tables.clear(); + //delete own cursors: + m_cursors.clear(); + } + //do not allow the driver to touch me: I will kill myself. + m_driver->m_connections.take( this ); + }*/ +} + +ConnectionData* Connection::data() const +{ + return d->conn_data; +} + +bool Connection::connect() +{ + clearError(); + if (d->isConnected) { + setError(ERR_ALREADY_CONNECTED, i18n("Connection already established.") ); + return false; + } + + d->serverVersion.clear(); + if (!(d->isConnected = drv_connect(d->serverVersion))) { + setError(m_driver->isFileDriver() ? + i18n("Could not open \"%1\" project file.").arg(QDir::convertSeparators(d->conn_data->fileName())) + : i18n("Could not connect to \"%1\" database server.").arg(d->conn_data->serverInfoString()) ); + } + return d->isConnected; +} + +bool Connection::isDatabaseUsed() const +{ + return !d->usedDatabase.isEmpty() && d->isConnected && drv_isDatabaseUsed(); +} + +void Connection::clearError() +{ + Object::clearError(); + m_sql = QString::null; +} + +bool Connection::disconnect() +{ + clearError(); + if (!d->isConnected) + return true; + + if (!closeDatabase()) + return false; + + bool ok = drv_disconnect(); + if (ok) + d->isConnected = false; + return ok; +} + +bool Connection::isConnected() const +{ + return d->isConnected; +} + +bool Connection::checkConnected() +{ + if (d->isConnected) { + clearError(); + return true; + } + setError(ERR_NO_CONNECTION, i18n("Not connected to the database server.") ); + return false; +} + +bool Connection::checkIsDatabaseUsed() +{ + if (isDatabaseUsed()) { + clearError(); + return true; + } + setError(ERR_NO_DB_USED, i18n("Currently no database is used.") ); + return false; +} + +QStringList Connection::databaseNames(bool also_system_db) +{ + KexiDBDbg << "Connection::databaseNames("<<also_system_db<<")"<< endl; + if (!checkConnected()) + return QStringList(); + + QString tmpdbName; + //some engines need to have opened any database before executing "create database" + if (!useTemporaryDatabaseIfNeeded(tmpdbName)) + return QStringList(); + + QStringList list, non_system_list; + + bool ret = drv_getDatabasesList( list ); + + if (!tmpdbName.isEmpty()) { + //whatever result is - now we have to close temporary opened database: + if (!closeDatabase()) + return QStringList(); + } + + if (!ret) + return QStringList(); + + if (also_system_db) + return list; + //filter system databases: + for (QStringList::ConstIterator it = list.constBegin(); it!=list.constEnd(); ++it) { + KexiDBDbg << "Connection::databaseNames(): " << *it << endl; + if (!m_driver->isSystemDatabaseName(*it)) { + KexiDBDbg << "add " << *it << endl; + non_system_list << (*it); + } + } + return non_system_list; +} + +bool Connection::drv_getDatabasesList( QStringList &list ) +{ + list.clear(); + return true; +} + +bool Connection::drv_databaseExists( const QString &dbName, bool ignoreErrors ) +{ + QStringList list = databaseNames(true);//also system + if (error()) { + return false; + } + + if (list.find( dbName )==list.end()) { + if (!ignoreErrors) + setError(ERR_OBJECT_NOT_FOUND, i18n("The database \"%1\" does not exist.").arg(dbName)); + return false; + } + + return true; +} + +bool Connection::databaseExists( const QString &dbName, bool ignoreErrors ) +{ +// KexiDBDbg << "Connection::databaseExists(" << dbName << "," << ignoreErrors << ")" << endl; + if (!checkConnected()) + return false; + clearError(); + + if (m_driver->isFileDriver()) { + //for file-based db: file must exists and be accessible +//js: moved from useDatabase(): + QFileInfo file(d->conn_data->fileName()); + if (!file.exists() || ( !file.isFile() && !file.isSymLink()) ) { + if (!ignoreErrors) + setError(ERR_OBJECT_NOT_FOUND, i18n("Database file \"%1\" does not exist.") + .arg(QDir::convertSeparators(d->conn_data->fileName())) ); + return false; + } + if (!file.isReadable()) { + if (!ignoreErrors) + setError(ERR_ACCESS_RIGHTS, i18n("Database file \"%1\" is not readable.") + .arg(QDir::convertSeparators(d->conn_data->fileName())) ); + return false; + } + if (!file.isWritable()) { + if (!ignoreErrors) + setError(ERR_ACCESS_RIGHTS, i18n("Database file \"%1\" is not writable.") + .arg(QDir::convertSeparators(d->conn_data->fileName())) ); + return false; + } + return true; + } + + QString tmpdbName; + //some engines need to have opened any database before executing "create database" + const bool orig_skip_databaseExists_check_in_useDatabase = d->skip_databaseExists_check_in_useDatabase; + d->skip_databaseExists_check_in_useDatabase = true; + bool ret = useTemporaryDatabaseIfNeeded(tmpdbName); + d->skip_databaseExists_check_in_useDatabase = orig_skip_databaseExists_check_in_useDatabase; + if (!ret) + return false; + + ret = drv_databaseExists(dbName, ignoreErrors); + + if (!tmpdbName.isEmpty()) { + //whatever result is - now we have to close temporary opened database: + if (!closeDatabase()) + return false; + } + + return ret; +} + +#define createDatabase_CLOSE \ + { if (!closeDatabase()) { \ + setError(i18n("Database \"%1\" created but could not be closed after creation.").arg(dbName) ); \ + return false; \ + } } + +#define createDatabase_ERROR \ + { createDatabase_CLOSE; return false; } + + +bool Connection::createDatabase( const QString &dbName ) +{ + if (!checkConnected()) + return false; + + if (databaseExists( dbName )) { + setError(ERR_OBJECT_EXISTS, i18n("Database \"%1\" already exists.").arg(dbName) ); + return false; + } + if (m_driver->isSystemDatabaseName( dbName )) { + setError(ERR_SYSTEM_NAME_RESERVED, + i18n("Cannot create database \"%1\". This name is reserved for system database.").arg(dbName) ); + return false; + } + if (m_driver->isFileDriver()) { + //update connection data if filename differs + d->conn_data->setFileName( dbName ); + } + + QString tmpdbName; + //some engines need to have opened any database before executing "create database" + if (!useTemporaryDatabaseIfNeeded(tmpdbName)) + return false; + + //low-level create + if (!drv_createDatabase( dbName )) { + setError(i18n("Error creating database \"%1\" on the server.").arg(dbName) ); + closeDatabase();//sanity + return false; + } + + if (!tmpdbName.isEmpty()) { + //whatever result is - now we have to close temporary opened database: + if (!closeDatabase()) + return false; + } + + if (!tmpdbName.isEmpty() || !m_driver->d->isDBOpenedAfterCreate) { + //db need to be opened + if (!useDatabase( dbName, false/*not yet kexi compatible!*/ )) { + setError(i18n("Database \"%1\" created but could not be opened.").arg(dbName) ); + return false; + } + } + else { + //just for the rule + d->usedDatabase = dbName; + } + + Transaction trans; + if (m_driver->transactionsSupported()) { + trans = beginTransaction(); + if (!trans.active()) + return false; + } +//not needed since closeDatabase() rollbacks transaction: TransactionGuard trans_g(this); +// if (error()) +// return false; + + //-create system tables schema objects + if (!setupKexiDBSystemSchema()) + return false; + + //-physically create system tables + for (QPtrDictIterator<TableSchema> it(d->kexiDBSystemTables); it.current(); ++it) { + if (!drv_createTable( it.current()->name() )) + createDatabase_ERROR; + } + +/* moved to KexiProject... + + //-create default part info + TableSchema *ts; + if (!(ts = tableSchema("kexi__parts"))) + createDatabase_ERROR; + FieldList *fl = ts->subList("p_id", "p_name", "p_mime", "p_url"); + if (!fl) + createDatabase_ERROR; + if (!insertRecord(*fl, QVariant(1), QVariant("Tables"), QVariant("kexi/table"), QVariant("http://koffice.org/kexi/"))) + createDatabase_ERROR; + if (!insertRecord(*fl, QVariant(2), QVariant("Queries"), QVariant("kexi/query"), QVariant("http://koffice.org/kexi/"))) + createDatabase_ERROR; +*/ + + //-insert KexiDB version info: + TableSchema *t_db = tableSchema("kexi__db"); + if (!t_db) + createDatabase_ERROR; + if ( !insertRecord(*t_db, "kexidb_major_ver", KexiDB::version().major) + || !insertRecord(*t_db, "kexidb_minor_ver", KexiDB::version().minor)) + createDatabase_ERROR; + + if (trans.active() && !commitTransaction(trans)) + createDatabase_ERROR; + + createDatabase_CLOSE; + return true; +} + +#undef createDatabase_CLOSE +#undef createDatabase_ERROR + +bool Connection::useDatabase( const QString &dbName, bool kexiCompatible, bool *cancelled, MessageHandler* msgHandler ) +{ + if (cancelled) + *cancelled = false; + KexiDBDbg << "Connection::useDatabase(" << dbName << "," << kexiCompatible <<")" << endl; + if (!checkConnected()) + return false; + + if (dbName.isEmpty()) + return false; + QString my_dbName = dbName; +// if (my_dbName.isEmpty()) { +// const QStringList& db_lst = databaseNames(); +// if (!db_lst.isEmpty()) +// my_dbName = db_lst.first(); +// } + if (d->usedDatabase == my_dbName) + return true; //already used + + if (!d->skip_databaseExists_check_in_useDatabase) { + if (!databaseExists(my_dbName, false /*don't ignore errors*/)) + return false; //database must exist + } + + if (!d->usedDatabase.isEmpty() && !closeDatabase()) //close db if already used + return false; + + d->usedDatabase = ""; + + if (!drv_useDatabase( my_dbName, cancelled, msgHandler )) { + if (cancelled && *cancelled) + return false; + QString msg(i18n("Opening database \"%1\" failed.").arg( my_dbName )); + if (error()) + setError( this, msg ); + else + setError( msg ); + return false; + } + + //-create system tables schema objects + if (!setupKexiDBSystemSchema()) + return false; + + if (kexiCompatible && my_dbName.lower()!=anyAvailableDatabaseName().lower()) { + //-get global database information + int num; + bool ok; +// static QString notfound_str = i18n("\"%1\" database property not found"); + num = d->dbProperties->value("kexidb_major_ver").toInt(&ok); + if (!ok) + return false; + d->databaseVersion.major = num; +/* if (true!=querySingleNumber( + "select db_value from kexi__db where db_property=" + m_driver->escapeString(QString("kexidb_major_ver")), num)) { + d->errorInvalidDBContents(notfound_str.arg("kexidb_major_ver")); + return false; + }*/ + num = d->dbProperties->value("kexidb_minor_ver").toInt(&ok); + if (!ok) + return false; + d->databaseVersion.minor = num; +/* if (true!=querySingleNumber( + "select db_value from kexi__db where db_property=" + m_driver->escapeString(QString("kexidb_minor_ver")), num)) { + d->errorInvalidDBContents(notfound_str.arg("kexidb_minor_ver")); + return false; + }*/ + +#if 0 //this is already checked in DriverManagerInternal::lookupDrivers() + //** error if major version does not match + if (m_driver->versionMajor()!=KexiDB::versionMajor()) { + setError(ERR_INCOMPAT_DATABASE_VERSION, + i18n("Database version (%1) does not match Kexi application's version (%2)") + .arg( QString("%1.%2").arg(versionMajor()).arg(versionMinor()) ) + .arg( QString("%1.%2").arg(KexiDB::versionMajor()).arg(KexiDB::versionMinor()) ) ); + return false; + } + if (m_driver->versionMinor()!=KexiDB::versionMinor()) { + //js TODO: COMPATIBILITY CODE HERE! + //js TODO: CONVERSION CODE HERE (or signal that conversion is needed) + } +#endif + } + d->usedDatabase = my_dbName; + return true; +} + +bool Connection::closeDatabase() +{ + if (d->usedDatabase.isEmpty()) + return true; //no db used + if (!checkConnected()) + return true; + + bool ret = true; + +/*! \todo (js) add CLEVER algorithm here for nested transactions */ + if (m_driver->transactionsSupported()) { + //rollback all transactions + QValueList<Transaction>::ConstIterator it; + d->dont_remove_transactions=true; //lock! + for (it=d->transactions.constBegin(); it!= d->transactions.constEnd(); ++it) { + if (!rollbackTransaction(*it)) {//rollback as much as you can, don't stop on prev. errors + ret = false; + } + else { + KexiDBDbg << "Connection::closeDatabase(): transaction rolled back!" << endl; + KexiDBDbg << "Connection::closeDatabase(): trans.refcount==" << + ((*it).m_data ? QString::number((*it).m_data->refcount) : "(null)") << endl; + } + } + d->dont_remove_transactions=false; //unlock! + d->transactions.clear(); //free trans. data + } + + //delete own cursors: + m_cursors.clear(); + //delete own schemas + d->tables.clear(); + d->kexiDBSystemTables.clear(); + d->queries.clear(); + + if (!drv_closeDatabase()) + return false; + + d->usedDatabase = ""; +// KexiDBDbg << "Connection::closeDatabase(): " << ret << endl; + return ret; +} + +QString Connection::currentDatabase() const +{ + return d->usedDatabase; +} + +bool Connection::useTemporaryDatabaseIfNeeded(QString &tmpdbName) +{ + if (!m_driver->isFileDriver() && m_driver->beh->USING_DATABASE_REQUIRED_TO_CONNECT + && !isDatabaseUsed()) { + //we have no db used, but it is required by engine to have used any! + tmpdbName = anyAvailableDatabaseName(); + if (tmpdbName.isEmpty()) { + setError(ERR_NO_DB_USED, i18n("Cannot find any database for temporary connection.") ); + return false; + } + const bool orig_skip_databaseExists_check_in_useDatabase = d->skip_databaseExists_check_in_useDatabase; + d->skip_databaseExists_check_in_useDatabase = true; + bool ret = useDatabase(tmpdbName, false); + d->skip_databaseExists_check_in_useDatabase = orig_skip_databaseExists_check_in_useDatabase; + if (!ret) { + setError(errorNum(), + i18n("Error during starting temporary connection using \"%1\" database name.") + .arg(tmpdbName) ); + return false; + } + } + return true; +} + +bool Connection::dropDatabase( const QString &dbName ) +{ + if (!checkConnected()) + return false; + + QString dbToDrop; + if (dbName.isEmpty() && d->usedDatabase.isEmpty()) { + if (!m_driver->isFileDriver() + || (m_driver->isFileDriver() && d->conn_data->fileName().isEmpty()) ) { + setError(ERR_NO_NAME_SPECIFIED, i18n("Cannot drop database - name not specified.") ); + return false; + } + //this is a file driver so reuse previously passed filename + dbToDrop = d->conn_data->fileName(); + } + else { + if (dbName.isEmpty()) { + dbToDrop = d->usedDatabase; + } else { + if (m_driver->isFileDriver()) //lets get full path + dbToDrop = QFileInfo(dbName).absFilePath(); + else + dbToDrop = dbName; + } + } + + if (dbToDrop.isEmpty()) { + setError(ERR_NO_NAME_SPECIFIED, i18n("Cannot delete database - name not specified.") ); + return false; + } + + if (m_driver->isSystemDatabaseName( dbToDrop )) { + setError(ERR_SYSTEM_NAME_RESERVED, i18n("Cannot delete system database \"%1\".").arg(dbToDrop) ); + return false; + } + + if (isDatabaseUsed() && d->usedDatabase == dbToDrop) { + //we need to close database because cannot drop used this database + if (!closeDatabase()) + return false; + } + + QString tmpdbName; + //some engines need to have opened any database before executing "drop database" + if (!useTemporaryDatabaseIfNeeded(tmpdbName)) + return false; + + //ok, now we have access to dropping + bool ret = drv_dropDatabase( dbToDrop ); + + if (!tmpdbName.isEmpty()) { + //whatever result is - now we have to close temporary opened database: + if (!closeDatabase()) + return false; + } + return ret; +} + +QStringList Connection::objectNames(int objType, bool* ok) +{ + QStringList list; + + if (!checkIsDatabaseUsed()) { + if(ok) + *ok = false; + return list; + } + + QString sql; + if (objType==KexiDB::AnyObjectType) + sql = "SELECT o_name FROM kexi__objects"; + else + sql = QString::fromLatin1("SELECT o_name FROM kexi__objects WHERE o_type=%1").arg(objType); + + Cursor *c = executeQuery(sql); + if (!c) { + if(ok) + *ok = false; + return list; + } + + for (c->moveFirst(); !c->eof(); c->moveNext()) { + QString name = c->value(0).toString(); + if (KexiUtils::isIdentifier( name )) { + list.append(name); + } + } + + if (!deleteCursor(c)) { + if(ok) + *ok = false; + return list; + } + + if(ok) + *ok = true; + return list; +} + +QStringList Connection::tableNames(bool also_system_tables) +{ + bool ok = true; + QStringList list = objectNames(TableObjectType, &ok); + if (also_system_tables && ok) { + list += Connection::kexiDBSystemTableNames(); + } + return list; +} + +//! \todo (js): this will depend on KexiDB lib version +const QStringList& Connection::kexiDBSystemTableNames() +{ + if (KexiDB_kexiDBSystemTableNames.isEmpty()) { + KexiDB_kexiDBSystemTableNames + << "kexi__objects" + << "kexi__objectdata" + << "kexi__fields" +// << "kexi__querydata" +// << "kexi__queryfields" +// << "kexi__querytables" + << "kexi__db" + ; + } + return KexiDB_kexiDBSystemTableNames; +} + +KexiDB::ServerVersionInfo* Connection::serverVersion() const +{ + return isConnected() ? &d->serverVersion : 0; +} + +KexiDB::DatabaseVersionInfo* Connection::databaseVersion() const +{ + return isDatabaseUsed() ? &d->databaseVersion : 0; +} + +DatabaseProperties& Connection::databaseProperties() +{ + return *d->dbProperties; +} + +QValueList<int> Connection::tableIds() +{ + return objectIds(KexiDB::TableObjectType); +} + +QValueList<int> Connection::queryIds() +{ + return objectIds(KexiDB::QueryObjectType); +} + +QValueList<int> Connection::objectIds(int objType) +{ + QValueList<int> list; + + if (!checkIsDatabaseUsed()) + return list; + + Cursor *c = executeQuery( + QString::fromLatin1("SELECT o_id, o_name FROM kexi__objects WHERE o_type=%1").arg(objType)); + if (!c) + return list; + for (c->moveFirst(); !c->eof(); c->moveNext()) + { + QString tname = c->value(1).toString(); //kexi__objects.o_name + if (KexiUtils::isIdentifier( tname )) { + list.append(c->value(0).toInt()); //kexi__objects.o_id + } + } + + deleteCursor(c); + + return list; +} + +QString Connection::createTableStatement( const KexiDB::TableSchema& tableSchema ) const +{ +// Each SQL identifier needs to be escaped in the generated query. + QString sql; + sql.reserve(4096); + sql = "CREATE TABLE " + escapeIdentifier(tableSchema.name()) + " ("; + bool first=true; + Field::ListIterator it( tableSchema.m_fields ); + Field *field; + for (;(field = it.current())!=0; ++it) { + if (first) + first = false; + else + sql += ", "; + QString v = escapeIdentifier(field->name()) + " "; + const bool autoinc = field->isAutoIncrement(); + const bool pk = field->isPrimaryKey() || (autoinc && m_driver->beh->AUTO_INCREMENT_REQUIRES_PK); +//TODO: warning: ^^^^^ this allows only one autonumber per table when AUTO_INCREMENT_REQUIRES_PK==true! + if (autoinc && m_driver->beh->SPECIAL_AUTO_INCREMENT_DEF) { + if (pk) + v += m_driver->beh->AUTO_INCREMENT_TYPE + " " + m_driver->beh->AUTO_INCREMENT_PK_FIELD_OPTION; + else + v += m_driver->beh->AUTO_INCREMENT_TYPE + " " + m_driver->beh->AUTO_INCREMENT_FIELD_OPTION; + } + else { + if (autoinc && !m_driver->beh->AUTO_INCREMENT_TYPE.isEmpty()) + v += m_driver->beh->AUTO_INCREMENT_TYPE; + else + v += m_driver->sqlTypeName(field->type(), field->precision()); + + if (field->isUnsigned()) + v += (" " + m_driver->beh->UNSIGNED_TYPE_KEYWORD); + + if (field->isFPNumericType() && field->precision()>0) { + if (field->scale()>0) + v += QString::fromLatin1("(%1,%2)").arg(field->precision()).arg(field->scale()); + else + v += QString::fromLatin1("(%1)").arg(field->precision()); + } + else if (field->type()==Field::Text && field->length()>0) + v += QString::fromLatin1("(%1)").arg(field->length()); + + if (autoinc) + v += (" " + + (pk ? m_driver->beh->AUTO_INCREMENT_PK_FIELD_OPTION : m_driver->beh->AUTO_INCREMENT_FIELD_OPTION)); + else + //TODO: here is automatically a single-field key created + if (pk) + v += " PRIMARY KEY"; + if (!pk && field->isUniqueKey()) + v += " UNIQUE"; +///@todo IS this ok for all engines?: if (!autoinc && !field->isPrimaryKey() && field->isNotNull()) + if (!autoinc && !pk && field->isNotNull()) + v += " NOT NULL"; //only add not null option if no autocommit is set + if (field->defaultValue().isValid()) { + QString valToSQL( m_driver->valueToSQL( field, field->defaultValue() ) ); + if (!valToSQL.isEmpty()) //for sanity + v += QString::fromLatin1(" DEFAULT ") + valToSQL; + } + } + sql += v; + } + sql += ")"; + return sql; +} + +//yeah, it is very efficient: +#define C_A(a) , const QVariant& c ## a + +#define V_A0 m_driver->valueToSQL( tableSchema.field(0), c0 ) +#define V_A(a) +","+m_driver->valueToSQL( \ + tableSchema.field(a) ? tableSchema.field(a)->type() : Field::Text, c ## a ) + +// KexiDBDbg << "******** " << QString("INSERT INTO ") + +// escapeIdentifier(tableSchema.name()) + +// " VALUES (" + vals + ")" <<endl; + +#define C_INS_REC(args, vals) \ + bool Connection::insertRecord(KexiDB::TableSchema &tableSchema args) {\ + return executeSQL( \ + QString("INSERT INTO ") + escapeIdentifier(tableSchema.name()) + " VALUES (" + vals + ")" \ + ); \ + } + +#define C_INS_REC_ALL \ +C_INS_REC( C_A(0), V_A0 ) \ +C_INS_REC( C_A(0) C_A(1), V_A0 V_A(1) ) \ +C_INS_REC( C_A(0) C_A(1) C_A(2), V_A0 V_A(1) V_A(2) ) \ +C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3), V_A0 V_A(1) V_A(2) V_A(3) ) \ +C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) ) \ +C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) ) \ +C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5) C_A(6), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) V_A(6) ) \ +C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5) C_A(6) C_A(7), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) V_A(6) V_A(7) ) + +C_INS_REC_ALL + +#undef V_A0 +#undef V_A +#undef C_INS_REC + +#define V_A0 value += m_driver->valueToSQL( flist->first(), c0 ); +#define V_A( a ) value += ("," + m_driver->valueToSQL( flist->next(), c ## a )); +//#define V_ALAST( a ) valueToSQL( flist->last(), c ## a ) + + +#define C_INS_REC(args, vals) \ + bool Connection::insertRecord(FieldList& fields args) \ + { \ + QString value; \ + Field::List *flist = fields.fields(); \ + vals \ + return executeSQL( \ + QString("INSERT INTO ") + \ + ((fields.fields()->first() && fields.fields()->first()->table()) ? \ + escapeIdentifier(fields.fields()->first()->table()->name()) : \ + "??") \ + + "(" + fields.sqlFieldsList(m_driver) + ") VALUES (" + value + ")" \ + ); \ + } + +C_INS_REC_ALL + +#undef C_A +#undef V_A +#undef V_ALAST +#undef C_INS_REC +#undef C_INS_REC_ALL + +bool Connection::insertRecord(TableSchema &tableSchema, QValueList<QVariant>& values) +{ +// Each SQL identifier needs to be escaped in the generated query. + Field::List *fields = tableSchema.fields(); + Field *f = fields->first(); +// QString s_val; +// s_val.reserve(4096); + m_sql = QString::null; + QValueList<QVariant>::ConstIterator it = values.constBegin(); +// int i=0; + while (f && (it!=values.end())) { + if (m_sql.isEmpty()) + m_sql = QString("INSERT INTO ") + + escapeIdentifier(tableSchema.name()) + + " VALUES ("; + else + m_sql += ","; + m_sql += m_driver->valueToSQL( f, *it ); +// KexiDBDbg << "val" << i++ << ": " << m_driver->valueToSQL( f, *it ) << endl; + ++it; + f=fields->next(); + } + m_sql += ")"; + +// KexiDBDbg<<"******** "<< m_sql << endl; + return executeSQL(m_sql); +} + +bool Connection::insertRecord(FieldList& fields, QValueList<QVariant>& values) +{ +// Each SQL identifier needs to be escaped in the generated query. + Field::List *flist = fields.fields(); + Field *f = flist->first(); + if (!f) + return false; +// QString s_val; +// s_val.reserve(4096); + m_sql = QString::null; + QValueList<QVariant>::ConstIterator it = values.constBegin(); +// int i=0; + while (f && (it!=values.constEnd())) { + if (m_sql.isEmpty()) + m_sql = QString("INSERT INTO ") + + escapeIdentifier(flist->first()->table()->name()) + "(" + + fields.sqlFieldsList(m_driver) + ") VALUES ("; + else + m_sql += ","; + m_sql += m_driver->valueToSQL( f, *it ); +// KexiDBDbg << "val" << i++ << ": " << m_driver->valueToSQL( f, *it ) << endl; + ++it; + f=flist->next(); + } + m_sql += ")"; + + return executeSQL(m_sql); +} + +bool Connection::executeSQL( const QString& statement ) +{ + m_sql = statement; //remember for error handling + if (!drv_executeSQL( m_sql )) { + m_errMsg = QString::null; //clear as this could be most probably jsut "Unknown error" string. + m_errorSql = statement; + setError(this, ERR_SQL_EXECUTION_ERROR, i18n("Error while executing SQL statement.")); + return false; + } + return true; +} + +QString Connection::selectStatement( KexiDB::QuerySchema& querySchema, + const QValueList<QVariant>& params, + const SelectStatementOptions& options) const +{ +//"SELECT FROM ..." is theoretically allowed " +//if (querySchema.fieldCount()<1) +// return QString::null; +// Each SQL identifier needs to be escaped in the generated query. + + if (!querySchema.statement().isEmpty()) + return querySchema.statement(); + +//! @todo looking at singleTable is visually nice but a field name can conflict +//! with function or variable name... + Field *f; + uint number = 0; + bool singleTable = querySchema.tables()->count() <= 1; + if (singleTable) { + //make sure we will have single table: + for (Field::ListIterator it = querySchema.fieldsIterator(); (f = it.current()); ++it, number++) { + if (querySchema.isColumnVisible(number) && f->table() && f->table()->lookupFieldSchema( *f )) { + //uups, no, there's at least one left join + singleTable = false; + break; + } + } + } + + QString sql; //final sql string + sql.reserve(4096); +//unused QString s_from_additional; //additional tables list needed for lookup fields + QString s_additional_joins; //additional joins needed for lookup fields + QString s_additional_fields; //additional fields to append to the fields list + uint internalUniqueTableAliasNumber = 0; //used to build internalUniqueTableAliases + uint internalUniqueQueryAliasNumber = 0; //used to build internalUniqueQueryAliases + number = 0; + QPtrList<QuerySchema> subqueries_for_lookup_data; // subqueries will be added to FROM section + QString kexidb_subquery_prefix("__kexidb_subquery_"); + for (Field::ListIterator it = querySchema.fieldsIterator(); (f = it.current()); ++it, number++) { + if (querySchema.isColumnVisible(number)) { + if (!sql.isEmpty()) + sql += QString::fromLatin1(", "); + + if (f->isQueryAsterisk()) { + if (!singleTable && static_cast<QueryAsterisk*>(f)->isSingleTableAsterisk()) //single-table * + sql += escapeIdentifier(f->table()->name(), options.identifierEscaping) + + QString::fromLatin1(".*"); + else //all-tables * (or simplified table.* when there's only one table) + sql += QString::fromLatin1("*"); + } + else { + if (f->isExpression()) { + sql += f->expression()->toString(); + } + else { + if (!f->table()) //sanity check + return QString::null; + + QString tableName; + int tablePosition = querySchema.tableBoundToColumn(number); + if (tablePosition>=0) + tableName = querySchema.tableAlias(tablePosition); + if (tableName.isEmpty()) + tableName = f->table()->name(); + + if (!singleTable) { + sql += (escapeIdentifier(tableName, options.identifierEscaping) + "."); + } + sql += escapeIdentifier(f->name(), options.identifierEscaping); + } + QString aliasString = QString(querySchema.columnAlias(number)); + if (!aliasString.isEmpty()) + sql += (QString::fromLatin1(" AS ") + aliasString); +//! @todo add option that allows to omit "AS" keyword + } + LookupFieldSchema *lookupFieldSchema = (options.addVisibleLookupColumns && f->table()) + ? f->table()->lookupFieldSchema( *f ) : 0; + if (lookupFieldSchema && lookupFieldSchema->boundColumn()>=0) { + // Lookup field schema found + // Now we also need to fetch "visible" value from the lookup table, not only the value of binding. + // -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken) + // "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField" + LookupFieldSchema::RowSource& rowSource = lookupFieldSchema->rowSource(); + if (rowSource.type()==LookupFieldSchema::RowSource::Table) { + TableSchema *lookupTable = querySchema.connection()->tableSchema( rowSource.name() ); + FieldList* visibleColumns = 0; + Field *boundField = 0; + if (lookupTable + && (uint)lookupFieldSchema->boundColumn() < lookupTable->fieldCount() + && (visibleColumns = lookupTable->subList( lookupFieldSchema->visibleColumns() )) + && (boundField = lookupTable->field( lookupFieldSchema->boundColumn() ))) + { + //add LEFT OUTER JOIN + if (!s_additional_joins.isEmpty()) + s_additional_joins += QString::fromLatin1(" "); + QString internalUniqueTableAlias( QString("__kexidb_") + lookupTable->name() + "_" + + QString::number(internalUniqueTableAliasNumber++) ); + s_additional_joins += QString("LEFT OUTER JOIN %1 AS %2 ON %3.%4=%5.%6") + .arg(escapeIdentifier(lookupTable->name(), options.identifierEscaping)) + .arg(internalUniqueTableAlias) + .arg(escapeIdentifier(f->table()->name(), options.identifierEscaping)) + .arg(escapeIdentifier(f->name(), options.identifierEscaping)) + .arg(internalUniqueTableAlias) + .arg(escapeIdentifier(boundField->name(), options.identifierEscaping)); + + //add visibleField to the list of SELECTed fields //if it is not yet present there +//not needed if (!querySchema.findTableField( visibleField->table()->name()+"."+visibleField->name() )) { +#if 0 + if (!querySchema.table( visibleField->table()->name() )) { +/* not true + //table should be added after FROM + if (!s_from_additional.isEmpty()) + s_from_additional += QString::fromLatin1(", "); + s_from_additional += escapeIdentifier(visibleField->table()->name(), options.identifierEscaping); + */ + } +#endif + if (!s_additional_fields.isEmpty()) + s_additional_fields += QString::fromLatin1(", "); +// s_additional_fields += (internalUniqueTableAlias + "." //escapeIdentifier(visibleField->table()->name(), options.identifierEscaping) + "." +// escapeIdentifier(visibleField->name(), options.identifierEscaping)); +//! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?" +//! @todo Add possibility for joining the values at client side. + s_additional_fields += visibleColumns->sqlFieldsList( + driver(), " || ' ' || ", internalUniqueTableAlias, options.identifierEscaping); + } + delete visibleColumns; + } + else if (rowSource.type()==LookupFieldSchema::RowSource::Query) { + QuerySchema *lookupQuery = querySchema.connection()->querySchema( rowSource.name() ); + if (!lookupQuery) { + KexiDBWarn << "Connection::selectStatement(): !lookupQuery" << endl; + return QString::null; + } + const QueryColumnInfo::Vector fieldsExpanded( lookupQuery->fieldsExpanded() ); + if ((uint)lookupFieldSchema->boundColumn() >= fieldsExpanded.count()) { + KexiDBWarn << "Connection::selectStatement(): (uint)lookupFieldSchema->boundColumn() >= fieldsExpanded.count()" << endl; + return QString::null; + } + QueryColumnInfo *boundColumnInfo = fieldsExpanded.at( lookupFieldSchema->boundColumn() ); + if (!boundColumnInfo) { + KexiDBWarn << "Connection::selectStatement(): !boundColumnInfo" << endl; + return QString::null; + } + Field *boundField = boundColumnInfo->field; + if (!boundField) { + KexiDBWarn << "Connection::selectStatement(): !boundField" << endl; + return QString::null; + } + //add LEFT OUTER JOIN + if (!s_additional_joins.isEmpty()) + s_additional_joins += QString::fromLatin1(" "); + QString internalUniqueQueryAlias( + kexidb_subquery_prefix + lookupQuery->name() + "_" + + QString::number(internalUniqueQueryAliasNumber++) ); + s_additional_joins += QString("LEFT OUTER JOIN (%1) AS %2 ON %3.%4=%5.%6") + .arg(selectStatement( *lookupQuery, params, options )) + .arg(internalUniqueQueryAlias) + .arg(escapeIdentifier(f->table()->name(), options.identifierEscaping)) + .arg(escapeIdentifier(f->name(), options.identifierEscaping)) + .arg(internalUniqueQueryAlias) + .arg(escapeIdentifier(boundColumnInfo->aliasOrName(), options.identifierEscaping)); + + if (!s_additional_fields.isEmpty()) + s_additional_fields += QString::fromLatin1(", "); + const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() ); + QString expression; + foreach (QValueList<uint>::ConstIterator, visibleColumnsIt, visibleColumns) { +//! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?" +//! @todo Add possibility for joining the values at client side. + if (fieldsExpanded.count() <= (*visibleColumnsIt)) { + KexiDBWarn << "Connection::selectStatement(): fieldsExpanded.count() <= (*visibleColumnsIt) : " + << fieldsExpanded.count() << " <= " << *visibleColumnsIt << endl; + return QString::null; + } + if (!expression.isEmpty()) + expression += " || ' ' || "; + expression += (internalUniqueQueryAlias + "." + + escapeIdentifier(fieldsExpanded[*visibleColumnsIt]->aliasOrName(), + options.identifierEscaping)); + } + s_additional_fields += expression; +//subqueries_for_lookup_data.append(lookupQuery); + } + else { + KexiDBWarn << "Connection::selectStatement(): unsupported row source type " + << rowSource.typeName() << endl; + return QString(); + } + } + } + } + + //add lookup fields + if (!s_additional_fields.isEmpty()) + sql += (QString::fromLatin1(", ") + s_additional_fields); + + if (options.alsoRetrieveROWID) { //append rowid column + QString s; + if (!sql.isEmpty()) + s = QString::fromLatin1(", "); + if (querySchema.masterTable()) + s += (escapeIdentifier(querySchema.masterTable()->name())+"."); + s += m_driver->beh->ROW_ID_FIELD_NAME; + sql += s; + } + + sql.prepend("SELECT "); + TableSchema::List* tables = querySchema.tables(); + if ((tables && !tables->isEmpty()) || !subqueries_for_lookup_data.isEmpty()) { + sql += QString::fromLatin1(" FROM "); + QString s_from; + if (tables) { + TableSchema *table; + number = 0; + for (TableSchema::ListIterator it(*tables); (table = it.current()); + ++it, number++) + { + if (!s_from.isEmpty()) + s_from += QString::fromLatin1(", "); + s_from += escapeIdentifier(table->name(), options.identifierEscaping); + QString aliasString = QString(querySchema.tableAlias(number)); + if (!aliasString.isEmpty()) + s_from += (QString::fromLatin1(" AS ") + aliasString); + } + /*unused if (!s_from_additional.isEmpty()) {//additional tables list needed for lookup fields + if (!s_from.isEmpty()) + s_from += QString::fromLatin1(", "); + s_from += s_from_additional; + }*/ + } + // add subqueries for lookup data + uint subqueries_for_lookup_data_counter = 0; + for (QPtrListIterator<QuerySchema> it(subqueries_for_lookup_data); + subqueries_for_lookup_data.current(); ++it, subqueries_for_lookup_data_counter++) + { + if (!s_from.isEmpty()) + s_from += QString::fromLatin1(", "); + s_from += QString::fromLatin1("("); + s_from += selectStatement( *it.current(), params, options ); + s_from += QString::fromLatin1(") AS %1%2") + .arg(kexidb_subquery_prefix).arg(subqueries_for_lookup_data_counter); + } + sql += s_from; + } + QString s_where; + s_where.reserve(4096); + + //JOINS + if (!s_additional_joins.isEmpty()) { + sql += QString::fromLatin1(" ") + s_additional_joins + QString::fromLatin1(" "); + } + +//@todo: we're using WHERE for joins now; use INNER/LEFT/RIGHT JOIN later + + //WHERE + Relationship *rel; + bool wasWhere = false; //for later use + for (Relationship::ListIterator it(*querySchema.relationships()); (rel = it.current()); ++it) { + if (s_where.isEmpty()) { + wasWhere = true; + } + else + s_where += QString::fromLatin1(" AND "); + Field::Pair *pair; + QString s_where_sub; + for (QPtrListIterator<Field::Pair> p_it(*rel->fieldPairs()); (pair = p_it.current()); ++p_it) { + if (!s_where_sub.isEmpty()) + s_where_sub += QString::fromLatin1(" AND "); + s_where_sub += ( + escapeIdentifier(pair->first->table()->name(), options.identifierEscaping) + + QString::fromLatin1(".") + + escapeIdentifier(pair->first->name(), options.identifierEscaping) + + QString::fromLatin1(" = ") + + escapeIdentifier(pair->second->table()->name(), options.identifierEscaping) + + QString::fromLatin1(".") + + escapeIdentifier(pair->second->name(), options.identifierEscaping)); + } + if (rel->fieldPairs()->count()>1) { + s_where_sub.prepend("("); + s_where_sub += QString::fromLatin1(")"); + } + s_where += s_where_sub; + } + //EXPLICITLY SPECIFIED WHERE EXPRESSION + if (querySchema.whereExpression()) { + QuerySchemaParameterValueListIterator paramValuesIt(*m_driver, params); + QuerySchemaParameterValueListIterator *paramValuesItPtr = params.isEmpty() ? 0 : ¶mValuesIt; + if (wasWhere) { +//TODO: () are not always needed + s_where = "(" + s_where + ") AND (" + querySchema.whereExpression()->toString(paramValuesItPtr) + ")"; + } + else { + s_where = querySchema.whereExpression()->toString(paramValuesItPtr); + } + } + if (!s_where.isEmpty()) + sql += QString::fromLatin1(" WHERE ") + s_where; +//! \todo (js) add other sql parts + //(use wasWhere here) + + // ORDER BY + QString orderByString( + querySchema.orderByColumnList().toSQLString(!singleTable/*includeTableName*/, + driver(), options.identifierEscaping) ); + const QValueVector<int> pkeyFieldsOrder( querySchema.pkeyFieldsOrder() ); + if (orderByString.isEmpty() && !pkeyFieldsOrder.isEmpty()) { + //add automatic ORDER BY if there is no explicity defined (especially helps when there are complex JOINs) + OrderByColumnList automaticPKOrderBy; + const QueryColumnInfo::Vector fieldsExpanded( querySchema.fieldsExpanded() ); + foreach (QValueVector<int>::ConstIterator, it, pkeyFieldsOrder) { + if ((*it) < 0) // no field mentioned in this query + continue; + if ((*it) >= (int)fieldsExpanded.count()) { + KexiDBWarn << "Connection::selectStatement(): ORDER BY: (*it) >= fieldsExpanded.count() - " + << (*it) << " >= " << fieldsExpanded.count() << endl; + continue; + } + QueryColumnInfo *ci = fieldsExpanded[ *it ]; + automaticPKOrderBy.appendColumn( *ci ); + } + orderByString = automaticPKOrderBy.toSQLString(!singleTable/*includeTableName*/, + driver(), options.identifierEscaping); + } + if (!orderByString.isEmpty()) + sql += (" ORDER BY " + orderByString); + + //KexiDBDbg << sql << endl; + return sql; +} + +QString Connection::selectStatement( KexiDB::TableSchema& tableSchema, + const SelectStatementOptions& options) const +{ + return selectStatement( *tableSchema.query(), options ); +} + +Field* Connection::findSystemFieldName(KexiDB::FieldList* fieldlist) +{ + Field *f = fieldlist->fields()->first(); + while (f) { + if (m_driver->isSystemFieldName( f->name() )) + return f; + f = fieldlist->fields()->next(); + } + return 0; +} + +Q_ULLONG Connection::lastInsertedAutoIncValue(const QString& aiFieldName, const QString& tableName, + Q_ULLONG* ROWID) +{ + Q_ULLONG row_id = drv_lastInsertRowID(); + if (ROWID) + *ROWID = row_id; + if (m_driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE) { + return row_id; + } + RowData rdata; + if (row_id<=0 || true!=querySingleRecord( + QString::fromLatin1("SELECT ") + tableName + QString::fromLatin1(".") + aiFieldName + QString::fromLatin1(" FROM ") + tableName + + QString::fromLatin1(" WHERE ") + m_driver->beh->ROW_ID_FIELD_NAME + QString::fromLatin1("=") + QString::number(row_id), rdata)) + { +// KexiDBDbg << "Connection::lastInsertedAutoIncValue(): row_id<=0 || true!=querySingleRecord()" << endl; + return (Q_ULLONG)-1; //ULL; + } + return rdata[0].toULongLong(); +} + +Q_ULLONG Connection::lastInsertedAutoIncValue(const QString& aiFieldName, + const KexiDB::TableSchema& table, Q_ULLONG* ROWID) +{ + return lastInsertedAutoIncValue(aiFieldName,table.name(), ROWID); +} + +//! Creates a Field list for kexi__fields, for sanity. Used by createTable() +static FieldList* createFieldListForKexi__Fields(TableSchema *kexi__fieldsSchema) +{ + if (!kexi__fieldsSchema) + return 0; + return kexi__fieldsSchema->subList( + "t_id", + "f_type", + "f_name", + "f_length", + "f_precision", + "f_constraints", + "f_options", + "f_default", + "f_order", + "f_caption", + "f_help" + ); +} + +//! builds a list of values for field's \a f properties. Used by createTable(). +void buildValuesForKexi__Fields(QValueList<QVariant>& vals, Field* f) +{ + vals.clear(); + vals + << QVariant(f->table()->id()) + << QVariant(f->type()) + << QVariant(f->name()) + << QVariant(f->isFPNumericType() ? f->scale() : f->length()) + << QVariant(f->isFPNumericType() ? f->precision() : 0) + << QVariant(f->constraints()) + << QVariant(f->options()) + // KexiDB::variantToString() is needed here because the value can be of any QVariant type, + // depending on f->type() + << (f->defaultValue().isNull() + ? QVariant() : QVariant(KexiDB::variantToString( f->defaultValue() ))) + << QVariant(f->order()) + << QVariant(f->caption()) + << QVariant(f->description()); +} + +bool Connection::storeMainFieldSchema(Field *field) +{ + if (!field || !field->table()) + return false; + FieldList *fl = createFieldListForKexi__Fields(d->tables_byname["kexi__fields"]); + if (!fl) + return false; + + QValueList<QVariant> vals; + buildValuesForKexi__Fields(vals, field); + QValueList<QVariant>::ConstIterator valsIt = vals.constBegin(); + Field *f; + bool first = true; + QString sql = "UPDATE kexi__fields SET "; + for (Field::ListIterator it( fl->fieldsIterator() ); (f = it.current()); ++it, ++valsIt) { + sql.append( (first ? QString::null : QString(", ")) + + f->name() + "=" + m_driver->valueToSQL( f, *valsIt ) ); + if (first) + first = false; + } + delete fl; + + sql.append(QString(" WHERE t_id=") + QString::number( field->table()->id() ) + + " AND f_name=" + m_driver->valueToSQL( Field::Text, field->name() ) ); + return executeSQL( sql ); +} + +#define createTable_ERR \ + { KexiDBDbg << "Connection::createTable(): ERROR!" <<endl; \ + setError(this, i18n("Creating table failed.")); \ + rollbackAutoCommitTransaction(tg.transaction()); \ + return false; } + //setError( errorNum(), i18n("Creating table failed.") + " " + errorMsg()); + +//! Creates a table according to the given schema +/*! Creates a table according to the given TableSchema, adding the table and + column definitions to kexi__* tables. Checks that a database is in use, + that the table name is not that of a system table, and that the schema + defines at least one column. + If the table exists, and replaceExisting is true, the table is replaced. + Otherwise, the table is not replaced. +*/ +bool Connection::createTable( KexiDB::TableSchema* tableSchema, bool replaceExisting ) +{ + if (!tableSchema || !checkIsDatabaseUsed()) + return false; + + //check if there are any fields + if (tableSchema->fieldCount()<1) { + clearError(); + setError(ERR_CANNOT_CREATE_EMPTY_OBJECT, i18n("Cannot create table without fields.")); + return false; + } + const bool internalTable = dynamic_cast<InternalTableSchema*>(tableSchema); + + const QString &tableName = tableSchema->name().lower(); + + if (!internalTable) { + if (m_driver->isSystemObjectName( tableName )) { + clearError(); + setError(ERR_SYSTEM_NAME_RESERVED, i18n("System name \"%1\" cannot be used as table name.") + .arg(tableSchema->name())); + return false; + } + + Field *sys_field = findSystemFieldName(tableSchema); + if (sys_field) { + clearError(); + setError(ERR_SYSTEM_NAME_RESERVED, + i18n("System name \"%1\" cannot be used as one of fields in \"%2\" table.") + .arg(sys_field->name()).arg(tableName)); + return false; + } + } + + bool previousSchemaStillKept = false; + + KexiDB::TableSchema *existingTable = 0; + if (replaceExisting) { + //get previous table (do not retrieve, though) + existingTable = d->tables_byname[tableName]; + if (existingTable) { + if (existingTable == tableSchema) { + clearError(); + setError(ERR_OBJECT_EXISTS, + i18n("Could not create the same table \"%1\" twice.").arg(tableSchema->name()) ); + return false; + } +//TODO(js): update any structure (e.g. queries) that depend on this table! + if (existingTable->id()>0) + tableSchema->m_id = existingTable->id(); //copy id from existing table + previousSchemaStillKept = true; + if (!dropTable( existingTable, false /*alsoRemoveSchema*/ )) + return false; + } + } + else { + if (this->tableSchema( tableSchema->name() ) != 0) { + clearError(); + setError(ERR_OBJECT_EXISTS, i18n("Table \"%1\" already exists.").arg(tableSchema->name()) ); + return false; + } + } + +/* if (replaceExisting) { + //get previous table (do not retrieve, though) + KexiDB::TableSchema *existingTable = d->tables_byname.take(name); + if (oldTable) { + }*/ + + TransactionGuard tg; + if (!beginAutoCommitTransaction(tg)) + return false; + + if (!drv_createTable(*tableSchema)) + createTable_ERR; + + //add schema data to kexi__* tables + if (!internalTable) { + //update kexi__objects + if (!storeObjectSchemaData( *tableSchema, true )) + createTable_ERR; + + TableSchema *ts = d->tables_byname["kexi__fields"]; + if (!ts) + return false; + //for sanity: remove field info (if any) for this table id + if (!KexiDB::deleteRow(*this, ts, "t_id", tableSchema->id())) + return false; + + FieldList *fl = createFieldListForKexi__Fields(d->tables_byname["kexi__fields"]); + if (!fl) + return false; + +// int order = 0; + Field *f; + for (Field::ListIterator it( *tableSchema->fields() ); (f = it.current()); ++it/*, order++*/) { + QValueList<QVariant> vals; + buildValuesForKexi__Fields(vals, f); + if (!insertRecord(*fl, vals )) + createTable_ERR; + } + delete fl; + + if (!storeExtendedTableSchemaData(*tableSchema)) + createTable_ERR; + } + + //finally: +/* if (replaceExisting) { + if (existingTable) { + d->tables.take(existingTable->id()); + delete existingTable; + } + }*/ + + bool res = commitAutoCommitTransaction(tg.transaction()); + + if (res) { + if (internalTable) { + //insert the internal table into structures + insertInternalTableSchema(tableSchema); + } + else { + if (previousSchemaStillKept) { + //remove previous table schema + removeTableSchemaInternal(tableSchema); + } + //store one schema object locally: + d->tables.insert(tableSchema->id(), tableSchema); + d->tables_byname.insert(tableSchema->name().lower(), tableSchema); + } + //ok, this table is not created by the connection + tableSchema->m_conn = this; + } + return res; +} + +void Connection::removeTableSchemaInternal(TableSchema *tableSchema) +{ + d->tables_byname.remove(tableSchema->name()); + d->tables.remove(tableSchema->id()); +} + +bool Connection::removeObject( uint objId ) +{ + clearError(); + //remove table schema from kexi__* tables + if (!KexiDB::deleteRow(*this, d->tables_byname["kexi__objects"], "o_id", objId) //schema entry + || !KexiDB::deleteRow(*this, d->tables_byname["kexi__objectdata"], "o_id", objId)) {//data blocks + setError(ERR_DELETE_SERVER_ERROR, i18n("Could not remove object's data.")); + return false; + } + return true; +} + +bool Connection::drv_dropTable( const QString& name ) +{ + m_sql = "DROP TABLE " + escapeIdentifier(name); + return executeSQL(m_sql); +} + +//! Drops a table corresponding to the name in the given schema +/*! Drops a table according to the name given by the TableSchema, removing the + table and column definitions to kexi__* tables. Checks first that the + table is not a system table. + + TODO: Should check that a database is currently in use? (c.f. createTable) +*/ +tristate Connection::dropTable( KexiDB::TableSchema* tableSchema ) +{ + return dropTable( tableSchema, true ); +} + +tristate Connection::dropTable( KexiDB::TableSchema* tableSchema, bool alsoRemoveSchema) +{ +// Each SQL identifier needs to be escaped in the generated query. + clearError(); + if (!tableSchema) + return false; + + QString errmsg(i18n("Table \"%1\" cannot be removed.\n")); + //be sure that we handle the correct TableSchema object: + if (tableSchema->id() < 0 + || this->tableSchema(tableSchema->name())!=tableSchema + || this->tableSchema(tableSchema->id())!=tableSchema) + { + setError(ERR_OBJECT_NOT_FOUND, errmsg.arg(tableSchema->name()) + +i18n("Unexpected name or identifier.")); + return false; + } + + tristate res = closeAllTableSchemaChangeListeners(*tableSchema); + if (true!=res) + return res; + + //sanity checks: + if (m_driver->isSystemObjectName( tableSchema->name() )) { + setError(ERR_SYSTEM_NAME_RESERVED, errmsg.arg(tableSchema->name()) + d->strItIsASystemObject()); + return false; + } + + TransactionGuard tg; + if (!beginAutoCommitTransaction(tg)) + return false; + + //for sanity we're checking if this table exists physically + if (drv_containsTable(tableSchema->name())) { + if (!drv_dropTable(tableSchema->name())) + return false; + } + + TableSchema *ts = d->tables_byname["kexi__fields"]; + if (!KexiDB::deleteRow(*this, ts, "t_id", tableSchema->id())) //field entries + return false; + + //remove table schema from kexi__objects table + if (!removeObject( tableSchema->id() )) { + return false; + } + + if (alsoRemoveSchema) { +//! \todo js: update any structure (e.g. queries) that depend on this table! + tristate res = removeDataBlock( tableSchema->id(), "extended_schema"); + if (!res) + return false; + removeTableSchemaInternal(tableSchema); + } + return commitAutoCommitTransaction(tg.transaction()); +} + +tristate Connection::dropTable( const QString& table ) +{ + clearError(); + TableSchema* ts = tableSchema( table ); + if (!ts) { + setError(ERR_OBJECT_NOT_FOUND, i18n("Table \"%1\" does not exist.") + .arg(table)); + return false; + } + return dropTable(ts); +} + +tristate Connection::alterTable( TableSchema& tableSchema, TableSchema& newTableSchema ) +{ + clearError(); + tristate res = closeAllTableSchemaChangeListeners(tableSchema); + if (true!=res) + return res; + + if (&tableSchema == &newTableSchema) { + setError(ERR_OBJECT_THE_SAME, i18n("Could not alter table \"%1\" using the same table.") + .arg(tableSchema.name())); + return false; + } +//TODO(js): implement real altering +//TODO(js): update any structure (e.g. query) that depend on this table! + bool ok, empty; +#if 0//TODO ucomment: + empty = isEmpty( tableSchema, ok ) && ok; +#else + empty = true; +#endif + if (empty) { + ok = createTable(&newTableSchema, true/*replace*/); + } + return ok; +} + +bool Connection::alterTableName(TableSchema& tableSchema, const QString& newName, bool replace) +{ + clearError(); + if (&tableSchema!=d->tables[tableSchema.id()]) { + setError(ERR_OBJECT_NOT_FOUND, i18n("Unknown table \"%1\"").arg(tableSchema.name())); + return false; + } + if (newName.isEmpty() || !KexiUtils::isIdentifier(newName)) { + setError(ERR_INVALID_IDENTIFIER, i18n("Invalid table name \"%1\"").arg(newName)); + return false; + } + const QString oldTableName = tableSchema.name(); + const QString newTableName = newName.lower().stripWhiteSpace(); + if (oldTableName.lower().stripWhiteSpace() == newTableName) { + setError(ERR_OBJECT_THE_SAME, i18n("Could rename table \"%1\" using the same name.") + .arg(newTableName)); + return false; + } +//TODO: alter table name for server DB backends! +//TODO: what about objects (queries/forms) that use old name? +//TODO + TableSchema *tableToReplace = this->tableSchema( newName ); + const bool destTableExists = tableToReplace != 0; + const int origID = destTableExists ? tableToReplace->id() : -1; //will be reused in the new table + if (!replace && destTableExists) { + setError(ERR_OBJECT_EXISTS, + i18n("Could not rename table \"%1\" to \"%2\". Table \"%3\" already exists.") + .arg(tableSchema.name()).arg(newName).arg(newName)); + return false; + } + +//helper: +#define alterTableName_ERR \ + tableSchema.setName(oldTableName) //restore old name + + TransactionGuard tg; + if (!beginAutoCommitTransaction(tg)) + return false; + + // drop the table replaced (with schema) + if (destTableExists) { + if (!replace) { + return false; + } + if (!dropTable( newName )) { + return false; + } + + // the new table owns the previous table's id: + if (!executeSQL(QString::fromLatin1("UPDATE kexi__objects SET o_id=%1 WHERE o_id=%2 AND o_type=%3") + .arg(origID).arg(tableSchema.id()).arg((int)TableObjectType))) + { + return false; + } + if (!executeSQL(QString::fromLatin1("UPDATE kexi__fields SET t_id=%1 WHERE t_id=%2") + .arg(origID).arg(tableSchema.id()))) + { + return false; + } + d->tables.take(tableSchema.id()); + d->tables.insert(origID, &tableSchema); + //maintain table ID + tableSchema.m_id = origID; + } + + if (!drv_alterTableName(tableSchema, newTableName)) { + alterTableName_ERR; + return false; + } + + // Update kexi__objects + //TODO + if (!executeSQL(QString::fromLatin1("UPDATE kexi__objects SET o_name=%1 WHERE o_id=%2") + .arg(m_driver->escapeString(tableSchema.name())).arg(tableSchema.id()))) + { + alterTableName_ERR; + return false; + } +//TODO what about caption? + + //restore old name: it will be changed soon! + tableSchema.setName(oldTableName); + + if (!commitAutoCommitTransaction(tg.transaction())) { + alterTableName_ERR; + return false; + } + + //update tableSchema: + d->tables_byname.take(tableSchema.name()); + tableSchema.setName(newTableName); + d->tables_byname.insert(tableSchema.name(), &tableSchema); + return true; +} + +bool Connection::drv_alterTableName(TableSchema& tableSchema, const QString& newName) +{ + const QString oldTableName = tableSchema.name(); + tableSchema.setName(newName); + + if (!executeSQL(QString::fromLatin1("ALTER TABLE %1 RENAME TO %2") + .arg(escapeIdentifier(oldTableName)).arg(escapeIdentifier(newName)))) + { + tableSchema.setName(oldTableName); //restore old name + return false; + } + return true; +} + +bool Connection::dropQuery( KexiDB::QuerySchema* querySchema ) +{ + clearError(); + if (!querySchema) + return false; + + TransactionGuard tg; + if (!beginAutoCommitTransaction(tg)) + return false; + +/* TableSchema *ts = d->tables_byname["kexi__querydata"]; + if (!KexiDB::deleteRow(*this, ts, "q_id", querySchema->id())) + return false; + + ts = d->tables_byname["kexi__queryfields"]; + if (!KexiDB::deleteRow(*this, ts, "q_id", querySchema->id())) + return false; + + ts = d->tables_byname["kexi__querytables"]; + if (!KexiDB::deleteRow(*this, ts, "q_id", querySchema->id())) + return false;*/ + + //remove query schema from kexi__objects table + if (!removeObject( querySchema->id() )) { + return false; + } + +//TODO(js): update any structure that depend on this table! + d->queries_byname.remove(querySchema->name()); + d->queries.remove(querySchema->id()); + + return commitAutoCommitTransaction(tg.transaction()); +} + +bool Connection::dropQuery( const QString& query ) +{ + clearError(); + QuerySchema* qs = querySchema( query ); + if (!qs) { + setError(ERR_OBJECT_NOT_FOUND, i18n("Query \"%1\" does not exist.") + .arg(query)); + return false; + } + return dropQuery(qs); +} + +bool Connection::drv_createTable( const KexiDB::TableSchema& tableSchema ) +{ + m_sql = createTableStatement(tableSchema); + KexiDBDbg<<"******** "<<m_sql<<endl; + return executeSQL(m_sql); +} + +bool Connection::drv_createTable( const QString& tableSchemaName ) +{ + TableSchema *ts = d->tables_byname[tableSchemaName]; + if (!ts) + return false; + return drv_createTable(*ts); +} + +bool Connection::beginAutoCommitTransaction(TransactionGuard &tg) +{ + if ((m_driver->d->features & Driver::IgnoreTransactions) + || !d->autoCommit) + { + tg.setTransaction( Transaction() ); + return true; + } + + // commit current transaction (if present) for drivers + // that allow single transaction per connection + if (m_driver->d->features & Driver::SingleTransactions) { + if (d->default_trans_started_inside) //only commit internally started transaction + if (!commitTransaction(d->default_trans, true)) { + tg.setTransaction( Transaction() ); + return false; //we have a real error + } + + d->default_trans_started_inside = d->default_trans.isNull(); + if (!d->default_trans_started_inside) { + tg.setTransaction( d->default_trans ); + tg.doNothing(); + return true; //reuse externally started transaction + } + } + else if (!(m_driver->d->features & Driver::MultipleTransactions)) { + tg.setTransaction( Transaction() ); + return true; //no trans. supported at all - just return + } + tg.setTransaction( beginTransaction() ); + return !error(); +} + +bool Connection::commitAutoCommitTransaction(const Transaction& trans) +{ + if (m_driver->d->features & Driver::IgnoreTransactions) + return true; + if (trans.isNull() || !m_driver->transactionsSupported()) + return true; + if (m_driver->d->features & Driver::SingleTransactions) { + if (!d->default_trans_started_inside) //only commit internally started transaction + return true; //give up + } + return commitTransaction(trans, true); +} + +bool Connection::rollbackAutoCommitTransaction(const Transaction& trans) +{ + if (trans.isNull() || !m_driver->transactionsSupported()) + return true; + return rollbackTransaction(trans); +} + +#define SET_ERR_TRANS_NOT_SUPP \ + { setError(ERR_UNSUPPORTED_DRV_FEATURE, \ + i18n("Transactions are not supported for \"%1\" driver.").arg(m_driver->name() )); } + +#define SET_BEGIN_TR_ERROR \ + { if (!error()) \ + setError(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, i18n("Begin transaction failed")); } + +Transaction Connection::beginTransaction() +{ + if (!checkIsDatabaseUsed()) + return Transaction::null; + Transaction trans; + if (m_driver->d->features & Driver::IgnoreTransactions) { + //we're creating dummy transaction data here, + //so it will look like active + trans.m_data = new TransactionData(this); + d->transactions.append(trans); + return trans; + } + if (m_driver->d->features & Driver::SingleTransactions) { + if (d->default_trans.active()) { + setError(ERR_TRANSACTION_ACTIVE, i18n("Transaction already started.") ); + return Transaction::null; + } + if (!(trans.m_data = drv_beginTransaction())) { + SET_BEGIN_TR_ERROR; + return Transaction::null; + } + d->default_trans = trans; + d->transactions.append(trans); + return d->default_trans; + } + if (m_driver->d->features & Driver::MultipleTransactions) { + if (!(trans.m_data = drv_beginTransaction())) { + SET_BEGIN_TR_ERROR; + return Transaction::null; + } + d->transactions.append(trans); + return trans; + } + + SET_ERR_TRANS_NOT_SUPP; + return Transaction::null; +} + +bool Connection::commitTransaction(const Transaction trans, bool ignore_inactive) +{ + if (!isDatabaseUsed()) + return false; +// if (!checkIsDatabaseUsed()) + //return false; + if ( !m_driver->transactionsSupported() + && !(m_driver->d->features & Driver::IgnoreTransactions)) + { + SET_ERR_TRANS_NOT_SUPP; + return false; + } + Transaction t = trans; + if (!t.active()) { //try default tr. + if (!d->default_trans.active()) { + if (ignore_inactive) + return true; + clearError(); + setError(ERR_NO_TRANSACTION_ACTIVE, i18n("Transaction not started.") ); + return false; + } + t = d->default_trans; + d->default_trans = Transaction::null; //now: no default tr. + } + bool ret = true; + if (! (m_driver->d->features & Driver::IgnoreTransactions) ) + ret = drv_commitTransaction(t.m_data); + if (t.m_data) + t.m_data->m_active = false; //now this transaction if inactive + if (!d->dont_remove_transactions) //true=transaction obj will be later removed from list + d->transactions.remove(t); + if (!ret && !error()) + setError(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, i18n("Error on commit transaction")); + return ret; +} + +bool Connection::rollbackTransaction(const Transaction trans, bool ignore_inactive) +{ + if (!isDatabaseUsed()) + return false; +// if (!checkIsDatabaseUsed()) +// return false; + if ( !m_driver->transactionsSupported() + && !(m_driver->d->features & Driver::IgnoreTransactions)) + { + SET_ERR_TRANS_NOT_SUPP; + return false; + } + Transaction t = trans; + if (!t.active()) { //try default tr. + if (!d->default_trans.active()) { + if (ignore_inactive) + return true; + clearError(); + setError(ERR_NO_TRANSACTION_ACTIVE, i18n("Transaction not started.") ); + return false; + } + t = d->default_trans; + d->default_trans = Transaction::null; //now: no default tr. + } + bool ret = true; + if (! (m_driver->d->features & Driver::IgnoreTransactions) ) + ret = drv_rollbackTransaction(t.m_data); + if (t.m_data) + t.m_data->m_active = false; //now this transaction if inactive + if (!d->dont_remove_transactions) //true=transaction obj will be later removed from list + d->transactions.remove(t); + if (!ret && !error()) + setError(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, i18n("Error on rollback transaction")); + return ret; +} + +#undef SET_ERR_TRANS_NOT_SUPP +#undef SET_BEGIN_TR_ERROR + +/*bool Connection::duringTransaction() +{ + return drv_duringTransaction(); +}*/ + +Transaction& Connection::defaultTransaction() const +{ + return d->default_trans; +} + +void Connection::setDefaultTransaction(const Transaction& trans) +{ + if (!isDatabaseUsed()) + return; +// if (!checkIsDatabaseUsed()) + // return; + if ( !(m_driver->d->features & Driver::IgnoreTransactions) + && (!trans.active() || !m_driver->transactionsSupported()) ) + { + return; + } + d->default_trans = trans; +} + +const QValueList<Transaction>& Connection::transactions() +{ + return d->transactions; +} + +bool Connection::autoCommit() const +{ + return d->autoCommit; +} + +bool Connection::setAutoCommit(bool on) +{ + if (d->autoCommit == on || m_driver->d->features & Driver::IgnoreTransactions) + return true; + if (!drv_setAutoCommit(on)) + return false; + d->autoCommit = on; + return true; +} + +TransactionData* Connection::drv_beginTransaction() +{ + QString old_sql = m_sql; //don't + if (!executeSQL( "BEGIN" )) + return 0; + return new TransactionData(this); +} + +bool Connection::drv_commitTransaction(TransactionData *) +{ + return executeSQL( "COMMIT" ); +} + +bool Connection::drv_rollbackTransaction(TransactionData *) +{ + return executeSQL( "ROLLBACK" ); +} + +bool Connection::drv_setAutoCommit(bool /*on*/) +{ + return true; +} + +Cursor* Connection::executeQuery( const QString& statement, uint cursor_options ) +{ + if (statement.isEmpty()) + return 0; + Cursor *c = prepareQuery( statement, cursor_options ); + if (!c) + return 0; + if (!c->open()) {//err - kill that + setError(c); + delete c; + return 0; + } + return c; +} + +Cursor* Connection::executeQuery( QuerySchema& query, const QValueList<QVariant>& params, + uint cursor_options ) +{ + Cursor *c = prepareQuery( query, params, cursor_options ); + if (!c) + return 0; + if (!c->open()) {//err - kill that + setError(c); + delete c; + return 0; + } + return c; +} + +Cursor* Connection::executeQuery( QuerySchema& query, uint cursor_options ) +{ + return executeQuery(query, QValueList<QVariant>(), cursor_options); +} + +Cursor* Connection::executeQuery( TableSchema& table, uint cursor_options ) +{ + return executeQuery( *table.query(), cursor_options ); +} + +Cursor* Connection::prepareQuery( TableSchema& table, uint cursor_options ) +{ + return prepareQuery( *table.query(), cursor_options ); +} + +Cursor* Connection::prepareQuery( QuerySchema& query, const QValueList<QVariant>& params, + uint cursor_options ) +{ + Cursor* cursor = prepareQuery(query, cursor_options); + if (cursor) + cursor->setQueryParameters(params); + return cursor; +} + +bool Connection::deleteCursor(Cursor *cursor) +{ + if (!cursor) + return false; + if (cursor->connection()!=this) {//illegal call + KexiDBWarn << "Connection::deleteCursor(): Cannot delete the cursor not owned by the same connection!" << endl; + return false; + } + const bool ret = cursor->close(); + delete cursor; + return ret; +} + +bool Connection::setupObjectSchemaData( const RowData &data, SchemaData &sdata ) +{ + //not found: retrieve schema +/* KexiDB::Cursor *cursor; + if (!(cursor = executeQuery( QString("select * from kexi__objects where o_id='%1'").arg(objId) ))) + return false; + if (!cursor->moveFirst()) { + deleteCursor(cursor); + return false; + }*/ + //if (!ok) { + //deleteCursor(cursor); + //return 0; +// } + bool ok; + sdata.m_id = data[0].toInt(&ok); + if (!ok) { + return false; + } + sdata.m_name = data[2].toString(); + if (!KexiUtils::isIdentifier( sdata.m_name )) { + setError(ERR_INVALID_IDENTIFIER, i18n("Invalid object name \"%1\"").arg(sdata.m_name)); + return false; + } + sdata.m_caption = data[3].toString(); + sdata.m_desc = data[4].toString(); + +// KexiDBDbg<<"@@@ Connection::setupObjectSchemaData() == " << sdata.schemaDataDebugString() << endl; + return true; +} + +tristate Connection::loadObjectSchemaData( int objectID, SchemaData &sdata ) +{ + RowData data; + if (true!=querySingleRecord(QString::fromLatin1( + "SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1") + .arg(objectID), data)) + return cancelled; + return setupObjectSchemaData( data, sdata ); +} + +tristate Connection::loadObjectSchemaData( int objectType, const QString& objectName, SchemaData &sdata ) +{ + RowData data; + if (true!=querySingleRecord(QString::fromLatin1("SELECT o_id, o_type, o_name, o_caption, o_desc " + "FROM kexi__objects WHERE o_type=%1 AND lower(o_name)=%2") + .arg(objectType).arg(m_driver->valueToSQL(Field::Text, objectName.lower())), data)) + return cancelled; + return setupObjectSchemaData( data, sdata ); +} + +bool Connection::storeObjectSchemaData( SchemaData &sdata, bool newObject ) +{ + TableSchema *ts = d->tables_byname["kexi__objects"]; + if (!ts) + return false; + if (newObject) { + int existingID; + if (true == querySingleNumber(QString::fromLatin1( + "SELECT o_id FROM kexi__objects WHERE o_type=%1 AND lower(o_name)=%2") + .arg(sdata.type()).arg(m_driver->valueToSQL(Field::Text, sdata.name().lower())), existingID)) + { + //we already have stored a schema data with the same name and type: + //just update it's properties as it would be existing object + sdata.m_id = existingID; + newObject = false; + } + } + if (newObject) { + FieldList *fl; + bool ok; + if (sdata.id()<=0) {//get new ID + fl = ts->subList("o_type", "o_name", "o_caption", "o_desc"); + ok = fl!=0; + if (ok && !insertRecord(*fl, QVariant(sdata.type()), QVariant(sdata.name()), + QVariant(sdata.caption()), QVariant(sdata.description()) )) + ok = false; + delete fl; + if (!ok) + return false; + //fetch newly assigned ID +//! @todo safe to cast it? + int obj_id = (int)lastInsertedAutoIncValue("o_id",*ts); + KexiDBDbg << "######## NEW obj_id == " << obj_id << endl; + if (obj_id<=0) + return false; + sdata.m_id = obj_id; + return true; + } else { + fl = ts->subList("o_id", "o_type", "o_name", "o_caption", "o_desc"); + ok = fl!=0; + if (ok && !insertRecord(*fl, QVariant(sdata.id()), QVariant(sdata.type()), QVariant(sdata.name()), + QVariant(sdata.caption()), QVariant(sdata.description()) )) + ok = false; + delete fl; + return ok; + } + } + //existing object: + return executeSQL(QString("UPDATE kexi__objects SET o_type=%2, o_caption=%3, o_desc=%4 WHERE o_id=%1") + .arg(sdata.id()).arg(sdata.type()) + .arg(m_driver->valueToSQL(KexiDB::Field::Text, sdata.caption())) + .arg(m_driver->valueToSQL(KexiDB::Field::Text, sdata.description())) ); +} + +tristate Connection::querySingleRecordInternal(RowData &data, const QString* sql, QuerySchema* query, + bool addLimitTo1) +{ + Q_ASSERT(sql || query); +//! @todo does not work with non-SQL data sources + if (sql) + m_sql = addLimitTo1 ? (*sql + " LIMIT 1") : *sql; // is this safe? + KexiDB::Cursor *cursor; + if (!(cursor = sql ? executeQuery( m_sql ) : executeQuery( *query ))) { + KexiDBWarn << "Connection::querySingleRecord(): !executeQuery() " << m_sql << endl; + return false; + } + if (!cursor->moveFirst() || cursor->eof()) { + const tristate result = cursor->error() ? false : cancelled; + KexiDBWarn << "Connection::querySingleRecord(): !cursor->moveFirst() || cursor->eof() m_sql=" << m_sql << endl; + setError(cursor); + deleteCursor(cursor); + return result; + } + cursor->storeCurrentRow(data); + return deleteCursor(cursor); +} + +tristate Connection::querySingleRecord(const QString& sql, RowData &data, bool addLimitTo1) +{ + return querySingleRecordInternal(data, &sql, 0, addLimitTo1); +} + +tristate Connection::querySingleRecord(QuerySchema& query, RowData &data, bool addLimitTo1) +{ + return querySingleRecordInternal(data, 0, &query, addLimitTo1); +} + +bool Connection::checkIfColumnExists(Cursor *cursor, uint column) +{ + if (column >= cursor->fieldCount()) { + setError(ERR_CURSOR_RECORD_FETCHING, i18n("Column %1 does not exist for the query.").arg(column)); + return false; + } + return true; +} + +tristate Connection::querySingleString(const QString& sql, QString &value, uint column, bool addLimitTo1) +{ + KexiDB::Cursor *cursor; + m_sql = addLimitTo1 ? (sql + " LIMIT 1") : sql; // is this safe?; + if (!(cursor = executeQuery( m_sql ))) { + KexiDBWarn << "Connection::querySingleRecord(): !executeQuery() " << m_sql << endl; + return false; + } + if (!cursor->moveFirst() || cursor->eof()) { + const tristate result = cursor->error() ? false : cancelled; + KexiDBWarn << "Connection::querySingleRecord(): !cursor->moveFirst() || cursor->eof() " << m_sql << endl; + deleteCursor(cursor); + return result; + } + if (!checkIfColumnExists(cursor, column)) { + deleteCursor(cursor); + return false; + } + value = cursor->value(column).toString(); + return deleteCursor(cursor); +} + +tristate Connection::querySingleNumber(const QString& sql, int &number, uint column, bool addLimitTo1) +{ + static QString str; + static bool ok; + const tristate result = querySingleString(sql, str, column, addLimitTo1); + if (result!=true) + return result; + number = str.toInt(&ok); + return ok; +} + +bool Connection::queryStringList(const QString& sql, QStringList& list, uint column) +{ + KexiDB::Cursor *cursor; + clearError(); + m_sql = sql; + if (!(cursor = executeQuery( m_sql ))) { + KexiDBWarn << "Connection::queryStringList(): !executeQuery() " << m_sql << endl; + return false; + } + cursor->moveFirst(); + if (cursor->error()) { + setError(cursor); + deleteCursor(cursor); + return false; + } + if (!cursor->eof() && !checkIfColumnExists(cursor, column)) { + deleteCursor(cursor); + return false; + } + list.clear(); + while (!cursor->eof()) { + list.append( cursor->value(column).toString() ); + if (!cursor->moveNext() && cursor->error()) { + setError(cursor); + deleteCursor(cursor); + return false; + } + } + return deleteCursor(cursor); +} + +bool Connection::resultExists(const QString& sql, bool &success, bool addLimitTo1) +{ + KexiDB::Cursor *cursor; + //optimization + if (m_driver->beh->SELECT_1_SUBQUERY_SUPPORTED) { + //this is at least for sqlite + if (addLimitTo1 && sql.left(6).upper() == "SELECT") + m_sql = QString("SELECT 1 FROM (") + sql + ") LIMIT 1"; // is this safe?; + else + m_sql = sql; + } + else { + if (addLimitTo1 && sql.left(6).upper() == "SELECT") + m_sql = sql + " LIMIT 1"; //not always safe! + else + m_sql = sql; + } + if (!(cursor = executeQuery( m_sql ))) { + KexiDBWarn << "Connection::querySingleRecord(): !executeQuery() " << m_sql << endl; + success = false; + return false; + } + if (!cursor->moveFirst() || cursor->eof()) { + success = !cursor->error(); + KexiDBWarn << "Connection::querySingleRecord(): !cursor->moveFirst() || cursor->eof() " << m_sql << endl; + setError(cursor); + deleteCursor(cursor); + return false; + } + success = deleteCursor(cursor); + return true; +} + +bool Connection::isEmpty( TableSchema& table, bool &success ) +{ + return !resultExists( selectStatement( *table.query() ), success ); +} + +//! Used by addFieldPropertyToExtendedTableSchemaData() +static void createExtendedTableSchemaMainElementIfNeeded( + QDomDocument& doc, QDomElement& extendedTableSchemaMainEl, + bool& extendedTableSchemaStringIsEmpty) +{ + if (!extendedTableSchemaStringIsEmpty) + return; + //init document + extendedTableSchemaMainEl = doc.createElement("EXTENDED_TABLE_SCHEMA"); + doc.appendChild( extendedTableSchemaMainEl ); + extendedTableSchemaMainEl.setAttribute("version", QString::number(KEXIDB_EXTENDED_TABLE_SCHEMA_VERSION)); + extendedTableSchemaStringIsEmpty = false; +} + +//! Used by addFieldPropertyToExtendedTableSchemaData() +static void createExtendedTableSchemaFieldElementIfNeeded(QDomDocument& doc, + QDomElement& extendedTableSchemaMainEl, const QString& fieldName, QDomElement& extendedTableSchemaFieldEl, + bool append = true) +{ + if (!extendedTableSchemaFieldEl.isNull()) + return; + extendedTableSchemaFieldEl = doc.createElement("field"); + if (append) + extendedTableSchemaMainEl.appendChild( extendedTableSchemaFieldEl ); + extendedTableSchemaFieldEl.setAttribute("name", fieldName); +} + +/*! @internal used by storeExtendedTableSchemaData() + Creates DOM node for \a propertyName and \a propertyValue. + Creates enclosing EXTENDED_TABLE_SCHEMA element if EXTENDED_TABLE_SCHEMA is true. + Updates extendedTableSchemaStringIsEmpty and extendedTableSchemaMainEl afterwards. + If extendedTableSchemaFieldEl is null, creates <field> element (with optional + "custom" attribute is \a custom is false). */ +static void addFieldPropertyToExtendedTableSchemaData( + Field *f, const char* propertyName, const QVariant& propertyValue, + QDomDocument& doc, QDomElement& extendedTableSchemaMainEl, + QDomElement& extendedTableSchemaFieldEl, + bool& extendedTableSchemaStringIsEmpty, + bool custom = false ) +{ + createExtendedTableSchemaMainElementIfNeeded(doc, + extendedTableSchemaMainEl, extendedTableSchemaStringIsEmpty); + createExtendedTableSchemaFieldElementIfNeeded( + doc, extendedTableSchemaMainEl, f->name(), extendedTableSchemaFieldEl); + + //create <property> + QDomElement extendedTableSchemaFieldPropertyEl = doc.createElement("property"); + extendedTableSchemaFieldEl.appendChild( extendedTableSchemaFieldPropertyEl ); + if (custom) + extendedTableSchemaFieldPropertyEl.setAttribute("custom", "true"); + extendedTableSchemaFieldPropertyEl.setAttribute("name", propertyName); + QDomElement extendedTableSchemaFieldPropertyValueEl; + switch (propertyValue.type()) { + case QVariant::String: + extendedTableSchemaFieldPropertyValueEl = doc.createElement("string"); + break; + case QVariant::CString: + extendedTableSchemaFieldPropertyValueEl = doc.createElement("cstring"); + break; + case QVariant::Int: + case QVariant::Double: + case QVariant::UInt: + case QVariant::LongLong: + case QVariant::ULongLong: + extendedTableSchemaFieldPropertyValueEl = doc.createElement("number"); + break; + case QVariant::Bool: + extendedTableSchemaFieldPropertyValueEl = doc.createElement("bool"); + break; + default: +//! @todo add more QVariant types + KexiDBFatal << "addFieldPropertyToExtendedTableSchemaData(): impl. error" << endl; + } + extendedTableSchemaFieldPropertyEl.appendChild( extendedTableSchemaFieldPropertyValueEl ); + extendedTableSchemaFieldPropertyValueEl.appendChild( + doc.createTextNode( propertyValue.toString() ) ); +} + +bool Connection::storeExtendedTableSchemaData(TableSchema& tableSchema) +{ +//! @todo future: save in older versions if neeed + QDomDocument doc("EXTENDED_TABLE_SCHEMA"); + QDomElement extendedTableSchemaMainEl; + bool extendedTableSchemaStringIsEmpty = true; + + //for each field: + Field *f; + for (Field::ListIterator it( *tableSchema.fields() ); (f = it.current()); ++it) { + QDomElement extendedTableSchemaFieldEl; + if (f->visibleDecimalPlaces()>=0/*nondefault*/ && KexiDB::supportsVisibleDecimalPlacesProperty(f->type())) { + addFieldPropertyToExtendedTableSchemaData( + f, "visibleDecimalPlaces", f->visibleDecimalPlaces(), doc, + extendedTableSchemaMainEl, extendedTableSchemaFieldEl, + extendedTableSchemaStringIsEmpty ); + } + // boolean field with "not null" + + // add custom properties + const Field::CustomPropertiesMap customProperties(f->customProperties()); + foreach( Field::CustomPropertiesMap::ConstIterator, itCustom, customProperties ) { + addFieldPropertyToExtendedTableSchemaData( + f, itCustom.key(), itCustom.data(), doc, + extendedTableSchemaMainEl, extendedTableSchemaFieldEl, extendedTableSchemaStringIsEmpty, + /*custom*/true ); + } + // save lookup table specification, if present + LookupFieldSchema *lookupFieldSchema = tableSchema.lookupFieldSchema( *f ); + if (lookupFieldSchema) { + createExtendedTableSchemaFieldElementIfNeeded( + doc, extendedTableSchemaMainEl, f->name(), extendedTableSchemaFieldEl, false/* !append */); + LookupFieldSchema::saveToDom(*lookupFieldSchema, doc, extendedTableSchemaFieldEl); + + if (extendedTableSchemaFieldEl.hasChildNodes()) { + // this element provides the definition, so let's append it now + createExtendedTableSchemaMainElementIfNeeded(doc, extendedTableSchemaMainEl, + extendedTableSchemaStringIsEmpty); + extendedTableSchemaMainEl.appendChild( extendedTableSchemaFieldEl ); + } + } + } + + // Store extended schema information (see ExtendedTableSchemaInformation in Kexi Wiki) + if (extendedTableSchemaStringIsEmpty) { +#ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString("** Extended table schema REMOVED.")); +#endif + if (!removeDataBlock( tableSchema.id(), "extended_schema")) + return false; + } + else { +#ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString("** Extended table schema set to:\n")+doc.toString(4)); +#endif + if (!storeDataBlock( tableSchema.id(), doc.toString(1), "extended_schema" )) + return false; + } + return true; +} + +bool Connection::loadExtendedTableSchemaData(TableSchema& tableSchema) +{ +#define loadExtendedTableSchemaData_ERR \ + { setError(i18n("Error while loading extended table schema information.")); \ + return false; } +#define loadExtendedTableSchemaData_ERR2(details) \ + { setError(i18n("Error while loading extended table schema information."), details); \ + return false; } +#define loadExtendedTableSchemaData_ERR3(data) \ + { setError(i18n("Error while loading extended table schema information."), \ + i18n("Invalid XML data: ") + data.left(1024) ); \ + return false; } + + // Load extended schema information, if present (see ExtendedTableSchemaInformation in Kexi Wiki) + QString extendedTableSchemaString; + tristate res = loadDataBlock( tableSchema.id(), extendedTableSchemaString, "extended_schema" ); + if (!res) + loadExtendedTableSchemaData_ERR; + // extendedTableSchemaString will be just empty if there is no such data block + +#ifdef KEXIDB_LOOKUP_FIELD_TEST +//<temp. for LookupFieldSchema tests> + if (tableSchema.name()=="cars") { + LookupFieldSchema *lookupFieldSchema = new LookupFieldSchema(); + lookupFieldSchema->rowSource().setType(LookupFieldSchema::RowSource::Table); + lookupFieldSchema->rowSource().setName("persons"); + lookupFieldSchema->setBoundColumn(0); //id + lookupFieldSchema->setVisibleColumn(3); //surname + tableSchema.setLookupFieldSchema( "owner", lookupFieldSchema ); + } +//</temp. for LookupFieldSchema tests> +#endif + + if (extendedTableSchemaString.isEmpty()) + return true; + + QDomDocument doc; + QString errorMsg; + int errorLine, errorColumn; + if (!doc.setContent( extendedTableSchemaString, &errorMsg, &errorLine, &errorColumn )) + loadExtendedTableSchemaData_ERR2( i18n("Error in XML data: \"%1\" in line %2, column %3.\nXML data: ") + .arg(errorMsg).arg(errorLine).arg(errorColumn) + extendedTableSchemaString.left(1024)); + +//! @todo look at the current format version (KEXIDB_EXTENDED_TABLE_SCHEMA_VERSION) + + if (doc.doctype().name()!="EXTENDED_TABLE_SCHEMA") + loadExtendedTableSchemaData_ERR3( extendedTableSchemaString ); + + QDomElement docEl = doc.documentElement(); + if (docEl.tagName()!="EXTENDED_TABLE_SCHEMA") + loadExtendedTableSchemaData_ERR3( extendedTableSchemaString ); + + for (QDomNode n = docEl.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement fieldEl = n.toElement(); + if (fieldEl.tagName()=="field") { + Field *f = tableSchema.field( fieldEl.attribute("name") ); + if (f) { + //set properties of the field: +//! @todo more properties + for (QDomNode propNode = fieldEl.firstChild(); + !propNode.isNull(); propNode = propNode.nextSibling()) + { + QDomElement propEl = propNode.toElement(); + bool ok; + int intValue; + if (propEl.tagName()=="property") { + QCString propertyName = propEl.attribute("name").latin1(); + if (propEl.attribute("custom")=="true") { + //custom property + f->setCustomProperty(propertyName, + KexiDB::loadPropertyValueFromDom( propEl.firstChild() )); + } + else if (propertyName == "visibleDecimalPlaces" + && KexiDB::supportsVisibleDecimalPlacesProperty(f->type())) + { + intValue = KexiDB::loadIntPropertyValueFromDom( propEl.firstChild(), &ok ); + if (ok) + f->setVisibleDecimalPlaces(intValue); + } +//! @todo more properties... + } + else if (propEl.tagName()=="lookup-column") { + LookupFieldSchema *lookupFieldSchema = LookupFieldSchema::loadFromDom(propEl); + if (lookupFieldSchema) + lookupFieldSchema->debug(); + tableSchema.setLookupFieldSchema( f->name(), lookupFieldSchema ); + } + } + } + else { + KexiDBWarn << "Connection::loadExtendedTableSchemaData(): no such field \"" + << fieldEl.attribute("name") << "\" in table \"" << tableSchema.name() << "\"" << endl; + } + } + } + + return true; +} + +KexiDB::Field* Connection::setupField( const RowData &data ) +{ + bool ok = true; + int f_int_type = data.at(1).toInt(&ok); + if (f_int_type<=Field::InvalidType || f_int_type>Field::LastType) + ok = false; + if (!ok) + return 0; + Field::Type f_type = (Field::Type)f_int_type; + int f_len = QMAX( 0, data.at(3).toInt(&ok) ); + if (!ok) + return 0; + int f_prec = data.at(4).toInt(&ok); + if (!ok) + return 0; + int f_constr = data.at(5).toInt(&ok); + if (!ok) + return 0; + int f_opts = data.at(6).toInt(&ok); + if (!ok) + return 0; + + if (!KexiUtils::isIdentifier( data.at(2).toString() )) { + setError(ERR_INVALID_IDENTIFIER, i18n("Invalid object name \"%1\"") + .arg( data.at(2).toString() )); + ok = false; + return 0; + } + + Field *f = new Field( + data.at(2).toString(), f_type, f_constr, f_opts, f_len, f_prec ); + + f->setDefaultValue( KexiDB::stringToVariant(data.at(7).toString(), Field::variantType( f_type ), ok) ); + if (!ok) { + KexiDBWarn << "Connection::setupTableSchema() problem with KexiDB::stringToVariant(" + << data.at(7).toString() << ")" << endl; + } + ok = true; //problem with defaultValue is not critical + + f->m_caption = data.at(9).toString(); + f->m_desc = data.at(10).toString(); + return f; +} + +KexiDB::TableSchema* Connection::setupTableSchema( const RowData &data ) +{ + TableSchema *t = new TableSchema( this ); + if (!setupObjectSchemaData( data, *t )) { + delete t; + return 0; + } + + KexiDB::Cursor *cursor; + if (!(cursor = executeQuery( + QString::fromLatin1("SELECT t_id, f_type, f_name, f_length, f_precision, f_constraints, " + "f_options, f_default, f_order, f_caption, f_help" + " FROM kexi__fields WHERE t_id=%1 ORDER BY f_order").arg(t->m_id) ))) + { + delete t; + return 0; + } + if (!cursor->moveFirst()) { + if (!cursor->error() && cursor->eof()) { + setError(i18n("Table has no fields defined.")); + } + deleteCursor(cursor); + delete t; + return 0; + } + + // For each field: load its schema + RowData fieldData; + bool ok = true; + while (!cursor->eof()) { +// KexiDBDbg<<"@@@ f_name=="<<cursor->value(2).asCString()<<endl; + cursor->storeCurrentRow(fieldData); + Field *f = setupField(fieldData); + if (!f) { + ok = false; + break; + } + t->addField(f); + cursor->moveNext(); + } + + if (!ok) {//error: + deleteCursor(cursor); + delete t; + return 0; + } + + if (!deleteCursor(cursor)) { + delete t; + return 0; + } + + if (!loadExtendedTableSchemaData(*t)) { + delete t; + return 0; + } + //store locally: + d->tables.insert(t->m_id, t); + d->tables_byname.insert(t->m_name.lower(), t); + return t; +} + +TableSchema* Connection::tableSchema( const QString& tableName ) +{ + QString m_tableName = tableName.lower(); + TableSchema *t = d->tables_byname[m_tableName]; + if (t) + return t; + //not found: retrieve schema + RowData data; + if (true!=querySingleRecord(QString::fromLatin1( + "SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE lower(o_name)='%1' AND o_type=%2") + .arg(m_tableName).arg(KexiDB::TableObjectType), data)) + return 0; + + return setupTableSchema(data); +} + +TableSchema* Connection::tableSchema( int tableId ) +{ + TableSchema *t = d->tables[tableId]; + if (t) + return t; + //not found: retrieve schema + RowData data; + if (true!=querySingleRecord(QString::fromLatin1( + "SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1") + .arg(tableId), data)) + return 0; + + return setupTableSchema(data); +} + +tristate Connection::loadDataBlock( int objectID, QString &dataString, const QString& dataID ) +{ + if (objectID<=0) + return false; + return querySingleString( + QString("SELECT o_data FROM kexi__objectdata WHERE o_id=") + QString::number(objectID) + + " AND " + KexiDB::sqlWhere(m_driver, KexiDB::Field::Text, "o_sub_id", dataID), + dataString ); +} + +bool Connection::storeDataBlock( int objectID, const QString &dataString, const QString& dataID ) +{ + if (objectID<=0) + return false; + QString sql(QString::fromLatin1("SELECT kexi__objectdata.o_id FROM kexi__objectdata WHERE o_id=%1").arg(objectID)); + QString sql_sub( KexiDB::sqlWhere(m_driver, KexiDB::Field::Text, "o_sub_id", dataID) ); + + bool ok, exists; + exists = resultExists(sql + " and " + sql_sub, ok); + if (!ok) + return false; + if (exists) { + return executeSQL( "UPDATE kexi__objectdata SET o_data=" + + m_driver->valueToSQL( KexiDB::Field::LongText, dataString ) + + " WHERE o_id=" + QString::number(objectID) + " AND " + sql_sub ); + } + return executeSQL( + QString::fromLatin1("INSERT INTO kexi__objectdata (o_id, o_data, o_sub_id) VALUES (") + + QString::number(objectID) +"," + m_driver->valueToSQL( KexiDB::Field::LongText, dataString ) + + "," + m_driver->valueToSQL( KexiDB::Field::Text, dataID ) + ")" ); +} + +bool Connection::removeDataBlock( int objectID, const QString& dataID) +{ + if (objectID<=0) + return false; + if (dataID.isEmpty()) + return KexiDB::deleteRow(*this, "kexi__objectdata", "o_id", QString::number(objectID)); + else + return KexiDB::deleteRow(*this, "kexi__objectdata", + "o_id", KexiDB::Field::Integer, objectID, "o_sub_id", KexiDB::Field::Text, dataID); +} + +KexiDB::QuerySchema* Connection::setupQuerySchema( const RowData &data ) +{ + bool ok = true; + const int objID = data[0].toInt(&ok); + if (!ok) + return false; + QString sqlText; + if (!loadDataBlock( objID, sqlText, "sql" )) { + setError(ERR_OBJECT_NOT_FOUND, + i18n("Could not find definition for query \"%1\". Removing this query is recommended.") + .arg(data[2].toString())); + return 0; + } + d->parser()->parse( sqlText ); + KexiDB::QuerySchema *query = d->parser()->query(); + //error? + if (!query) { + setError(ERR_SQL_PARSE_ERROR, + i18n("<p>Could not load definition for query \"%1\". " + "SQL statement for this query is invalid:<br><tt>%2</tt></p>\n" + "<p>You can open this query in Text View and correct it.</p>").arg(data[2].toString()) + .arg(d->parser()->statement())); + return 0; + } + if (!setupObjectSchemaData( data, *query )) { + delete query; + return 0; + } + d->queries.insert(query->m_id, query); + d->queries_byname.insert(query->m_name, query); + return query; +} + +QuerySchema* Connection::querySchema( const QString& queryName ) +{ + QString m_queryName = queryName.lower(); + QuerySchema *q = d->queries_byname[m_queryName]; + if (q) + return q; + //not found: retrieve schema + RowData data; + if (true!=querySingleRecord(QString::fromLatin1( + "SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE lower(o_name)='%1' AND o_type=%2") + .arg(m_queryName).arg(KexiDB::QueryObjectType), data)) + return 0; + + return setupQuerySchema(data); +} + +QuerySchema* Connection::querySchema( int queryId ) +{ + QuerySchema *q = d->queries[queryId]; + if (q) + return q; + //not found: retrieve schema + clearError(); + RowData data; + if (true!=querySingleRecord(QString::fromLatin1( + "SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1").arg(queryId), data)) + return 0; + + return setupQuerySchema(data); +} + +bool Connection::setQuerySchemaObsolete( const QString& queryName ) +{ + QuerySchema* oldQuery = querySchema( queryName ); + if (!oldQuery) + return false; + d->obsoleteQueries.append(oldQuery); + d->queries_byname.take(queryName); + d->queries.take(oldQuery->id()); + return true; +} + +TableSchema* Connection::newKexiDBSystemTableSchema(const QString& tsname) +{ + TableSchema *ts = new TableSchema(tsname.lower()); + insertInternalTableSchema( ts ); + return ts; +} + +bool Connection::isInternalTableSchema(const QString& tableName) +{ + return (d->kexiDBSystemTables[ d->tables_byname[tableName] ]) + // these are here for compatiblility because we're no longer instantiate + // them but can exist in projects created with previous Kexi versions: + || tableName=="kexi__final" || tableName=="kexi__useractions"; +} + +void Connection::insertInternalTableSchema(TableSchema *tableSchema) +{ + tableSchema->setKexiDBSystem(true); + d->kexiDBSystemTables.insert(tableSchema, tableSchema); + d->tables_byname.insert(tableSchema->name(), tableSchema); +} + +//! Creates kexi__* tables. +bool Connection::setupKexiDBSystemSchema() +{ + if (!d->kexiDBSystemTables.isEmpty()) + return true; //already set up + + TableSchema *t_objects = newKexiDBSystemTableSchema("kexi__objects"); + t_objects->addField( new Field("o_id", Field::Integer, Field::PrimaryKey | Field::AutoInc, Field::Unsigned) ) + .addField( new Field("o_type", Field::Byte, 0, Field::Unsigned) ) + .addField( new Field("o_name", Field::Text) ) + .addField( new Field("o_caption", Field::Text ) ) + .addField( new Field("o_desc", Field::LongText ) ); + + t_objects->debug(); + + TableSchema *t_objectdata = newKexiDBSystemTableSchema("kexi__objectdata"); + t_objectdata->addField( new Field("o_id", Field::Integer, Field::NotNull, Field::Unsigned) ) + .addField( new Field("o_data", Field::LongText) ) + .addField( new Field("o_sub_id", Field::Text) ); + + TableSchema *t_fields = newKexiDBSystemTableSchema("kexi__fields"); + t_fields->addField( new Field("t_id", Field::Integer, 0, Field::Unsigned) ) + .addField( new Field("f_type", Field::Byte, 0, Field::Unsigned) ) + .addField( new Field("f_name", Field::Text ) ) + .addField( new Field("f_length", Field::Integer ) ) + .addField( new Field("f_precision", Field::Integer ) ) + .addField( new Field("f_constraints", Field::Integer ) ) + .addField( new Field("f_options", Field::Integer ) ) + .addField( new Field("f_default", Field::Text ) ) + //these are additional properties: + .addField( new Field("f_order", Field::Integer ) ) + .addField( new Field("f_caption", Field::Text ) ) + .addField( new Field("f_help", Field::LongText ) ); + +/* TableSchema *t_querydata = newKexiDBSystemTableSchema("kexi__querydata"); + t_querydata->addField( new Field("q_id", Field::Integer, 0, Field::Unsigned) ) + .addField( new Field("q_sql", Field::LongText ) ) + .addField( new Field("q_valid", Field::Boolean ) ); + + TableSchema *t_queryfields = newKexiDBSystemTableSchema("kexi__queryfields"); + t_queryfields->addField( new Field("q_id", Field::Integer, 0, Field::Unsigned) ) + .addField( new Field("f_order", Field::Integer ) ) + .addField( new Field("f_id", Field::Integer ) ) + .addField( new Field("f_tab_asterisk", Field::Integer, 0, Field::Unsigned) ) + .addField( new Field("f_alltab_asterisk", Field::Boolean) ); + + TableSchema *t_querytables = newKexiDBSystemTableSchema("kexi__querytables"); + t_querytables->addField( new Field("q_id", Field::Integer, 0, Field::Unsigned) ) + .addField( new Field("t_id", Field::Integer, 0, Field::Unsigned) ) + .addField( new Field("t_order", Field::Integer, 0, Field::Unsigned) );*/ + + TableSchema *t_db = newKexiDBSystemTableSchema("kexi__db"); + t_db->addField( new Field("db_property", Field::Text, Field::NoConstraints, Field::NoOptions, 32 ) ) + .addField( new Field("db_value", Field::LongText ) ); + +/* moved to KexiProject... + TableSchema *t_parts = newKexiDBSystemTableSchema("kexi__parts"); + t_parts->addField( new Field("p_id", Field::Integer, Field::PrimaryKey | Field::AutoInc, Field::Unsigned) ) + .addField( new Field("p_name", Field::Text) ) + .addField( new Field("p_mime", Field::Text ) ) + .addField( new Field("p_url", Field::Text ) ); +*/ + +/*UNUSED + TableSchema *t_final = newKexiDBSystemTableSchema("kexi__final"); + t_final->addField( new Field("p_id", Field::Integer, 0, Field::Unsigned) ) + .addField( new Field("property", Field::LongText ) ) + .addField( new Field("value", Field::BLOB) ); + + TableSchema *t_useractions = newKexiDBSystemTableSchema("kexi__useractions"); + t_useractions->addField( new Field("p_id", Field::Integer, 0, Field::Unsigned) ) + .addField( new Field("scope", Field::Integer ) ) + .addField( new Field("name", Field::LongText ) ) + .addField( new Field("text", Field::LongText ) ) + .addField( new Field("icon", Field::LongText ) ) + .addField( new Field("method", Field::Integer ) ) + .addField( new Field("arguments", Field::LongText) ); +*/ + return true; +} + +void Connection::removeMe(TableSchema *ts) +{ + if (ts && !m_destructor_started) { + d->tables.take(ts->id()); + d->tables_byname.take(ts->name()); + } +} + +QString Connection::anyAvailableDatabaseName() +{ + if (!d->availableDatabaseName.isEmpty()) { + return d->availableDatabaseName; + } + return m_driver->beh->ALWAYS_AVAILABLE_DATABASE_NAME; +} + +void Connection::setAvailableDatabaseName(const QString& dbName) +{ + d->availableDatabaseName = dbName; +} + +//! @internal used in updateRow(), insertRow(), +inline void updateRowDataWithNewValues(QuerySchema &query, RowData& data, KexiDB::RowEditBuffer::DBMap& b, + QMap<QueryColumnInfo*,int>& columnsOrderExpanded) +{ + columnsOrderExpanded = query.columnsOrder(QuerySchema::ExpandedList); + QMap<QueryColumnInfo*,int>::ConstIterator columnsOrderExpandedIt; + for (KexiDB::RowEditBuffer::DBMap::ConstIterator it=b.constBegin();it!=b.constEnd();++it) { + columnsOrderExpandedIt = columnsOrderExpanded.find( it.key() ); + if (columnsOrderExpandedIt == columnsOrderExpanded.constEnd()) { + KexiDBWarn << "(Connection) updateRowDataWithNewValues(): \"now also assign new value in memory\" step " + "- could not find item '" << it.key()->aliasOrName() << "'" << endl; + continue; + } + data[ columnsOrderExpandedIt.data() ] = it.data(); + } +} + +bool Connection::updateRow(QuerySchema &query, RowData& data, RowEditBuffer& buf, bool useROWID) +{ +// Each SQL identifier needs to be escaped in the generated query. +// query.debug(); + + KexiDBDbg << "Connection::updateRow.." << endl; + clearError(); + //--get PKEY + if (buf.dbBuffer().isEmpty()) { + KexiDBDbg << " -- NO CHANGES DATA!" << endl; + return true; + } + TableSchema *mt = query.masterTable(); + if (!mt) { + KexiDBWarn << " -- NO MASTER TABLE!" << endl; + setError(ERR_UPDATE_NO_MASTER_TABLE, + i18n("Could not update row because there is no master table defined.")); + return false; + } + IndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0; + if (!useROWID && !pkey) { + KexiDBWarn << " -- NO MASTER TABLE's PKEY!" << endl; + setError(ERR_UPDATE_NO_MASTER_TABLES_PKEY, + i18n("Could not update row because master table has no primary key defined.")); +//! @todo perhaps we can try to update without using PKEY? + return false; + } + //update the record: + m_sql = "UPDATE " + escapeIdentifier(mt->name()) + " SET "; + QString sqlset, sqlwhere; + sqlset.reserve(1024); + sqlwhere.reserve(1024); + KexiDB::RowEditBuffer::DBMap b = buf.dbBuffer(); + for (KexiDB::RowEditBuffer::DBMap::ConstIterator it=b.constBegin();it!=b.constEnd();++it) { + if (it.key()->field->table()!=mt) + continue; // skip values for fields outside of the master table (e.g. a "visible value" of the lookup field) + if (!sqlset.isEmpty()) + sqlset+=","; + sqlset += (escapeIdentifier(it.key()->field->name()) + "=" + + m_driver->valueToSQL(it.key()->field,it.data())); + } + if (pkey) { + const QValueVector<int> pkeyFieldsOrder( query.pkeyFieldsOrder() ); + KexiDBDbg << pkey->fieldCount() << " ? " << query.pkeyFieldsCount() << endl; + if (pkey->fieldCount() != query.pkeyFieldsCount()) { //sanity check + KexiDBWarn << " -- NO ENTIRE MASTER TABLE's PKEY SPECIFIED!" << endl; + setError(ERR_UPDATE_NO_ENTIRE_MASTER_TABLES_PKEY, + i18n("Could not update row because it does not contain entire master table's primary key.")); + return false; + } + if (!pkey->fields()->isEmpty()) { + uint i=0; + for (Field::ListIterator it = pkey->fieldsIterator(); it.current(); i++, ++it) { + if (!sqlwhere.isEmpty()) + sqlwhere+=" AND "; + QVariant val = data[ pkeyFieldsOrder[i] ]; + if (val.isNull() || !val.isValid()) { + setError(ERR_UPDATE_NULL_PKEY_FIELD, + i18n("Primary key's field \"%1\" cannot be empty.").arg(it.current()->name())); + //js todo: pass the field's name somewhere! + return false; + } + sqlwhere += ( escapeIdentifier(it.current()->name()) + "=" + + m_driver->valueToSQL( it.current(), val ) ); + } + } + } + else {//use ROWID + sqlwhere = ( escapeIdentifier(m_driver->beh->ROW_ID_FIELD_NAME) + "=" + + m_driver->valueToSQL(Field::BigInteger, data[data.size()-1])); + } + m_sql += (sqlset + " WHERE " + sqlwhere); + KexiDBDbg << " -- SQL == " << ((m_sql.length() > 400) ? (m_sql.left(400)+"[.....]") : m_sql) << endl; + + if (!executeSQL(m_sql)) { + setError(ERR_UPDATE_SERVER_ERROR, i18n("Row updating on the server failed.")); + return false; + } + //success: now also assign new values in memory: + QMap<QueryColumnInfo*,int> columnsOrderExpanded; + updateRowDataWithNewValues(query, data, b, columnsOrderExpanded); + return true; +} + +bool Connection::insertRow(QuerySchema &query, RowData& data, RowEditBuffer& buf, bool getROWID) +{ +// Each SQL identifier needs to be escaped in the generated query. + KexiDBDbg << "Connection::updateRow.." << endl; + clearError(); + //--get PKEY + /*disabled: there may be empty rows (with autoinc) + if (buf.dbBuffer().isEmpty()) { + KexiDBDbg << " -- NO CHANGES DATA!" << endl; + return true; }*/ + TableSchema *mt = query.masterTable(); + if (!mt) { + KexiDBWarn << " -- NO MASTER TABLE!" << endl; + setError(ERR_INSERT_NO_MASTER_TABLE, + i18n("Could not insert row because there is no master table defined.")); + return false; + } + IndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0; + if (!getROWID && !pkey) + KexiDBWarn << " -- WARNING: NO MASTER TABLE's PKEY" << endl; + + QString sqlcols, sqlvals; + sqlcols.reserve(1024); + sqlvals.reserve(1024); + + //insert the record: + m_sql = "INSERT INTO " + escapeIdentifier(mt->name()) + " ("; + KexiDB::RowEditBuffer::DBMap b = buf.dbBuffer(); + + // add default values, if available (for any column without value explicitly set) + const QueryColumnInfo::Vector fieldsExpanded( query.fieldsExpanded( QuerySchema::Unique ) ); + for (uint i=0; i<fieldsExpanded.count(); i++) { + QueryColumnInfo *ci = fieldsExpanded.at(i); + if (ci->field && KexiDB::isDefaultValueAllowed(ci->field) + && !ci->field->defaultValue().isNull() + && !b.contains( ci )) + { + KexiDBDbg << "Connection::insertRow(): adding default value '" << ci->field->defaultValue().toString() + << "' for column '" << ci->field->name() << "'" << endl; + b.insert( ci, ci->field->defaultValue() ); + } + } + + if (b.isEmpty()) { + // empty row inserting requested: + if (!getROWID && !pkey) { + KexiDBWarn << "MASTER TABLE's PKEY REQUIRED FOR INSERTING EMPTY ROWS: INSERT CANCELLED" << endl; + setError(ERR_INSERT_NO_MASTER_TABLES_PKEY, + i18n("Could not insert row because master table has no primary key defined.")); + return false; + } + if (pkey) { + const QValueVector<int> pkeyFieldsOrder( query.pkeyFieldsOrder() ); +// KexiDBDbg << pkey->fieldCount() << " ? " << query.pkeyFieldsCount() << endl; + if (pkey->fieldCount() != query.pkeyFieldsCount()) { //sanity check + KexiDBWarn << "NO ENTIRE MASTER TABLE's PKEY SPECIFIED!" << endl; + setError(ERR_INSERT_NO_ENTIRE_MASTER_TABLES_PKEY, + i18n("Could not insert row because it does not contain entire master table's primary key.") + .arg(query.name())); + return false; + } + } + //at least one value is needed for VALUES section: find it and set to NULL: + Field *anyField = mt->anyNonPKField(); + if (!anyField) { + if (!pkey) { + KexiDBWarn << "WARNING: NO FIELD AVAILABLE TO SET IT TO NULL" << endl; + return false; + } + else { + //try to set NULL in pkey field (could not work for every SQL engine!) + anyField = pkey->fields()->first(); + } + } + sqlcols += escapeIdentifier(anyField->name()); + sqlvals += m_driver->valueToSQL(anyField,QVariant()/*NULL*/); + } + else { + // non-empty row inserting requested: + for (KexiDB::RowEditBuffer::DBMap::ConstIterator it=b.constBegin();it!=b.constEnd();++it) { + if (it.key()->field->table()!=mt) + continue; // skip values for fields outside of the master table (e.g. a "visible value" of the lookup field) + if (!sqlcols.isEmpty()) { + sqlcols+=","; + sqlvals+=","; + } + sqlcols += escapeIdentifier(it.key()->field->name()); + sqlvals += m_driver->valueToSQL(it.key()->field,it.data()); + } + } + m_sql += (sqlcols + ") VALUES (" + sqlvals + ")"); +// KexiDBDbg << " -- SQL == " << m_sql << endl; + + bool res = executeSQL(m_sql); + + if (!res) { + setError(ERR_INSERT_SERVER_ERROR, i18n("Row inserting on the server failed.")); + return false; + } + //success: now also assign a new value in memory: + QMap<QueryColumnInfo*,int> columnsOrderExpanded; + updateRowDataWithNewValues(query, data, b, columnsOrderExpanded); + + //fetch autoincremented values + QueryColumnInfo::List *aif_list = query.autoIncrementFields(); + Q_ULLONG ROWID = 0; + if (pkey && !aif_list->isEmpty()) { + //! @todo now only if PKEY is present, this should also work when there's no PKEY + QueryColumnInfo *id_columnInfo = aif_list->first(); +//! @todo safe to cast it? + Q_ULLONG last_id = lastInsertedAutoIncValue( + id_columnInfo->field->name(), id_columnInfo->field->table()->name(), &ROWID); + if (last_id==(Q_ULLONG)-1 || last_id<=0) { + //! @todo show error +//! @todo remove just inserted row. How? Using ROLLBACK? + return false; + } + RowData aif_data; + QString getAutoIncForInsertedValue = QString::fromLatin1("SELECT ") + + query.autoIncrementSQLFieldsList(m_driver) + + QString::fromLatin1(" FROM ") + + escapeIdentifier(id_columnInfo->field->table()->name()) + + QString::fromLatin1(" WHERE ") + + escapeIdentifier(id_columnInfo->field->name()) + "=" + + QString::number(last_id); + if (true!=querySingleRecord(getAutoIncForInsertedValue, aif_data)) { + //! @todo show error + return false; + } + QueryColumnInfo::ListIterator ci_it(*aif_list); + QueryColumnInfo *ci; + for (uint i=0; (ci = ci_it.current()); ++ci_it, i++) { +// KexiDBDbg << "Connection::insertRow(): AUTOINCREMENTED FIELD " << fi->field->name() << " == " +// << aif_data[i].toInt() << endl; + ( data[ columnsOrderExpanded[ ci ] ] = aif_data[i] ).cast( ci->field->variantType() ); //cast to get proper type + } + } + else { + ROWID = drv_lastInsertRowID(); +// KexiDBDbg << "Connection::insertRow(): new ROWID == " << (uint)ROWID << endl; + if (m_driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE) { + KexiDBWarn << "Connection::insertRow(): m_driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE" << endl; + return false; + } + } + if (getROWID && /*sanity check*/data.size() > fieldsExpanded.size()) { +// KexiDBDbg << "Connection::insertRow(): new ROWID == " << (uint)ROWID << endl; + data[data.size()-1] = ROWID; + } + return true; +} + +bool Connection::deleteRow(QuerySchema &query, RowData& data, bool useROWID) +{ +// Each SQL identifier needs to be escaped in the generated query. + KexiDBWarn << "Connection::deleteRow.." << endl; + clearError(); + TableSchema *mt = query.masterTable(); + if (!mt) { + KexiDBWarn << " -- NO MASTER TABLE!" << endl; + setError(ERR_DELETE_NO_MASTER_TABLE, + i18n("Could not delete row because there is no master table defined.") + .arg(query.name())); + return false; + } + IndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0; + +//! @todo allow to delete from a table without pkey + if (!useROWID && !pkey) { + KexiDBWarn << " -- WARNING: NO MASTER TABLE's PKEY" << endl; + setError(ERR_DELETE_NO_MASTER_TABLES_PKEY, + i18n("Could not delete row because there is no primary key for master table defined.")); + return false; + } + + //update the record: + m_sql = "DELETE FROM " + escapeIdentifier(mt->name()) + " WHERE "; + QString sqlwhere; + sqlwhere.reserve(1024); + + if (pkey) { + const QValueVector<int> pkeyFieldsOrder( query.pkeyFieldsOrder() ); + KexiDBDbg << pkey->fieldCount() << " ? " << query.pkeyFieldsCount() << endl; + if (pkey->fieldCount() != query.pkeyFieldsCount()) { //sanity check + KexiDBWarn << " -- NO ENTIRE MASTER TABLE's PKEY SPECIFIED!" << endl; + setError(ERR_DELETE_NO_ENTIRE_MASTER_TABLES_PKEY, + i18n("Could not delete row because it does not contain entire master table's primary key.")); + return false; + } + uint i=0; + for (Field::ListIterator it = pkey->fieldsIterator(); it.current(); i++, ++it) { + if (!sqlwhere.isEmpty()) + sqlwhere+=" AND "; + QVariant val = data[ pkeyFieldsOrder[i] ]; + if (val.isNull() || !val.isValid()) { + setError(ERR_DELETE_NULL_PKEY_FIELD, i18n("Primary key's field \"%1\" cannot be empty.") + .arg(it.current()->name())); +//js todo: pass the field's name somewhere! + return false; + } + sqlwhere += ( escapeIdentifier(it.current()->name()) + "=" + + m_driver->valueToSQL( it.current(), val ) ); + } + } + else {//use ROWID + sqlwhere = ( escapeIdentifier(m_driver->beh->ROW_ID_FIELD_NAME) + "=" + + m_driver->valueToSQL(Field::BigInteger, data[data.size()-1])); + } + m_sql += sqlwhere; + KexiDBDbg << " -- SQL == " << m_sql << endl; + + if (!executeSQL(m_sql)) { + setError(ERR_DELETE_SERVER_ERROR, i18n("Row deletion on the server failed.")); + return false; + } + return true; +} + +bool Connection::deleteAllRows(QuerySchema &query) +{ + clearError(); + TableSchema *mt = query.masterTable(); + if (!mt) { + KexiDBWarn << " -- NO MASTER TABLE!" << endl; + return false; + } + IndexSchema *pkey = mt->primaryKey(); + if (!pkey || pkey->fields()->isEmpty()) + KexiDBWarn << "Connection::deleteAllRows -- WARNING: NO MASTER TABLE's PKEY" << endl; + + m_sql = "DELETE FROM " + escapeIdentifier(mt->name()); + + KexiDBDbg << " -- SQL == " << m_sql << endl; + + if (!executeSQL(m_sql)) { + setError(ERR_DELETE_SERVER_ERROR, i18n("Row deletion on the server failed.")); + return false; + } + return true; +} + +void Connection::registerForTableSchemaChanges(TableSchemaChangeListenerInterface& listener, + TableSchema &schema) +{ + QPtrList<TableSchemaChangeListenerInterface>* listeners = d->tableSchemaChangeListeners[&schema]; + if (!listeners) { + listeners = new QPtrList<TableSchemaChangeListenerInterface>(); + d->tableSchemaChangeListeners.insert(&schema, listeners); + } +//TODO: inefficient + if (listeners->findRef( &listener )==-1) + listeners->append( &listener ); +} + +void Connection::unregisterForTableSchemaChanges(TableSchemaChangeListenerInterface& listener, + TableSchema &schema) +{ + QPtrList<TableSchemaChangeListenerInterface>* listeners = d->tableSchemaChangeListeners[&schema]; + if (!listeners) + return; +//TODO: inefficient + listeners->remove( &listener ); +} + +void Connection::unregisterForTablesSchemaChanges(TableSchemaChangeListenerInterface& listener) +{ + for (QPtrDictIterator< QPtrList<TableSchemaChangeListenerInterface> > it(d->tableSchemaChangeListeners); + it.current(); ++it) + { + if (-1!=it.current()->find(&listener)) + it.current()->take(); + } +} + +QPtrList<Connection::TableSchemaChangeListenerInterface>* +Connection::tableSchemaChangeListeners(TableSchema& tableSchema) const +{ + KexiDBDbg << d->tableSchemaChangeListeners.count() << endl; + return d->tableSchemaChangeListeners[&tableSchema]; +} + +tristate Connection::closeAllTableSchemaChangeListeners(TableSchema& tableSchema) +{ + QPtrList<Connection::TableSchemaChangeListenerInterface> *listeners = d->tableSchemaChangeListeners[&tableSchema]; + if (!listeners) + return true; + QPtrListIterator<KexiDB::Connection::TableSchemaChangeListenerInterface> tmpListeners(*listeners); //safer copy + tristate res = true; + //try to close every window + for (QPtrListIterator<KexiDB::Connection::TableSchemaChangeListenerInterface> it(tmpListeners); + it.current() && res==true; ++it) + { + res = it.current()->closeListener(); + } + return res; +} + +/*PreparedStatement::Ptr Connection::prepareStatement(PreparedStatement::StatementType, + TableSchema&) +{ + //safe? + return 0; +}*/ + +void Connection::setReadOnly(bool set) +{ + if (d->isConnected) + return; //sanity + d->readOnly = set; +} + +bool Connection::isReadOnly() const +{ + return d->readOnly; +} + +#include "connection.moc" diff --git a/kexi/kexidb/connection.h b/kexi/kexidb/connection.h new file mode 100644 index 00000000..b72e01d4 --- /dev/null +++ b/kexi/kexidb/connection.h @@ -0,0 +1,1198 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2007 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_CONNECTION_H +#define KEXIDB_CONNECTION_H + +#include <qobject.h> +#include <qstringlist.h> +#include <qintdict.h> +#include <qdict.h> +#include <qptrdict.h> +#include <qvaluevector.h> +#include <qvaluelist.h> +#include <qvariant.h> +#include <qguardedptr.h> + +#include <kexidb/object.h> +#include <kexidb/connectiondata.h> +#include <kexidb/tableschema.h> +#include <kexidb/queryschema.h> +#include <kexidb/queryschemaparameter.h> +#include <kexidb/transaction.h> +#include <kexidb/driver.h> +#include <kexidb/preparedstatement.h> + +#include <kexiutils/tristate.h> + +namespace KexiDB { + +//! structure for storing single record with type information +typedef QValueVector<QVariant> RowData; + +class Cursor; +class ConnectionPrivate; +class RowEditBuffer; +class DatabaseProperties; +class AlterTableHandler; + +/*! @short Provides database connection, allowing queries and data modification. + + This class represents a database connection established within a data source. + It supports data queries and modification by creating client-side database cursors. + Database transactions are supported. +*/ +class KEXI_DB_EXPORT Connection : public QObject, public KexiDB::Object +{ + Q_OBJECT + + public: + + /*! Opened connection is automatically disconnected and removed + from driver's connections list. + Note for driver developers: you should call destroy() + from you Connection's subclass destructor. */ + virtual ~Connection(); + + /*! \return parameters that were used to create this connection. */ + ConnectionData* data() const; + + /*! \return the driver used for this connection. */ + inline Driver* driver() const { return m_driver; } + + /*! + \brief Connects to driver with given parameters. + \return true if successful. */ + bool connect(); + + /*! \return true, if connection is properly established. */ + bool isConnected() const; + + /*! \return true, both if connection is properly established + and any database within this connection is properly used + with useDatabase(). */ + bool isDatabaseUsed() const; + + /*! \return true for read only connection. Used especially for file-based drivers. + Can be reimplemented in a driver to provide real read-only flag of the connection + (SQlite3 dirver does this). */ + virtual bool isReadOnly() const; + + /*! Reimplemented from Object: also clears sql string. + @sa recentSQLString() */ + virtual void clearError(); + + /*! \brief Disconnects from driver with given parameters. + + The database (if used) is closed, and any active transactions + (if supported) are rolled back, so commit these before disconnecting, + if you'd like to save your changes. */ + bool disconnect(); + + /*! \return list of database names for opened connection. + If \a also_system_db is true, the system database names are also returned. */ + QStringList databaseNames(bool also_system_db = false); + + /*! \return true if database \a dbName exists. + If \a ignoreErrors is true, error flag of connection + won't be modified for any errors (it will quietly return), + else (ignoreErrors == false) we can check why the database does + not exist using error(), errorNum() and/or errorMsg(). */ + bool databaseExists( const QString &dbName, bool ignoreErrors = true ); + + /*! \brief Creates new database with name \a dbName, using this connection. + + If database with \a dbName already exists, or other error occurred, + false is returned. + For file-based drivers, \a dbName should be equal to the database + filename (the same as specified for ConnectionData). + + See doc/dev/kexidb_issues.txt document, chapter "Table schema, query schema, etc. storage" + for database schema documentation (detailed description of kexi__* 'system' tables). + + \sa useDatabase() */ + bool createDatabase( const QString &dbName ); + + /*! + \brief Opens an existing database specified by \a dbName. + + If \a kexiCompatible is true (the default) initial checks will be performed + to recognize database Kexi-specific format. Set \a kexiCompatible to false + if you're using native database (one that have no Kexi System tables). + For file-based drivers, \a dbName should be equal to filename + (the same as specified for ConnectionData). + \return true on success, false on failure. + If user has cancelled this action and \a cancelled is not 0, *cancelled is set to true. */ + bool useDatabase( const QString &dbName, bool kexiCompatible = true, bool *cancelled = 0, + MessageHandler* msgHandler = 0 ); + + /*! + \brief Closes currently used database for this connection. + + Any active transactions (if supported) are rolled back, + so commit these before closing, if you'd like to save your changes. */ + bool closeDatabase(); + + /*! \brief Get the name of the current database + + \return name of currently used database for this connection or empty string + if there is no used database */ + QString currentDatabase() const; + + /*! \brief Drops database with name \a dbName. + + if dbName is not specified, currently used database name is used + (it is closed before dropping). + */ + bool dropDatabase( const QString &dbName = QString::null ); + + /*! \return names of all the \a objecttype (see \a ObjectTypes in global.h) + schemas stored in currently used database. KexiDB::AnyObjectType can be passed + as \a objType to get names of objects of any type. + If \a ok is not null then variable pointed by it will be set to the result. + On error, the functions can return incomplete list. */ + QStringList objectNames(int objType = KexiDB::AnyObjectType, bool* ok = 0); + + /*! \return names of all table schemas stored in currently + used database. If \a also_system_tables is true, + internal KexiDB system table names (kexi__*) are also returned. + \sa kexiDBSystemTableNames() */ + QStringList tableNames(bool also_system_tables = false); + + /*! \return list of internal KexiDB system table names + (kexi__*). This does not mean that these tables can be found + in currently opened database. Just static list of table + names is returned. + + The list contents may depend on KexiDB library version; + opened database can contain fewer 'system' tables than in current + KexiDB implementation, if the current one is newer than the one used + to build the database. */ + static const QStringList& kexiDBSystemTableNames(); + + /*! \return server version information for this connection. + If database is not connected (i.e. isConnected() is false) 0 is returned. */ + KexiDB::ServerVersionInfo* serverVersion() const; + + /*! \return version information for this connection. + If database is not used (i.e. isDatabaseUsed() is false) 0 is returned. + It can be compared to drivers' and KexiDB library version to maintain + backward/upward compatiblility. */ + KexiDB::DatabaseVersionInfo* databaseVersion() const; + + /*! \return DatabaseProperties object allowing to read and write global database properties + for this connection. */ + DatabaseProperties& databaseProperties(); + + /*! \return ids of all table schema names stored in currently + used database. These ids can be later used as argument for tableSchema(). + This is a shortcut for objectIds(TableObjectType). + If \a also_system_tables is true, + Internal KexiDB system tables (kexi__*) are not available here + because these have no identifiers assigned (more formally: id=-1). */ + QValueList<int> tableIds(); + + /*! \return ids of all database query schemas stored in currently + used database. These ids can be later used as argument for querySchema(). + This is a shortcut for objectIds(TableObjectType). */ + QValueList<int> queryIds(); + + /*! \return names of all schemas of object with \a objType type + that are stored in currently used database. */ + QValueList<int> objectIds(int objType); + + /*! \brief Creates new transaction handle and starts a new transaction. + \return KexiDB::Transaction object if transaction has been started + successfully, otherwise null transaction. + For drivers that allow single transaction per connection + (Driver::features() && SingleTransactions) this method can be called one time, + and then this single transaction will be default ( setDefaultTransaction() will + be called). + For drivers that allow multiple transactions per connection, no default transaction is + set automatically in beginTransaction() method, you could do this by hand. + \sa setDefaultTransaction(), defaultTransaction(). + */ + Transaction beginTransaction(); + +/*! \todo for nested transactions: + Tansaction* beginTransaction(transaction *parent_transaction); +*/ + /*! Commits transaction \a trans. + If there is not \a trans argument passed, and there is default transaction + (obtained from defaultTransaction()) defined, this one will be committed. + If default is not present, false is returned (when ignore_inactive is + false, the default), or true is returned (when ignore_inactive is true). + + On successful commit, \a trans object will be destroyed. + If this was default transaction, there is no default transaction for now. + */ + bool commitTransaction( Transaction trans = Transaction::null, + bool ignore_inactive = false ); + + /*! Rollbacks transaction \a trans. + If there is not \a trans argument passed, and there is default transaction + (obtained from defaultTransaction()) defined, this one will be rolled back. + If default is not present, false is returned (when ignore_inactive is + false, the default), or true is returned (when ignore_inactive is true). + + or any error occurred, false is returned. + + On successful rollback, \a trans object will be destroyed. + If this was default transaction, there is no default transaction for now. + */ + bool rollbackTransaction( Transaction trans = Transaction::null, + bool ignore_inactive = false ); + + /*! \return handle for default transaction for this connection + or null transaction if there is no such a transaction defined. + If transactions are supported: Any operation on database (e.g. inserts) + that is started without specifying transaction context, will be performed + in the context of this transaction. + + Returned null transaction doesn't mean that there is no transactions + started at all. + Default transaction can be defined automatically for some drivers -- + see beginTransaction(). + \sa KexiDB::Driver::transactionsSupported() + */ + Transaction& defaultTransaction() const; + + /*! Sets default transaction that will be used as context for operations + on data in opened database for this connection. */ + void setDefaultTransaction(const Transaction& trans); + + /*! \return set of handles of currently active transactions. + Note that in multithreading environment some of these + transactions can be already inactive after calling this method. + Use Transaction::active() to check that. Inactive transaction + handle is useless and can be safely dropped. + */ + const QValueList<Transaction>& transactions(); + + /*! \return true if "auto commit" option is on. + + When auto commit is on (the default on for any new Connection object), + every sql functional statement (statement that changes + data in the database implicitly starts a new transaction. + This transaction is automatically committed + after successful statement execution or rolled back on error. + + For drivers that do not support transactions (see Driver::features()) + this method shouldn't be called because it does nothing ans always returns false. + + No internal KexiDB object should changes this option, although auto commit's + behaviour depends on database engine's specifics. Engines that support only single + transaction per connection (see Driver::SingleTransactions), + use this single connection for autocommiting, so if there is already transaction + started by the KexiDB user program (with beginTransaction()), this transaction + is committed before any sql functional statement execution. In this situation + default transaction is also affected (see defaultTransaction()). + + Only for drivers that support nested transactions (Driver::NestedTransactions), + autocommiting works independently from previously started transaction, + + For other drivers set this option off if you need use transaction + for grouping more statements together. + + NOTE: nested transactions are not yet implemented in KexiDB API. + */ + bool autoCommit() const; + + /*! Changes auto commit option. This does not affect currently started transactions. + This option can be changed even when connection is not established. + \sa autoCommit() */ + bool setAutoCommit(bool on); + + /*! driver-specific string escaping */ +//js: MOVED TO Driver virtual QString escapeString(const QString& str) const = 0; +// virtual QCString escapeString(const QCString& str) const = 0; + + /*! Prepares SELECT query described by raw \a statement. + \return opened cursor created for results of this query + or NULL if there was any error. Cursor can have optionally applied \a cursor_options + (one of more selected from KexiDB::Cursor::Options). + Preparation means that returned cursor is created but not opened. + Open this when you would like to do it with Cursor::open(). + + Note for driver developers: you should initialize cursor engine-specific + resources and return Cursor subclass' object + (passing \a statement and \a cursor_options to it's constructor). + */ + virtual Cursor* prepareQuery( const QString& statement, uint cursor_options = 0) = 0; + + /*! \overload prepareQuery( const QString& statement = QString::null, uint cursor_options = 0) + Prepares query described by \a query schema. \a params are values of parameters that + will be inserted into places marked with [] before execution of the query. + + Note for driver developers: you should initialize cursor engine-specific + resources and return Cursor subclass' object + (passing \a query and \a cursor_options to it's constructor). + Kexi SQL and driver-specific escaping is performed on table names. + */ + Cursor* prepareQuery( QuerySchema& query, const QValueList<QVariant>& params, + uint cursor_options = 0 ); + + /*! \overload prepareQuery( QuerySchema& query, const QValueList<QVariant>& params, + uint cursor_options = 0 ) + Prepares query described by \a query schema without parameters. + */ + virtual Cursor* prepareQuery( QuerySchema& query, uint cursor_options = 0 ) = 0; + + /*! \overload prepareQuery( const QString& statement = QString::null, uint cursor_options = 0) + Statement is build from data provided by \a table schema, + it is like "select * from table_name".*/ + Cursor* prepareQuery( TableSchema& table, uint cursor_options = 0); + + /*! Executes SELECT query described by \a statement. + \return opened cursor created for results of this query + or NULL if there was any error on the cursor creation or opening. + Cursor can have optionally applied \a cursor_options + (one of more selected from KexiDB::Cursor::Options). + Identifiers in \a statement that are the same as keywords in Kexi + SQL or the backend's SQL need to have been escaped. + */ + Cursor* executeQuery( const QString& statement, uint cursor_options = 0 ); + + /*! \overload executeQuery( const QString& statement, uint cursor_options = 0 ) + \a params are values of parameters that + will be inserted into places marked with [] before execution of the query. + + Statement is build from data provided by \a query schema. + Kexi SQL and driver-specific escaping is performed on table names. */ + Cursor* executeQuery( QuerySchema& query, const QValueList<QVariant>& params, + uint cursor_options = 0 ); + + /*! \overload executeQuery( QuerySchema& query, const QValueList<QVariant>& params, + uint cursor_options = 0 ) */ + Cursor* executeQuery( QuerySchema& query, uint cursor_options = 0 ); + + /*! \overload executeQuery( const QString& statement, uint cursor_options = 0 ) + Executes query described by \a query schema without parameters. + Statement is build from data provided by \a table schema, + it is like "select * from table_name".*/ + Cursor* executeQuery( TableSchema& table, uint cursor_options = 0 ); + + /*! Deletes cursor \a cursor previously created by functions like executeQuery() + for this connection. + There is an attempt to close the cursor with Cursor::close() if it was opened. + Anyway, at last cursor is deleted. + \return true if cursor is properly closed before deletion. */ + bool deleteCursor(Cursor *cursor); + + /*! \return schema of a table pointed by \a tableId, retrieved from currently + used database. The schema is cached inside connection, + so retrieval is performed only once, on demand. */ + TableSchema* tableSchema( int tableId ); + + /*! \return schema of a table pointed by \a tableName, retrieved from currently + used database. KexiDB system table schema can be also retrieved. + \sa tableSchema( int tableId ) */ + TableSchema* tableSchema( const QString& tableName ); + + /*! \return schema of a query pointed by \a queryId, retrieved from currently + used database. The schema is cached inside connection, + so retrieval is performed only once, on demand. */ + QuerySchema* querySchema( int queryId ); + + /*! \return schema of a query pointed by \a queryName, retrieved from currently + used database. \sa querySchema( int queryId ) */ + QuerySchema* querySchema( const QString& queryName ); + + /*! Sets \a queryName query obsolete by moving it out of the query sets, so it will not be + accessible by querySchema( const QString& queryName ). The existing query object is not + destroyed, to avoid problems when it's referenced. In this case, + a new query schema will be retrieved directly from the backend. + + For now it's used in KexiQueryDesignerGuiEditor::storeLayout(). + This solves the problem when user has changed a query schema but already form still uses + previously instantiated query schema. + \return true if there is such query. Otherwise the method does nothing. */ + bool setQuerySchemaObsolete( const QString& queryName ); + +//js: MOVED TO Driver QString valueToSQL( const Field::Type ftype, const QVariant& v ) const; +// QString valueToSQL( const Field *field, const QVariant& v ) const; + + /*! Executes \a sql query and stores first record's data inside \a data. + This is convenient method when we need only first record from query result, + or when we know that query result has only one record. + If \a addLimitTo1 is true (the default), adds a LIMIT clause to the query, + so \a sql should not include one already. + \return true if query was successfully executed and first record has been found, + false on data retrieving failure, and cancelled if there's no single record available. */ + tristate querySingleRecord(const QString& sql, RowData &data, bool addLimitTo1 = true); + + /*! Like tristate querySingleRecord(const QString& sql, RowData &data) + but uses QuerySchema object. + If \a addLimitTo1 is true (the default), adds a LIMIT clause to the query. */ + tristate querySingleRecord(QuerySchema& query, RowData &data, bool addLimitTo1 = true); + + /*! Executes \a sql query and stores first record's field's (number \a column) string value + inside \a value. For efficiency it's recommended that a query defined by \a sql + should have just one field (SELECT one_field FROM ....). + If \a addLimitTo1 is true (the default), adds a LIMIT clause to the query, + so \a sql should not include one already. + \return true if query was successfully executed and first record has been found, + false on data retrieving failure, and cancelled if there's no single record available. + \sa queryStringList() */ + tristate querySingleString(const QString& sql, QString &value, uint column = 0, + bool addLimitTo1 = true); + + /*! Convenience function: executes \a sql query and stores first + record's field's (number \a column) value inside \a number. \sa querySingleString(). + Note: "LIMIT 1" is appended to \a sql statement if \a addLimitTo1 is true (the default). + \return true if query was successfully executed and first record has been found, + false on data retrieving failure, and cancelled if there's no single record available. */ + tristate querySingleNumber(const QString& sql, int &number, uint column = 0, + bool addLimitTo1 = true); + + /*! Executes \a sql query and stores Nth field's string value of every record + inside \a list, where N is equal to \a column. The list is initially cleared. + For efficiency it's recommended that a query defined by \a sql + should have just one field (SELECT one_field FROM ....). + \return true if all values were fetched successfuly, + false on data retrieving failure. Returning empty list can be still a valid result. + On errors, the list is not cleared, it may contain a few retrieved values. */ + bool queryStringList(const QString& sql, QStringList& list, uint column = 0); + + /*! \return true if there is at least one record returned in \a sql query. + Does not fetch any records. \a success will be set to false + on query execution errors (true otherwise), so you can see a difference between + "no results" and "query execution error" states. + Note: real executed query is: "SELECT 1 FROM (\a sql) LIMIT 1" + if \a addLimitTo1 is true (the default). */ + bool resultExists(const QString& sql, bool &success, bool addLimitTo1 = true); + + /*! \return true if there is at least one record in \a table. */ + bool isEmpty( TableSchema& table, bool &success ); + +//! @todo perhaps use Q_ULLONG here? + /*! \return number of records in \a sql query. + Does not fetch any records. -1 is returned on query execution errors (>0 otherwise). + Note: real executed query is: "SELECT COUNT() FROM (\a sql) LIMIT 1" + (using querySingleNumber()) */ + int resultCount(const QString& sql); + + //PROTOTYPE: + #define A , const QVariant& + #define H_INS_REC(args) bool insertRecord(TableSchema &tableSchema args) + #define H_INS_REC_ALL \ + H_INS_REC(A); \ + H_INS_REC(A A); \ + H_INS_REC(A A A); \ + H_INS_REC(A A A A); \ + H_INS_REC(A A A A A); \ + H_INS_REC(A A A A A A); \ + H_INS_REC(A A A A A A A); \ + H_INS_REC(A A A A A A A A) + H_INS_REC_ALL; + + #undef H_INS_REC + #define H_INS_REC(args) bool insertRecord(FieldList& fields args) + + H_INS_REC_ALL; + #undef H_INS_REC_ALL + #undef H_INS_REC + #undef A + + bool insertRecord(TableSchema &tableSchema, QValueList<QVariant>& values); + + bool insertRecord(FieldList& fields, QValueList<QVariant>& values); + + /*! Creates table defined by \a tableSchema. + Schema information is also added into kexi system tables, for later reuse. + \return true on success - \a tableSchema object is then + inserted to Connection structures - it is owned by Connection object now, + so you shouldn't destroy the tableSchema object by hand + (or declare it as local-scope variable). + + If \a replaceExisting is false (the default) and table with the same name + (as tableSchema->name()) exists, false is returned. + If \a replaceExisting is true, a table schema with the same name (if exists) + is overwritten, then a new table schema gets the same identifier + as existing table schema's identifier. + + Note that on error: + - \a tableSchema is not inserted into Connection's structures, + so you are still owner of this object + - existing table schema object is not destroyed (i.e. it is still available + e.g. using Connection::tableSchema(const QString& ), even if the table + was physically dropped. + */ + bool createTable( TableSchema* tableSchema, bool replaceExisting = false ); + + /*! Drops a table defined by \a tableSchema (both table object as well as physically). + If true is returned, schema information \a tableSchema is destoyed + (because it's owned), so don't keep this anymore! + No error is raised if the table does not exist physically + - its schema is removed even in this case. + */ +//! @todo (js): update any structure (e.g. query) that depend on this table! + tristate dropTable( TableSchema* tableSchema ); + + /*! It is a convenience function, does exactly the same as + bool dropTable( KexiDB::TableSchema* tableSchema ) */ + tristate dropTable( const QString& table ); + + /*! Alters \a tableSchema using \a newTableSchema in memory and on the db backend. + \return true on success, cancelled if altering was cancelled. */ +//! @todo (js): implement real altering +//! @todo (js): update any structure (e.g. query) that depend on this table! + tristate alterTable( TableSchema& tableSchema, TableSchema& newTableSchema); + + /*! Alters name of table described by \a tableSchema to \a newName. + If \a replace is true, destination table is completely dropped and replaced + by \a tableSchema, if present. In this case, identifier of + \a tableSchema becomes equal to the dropped table's id, what can be useful + if \a tableSchema was created with a temporary name and ID (used in AlterTableHandler). + + If \a replace is false (the default) and destination table is present + -- false is returned and ERR_OBJECT_EXISTS error is set. + The schema of \a tableSchema is updated on success. + \return true on success. */ + bool alterTableName(TableSchema& tableSchema, const QString& newName, bool replace = false); + + /*! Drops a query defined by \a querySchema. + If true is returned, schema information \a querySchema is destoyed + (because it's owned), so don't keep this anymore! + */ + bool dropQuery( QuerySchema* querySchema ); + + /*! It is a convenience function, does exactly the same as + bool dropQuery( KexiDB::QuerySchema* querySchema ) */ + bool dropQuery( const QString& query ); + + /*! Removes information about object with \a objId + from internal "kexi__object" and "kexi__objectdata" tables. + \return true on success. */ + bool removeObject( uint objId ); + + /*! \return first field from \a fieldlist that has system name, + null if there are no such field. + For checking Driver::isSystemFieldName() is used, so this check can + be driver-dependent. */ + Field* findSystemFieldName(FieldList *fieldlist); + + /*! \return name of any (e.g. first found) database for this connection. + This method does not close or open this connection. The method can be used + (it is also internally used, e.g. for database dropping) when we need + a database name before we can connect and execute any SQL statement + (e.g. DROP DATABASE). + + The method can return nul lstring, but in this situation no automatic (implicit) + connections could be made, what is useful by e.g. dropDatabase(). + + Note for driver developers: return here a name of database which you are sure + is existing. + Default implementation returns: + - value that previously had been set using setAvailableDatabaseName() for + this connection, if it is not empty + - else (2nd priority): value of DriverBehaviour::ALWAYS_AVAILABLE_DATABASE_NAME + if it is not empty. + + See decription of DriverBehaviour::ALWAYS_AVAILABLE_DATABASE_NAME member. + You may want to reimplement this method only when you need to depend on + this connection specifics + (e.g. you need to check something remotely). + */ + virtual QString anyAvailableDatabaseName(); + + /*! Sets \a dbName as name of a database that can be accessible. + This is option that e.g. application that make use of KexiDB library can set + to tune connection's behaviour when it needs to temporary connect to any database + in the server to do some work. + You can pass empty dbName - then anyAvailableDatabaseName() will try return + DriverBehaviour::ALWAYS_AVAILABLE_DATABASE_NAME (the default) value + instead of the one previously set with setAvailableDatabaseName(). + + \sa anyAvailableDatabaseName() + */ + void setAvailableDatabaseName(const QString& dbName); + + /*! Because some engines need to have opened any database before + executing administrative sql statements like "create database" or "drop database", + this method is used to use appropriate, existing database for this connection. + For file-based db drivers this always return true and does not set tmpdbName + to any value. For other db drivers: this sets tmpdbName to db name computed + using anyAvailableDatabaseName(), and if the name computed is empty, false + is returned; if it is not empty, useDatabase() is called. + False is returned also when useDatabase() fails. + You can call this method from your application's level if you really want to perform + tasks that require any used database. In such a case don't forget + to closeDatabase() if returned tmpdbName is not empty. + + Note: This method has nothing to do with creating or using temporary databases + in such meaning that these database are not persistent + */ + bool useTemporaryDatabaseIfNeeded(QString &tmpdbName); + + /*! \return autoincrement field's \a aiFieldName value + of last inserted record. This refers \a tableName table. + + Simply, method internally fetches last inserted record and returns selected + field's value. Requirements: field must be of integer type, there must be a + record inserted in current database session (whatever this means). + On error (Q_ULLONG)-1 is returned. + Last inserted record is identified by magical row identifier, usually called + ROWID (PostgreSQL has it as well as SQLite; + see DriverBehaviour::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE). + ROWID's value will be assigned back to \a ROWID if this pointer is not null. + */ + Q_ULLONG lastInsertedAutoIncValue(const QString& aiFieldName, const QString& tableName, + Q_ULLONG* ROWID = 0); + + /*! \overload int lastInsertedAutoIncValue(const QString&, const QString&, Q_ULLONG*) + */ + Q_ULLONG lastInsertedAutoIncValue(const QString& aiFieldName, + const TableSchema& table, Q_ULLONG* ROWID = 0); + + /*! Executes query \a statement, but without returning resulting + rows (used mostly for functional queries). + Only use this method if you really need. */ + bool executeSQL( const QString& statement ); + + //! @short options used in selectStatement() + class KEXI_DB_EXPORT SelectStatementOptions + { + public: + SelectStatementOptions(); + ~SelectStatementOptions(); + + //! A mode for escaping identifier, Driver::EscapeDriver|Driver::EscapeAsNecessary by default + int identifierEscaping; + + //! True if ROWID should be also retrieved. False by default. + bool alsoRetrieveROWID : 1; + + /*! True if relations (LEFT OUTER JOIN) for visible lookup columns should be added. + True by default. This is set to false when user-visible statement is generated + e.g. for the Query Designer. */ + bool addVisibleLookupColumns : 1; + }; + + /*! \return "SELECT ..." statement's string needed for executing query + defined by \a querySchema and \a params. */ + QString selectStatement( QuerySchema& querySchema, + const QValueList<QVariant>& params, + const SelectStatementOptions& options = SelectStatementOptions() ) const; + + /*! \overload QString selectStatement( QuerySchema& querySchema, + QValueList<QVariant> params = QValueList<QVariant>(), + const SelectStatementOptions& options = SelectStatementOptions() ) const; + \return "SELECT ..." statement's string needed for executing query + defined by \a querySchema. */ + inline QString selectStatement( QuerySchema& querySchema, + const SelectStatementOptions& options = SelectStatementOptions() ) const + { + return selectStatement(querySchema, QValueList<QVariant>(), options); + } + + /*! Stores object's schema data (id, name, caption, help text) + described by \a sdata on the backend. + If \a newObject is true, new entry is created, + and (when sdata.id() was <=0), new, unique object identifier + is obtained and assigned to \a sdata (see SchemaData::id()). + + If \a newObject is false, it's expected that entry on the + backend already exists, so it's updated (changes to identifier are not allowed). + \return true on success. */ + bool storeObjectSchemaData( SchemaData &sdata, bool newObject ); + + /*! Added for convenience. + \sa setupObjectSchemaData( const KexiDB::RowData &data, SchemaData &sdata ). + \return true on success, false on failure and cancelled when such object couldn't */ + tristate loadObjectSchemaData( int objectID, SchemaData &sdata ); + + /*! Finds object schema data for object of type \a objectType and name \a objectName. + If the object is found, resulted schema is stored in \a sdata and true is returned, + otherwise false is returned. */ + tristate loadObjectSchemaData( int objectType, const QString& objectName, SchemaData &sdata ); + + /*! Loads (potentially large) data block (e.g. xml form's representation), referenced by objectID + and puts it to \a dataString. The can be block indexed with optional \a dataID. + \return true on success, false on failure and cancelled when there is no such data block + \sa storeDataBlock(). */ + tristate loadDataBlock( int objectID, QString &dataString, const QString& dataID ); + + /*! Stores (potentially large) data block \a dataString (e.g. xml form's representation), + referenced by objectID. Block will be stored in "kexi__objectdata" table and + an optional \a dataID identifier. + If there is already such record in the table, it's simply overwritten. + \return true on success + \sa loadDataBlock(). */ + bool storeDataBlock( int objectID, const QString &dataString, const QString& dataID = QString::null ); + + /*! Removes (potentially large) string data (e.g. xml form's representation), + referenced by objectID, and pointed by optional \a dataID. + \return true on success. Does not fail if the block does not exist. + Note that if \a dataID is not specified, all data blocks for this dialog will be removed. + \sa loadDataBlock() storeDataBlock(). */ + bool removeDataBlock( int objectID, const QString& dataID = QString::null); + + class KEXI_DB_EXPORT TableSchemaChangeListenerInterface + { + public: + TableSchemaChangeListenerInterface() {} + virtual ~TableSchemaChangeListenerInterface() {} + /*! Closes listening object so it will be deleted and thus no longer use + a conflicting table schema. */ + virtual tristate closeListener() = 0; + + /*! i18n'd string that can be displayed for user to inform about + e.g. conflicting listeners. */ + QString listenerInfoString; + }; +//TMP// TODO: will be more generic + /** Register \a listener for receiving (listening) information about changes + in TableSchema object. Changes could be: altering and removing. */ + void registerForTableSchemaChanges(TableSchemaChangeListenerInterface& listener, + TableSchema& schema); + + void unregisterForTableSchemaChanges(TableSchemaChangeListenerInterface& listener, + TableSchema &schema); + + void unregisterForTablesSchemaChanges(TableSchemaChangeListenerInterface& listener); + + QPtrList<Connection::TableSchemaChangeListenerInterface>* + tableSchemaChangeListeners(TableSchema& tableSchema) const; + + tristate closeAllTableSchemaChangeListeners(TableSchema& tableSchema); + + /*! @internal Removes \a tableSchema from internal structures and + destroys it. Does not make any change at the backend. */ + void removeTableSchemaInternal(KexiDB::TableSchema *tableSchema); + + /*! @internal. Inserts internal table to Connection's structures, so it can be found by name. + This method is used for example in KexiProject to insert information about "kexi__blobs" + table schema. Use createTable() to physically create table. After createTable() + calling insertInternalTableSchema() is not required. + Also used internally by newKexiDBSystemTableSchema(const QString& tsname) */ + void insertInternalTableSchema(TableSchema *tableSchema); + +//! @todo move this somewhere to low level class (MIGRATION?) + /*! LOW LEVEL METHOD. For reimplemenation: returns true if table + with name \a tableName exists in the database. + \return false if it does not exist or error occurred. + The lookup is case insensitive. */ + virtual bool drv_containsTable( const QString &tableName ) = 0; + + /*! Creates table using \a tableSchema information. + \return true on success. Default implementation + builds a statement using createTableStatement() and calls drv_executeSQL() + Note for driver developers: reimplement this only if you want do to + this in other way. + + Moved to public for KexiMigrate. + @todo fix this after refactoring + */ + virtual bool drv_createTable( const TableSchema& tableSchema ); + + /*! Alters table's described \a tableSchema name to \a newName. + This is the default implementation, using "ALTER TABLE <oldname> RENAME TO <newname>", + what's supported by SQLite >= 3.2, PostgreSQL, MySQL. + Backends lacking ALTER TABLE, for example SQLite2, reimplement this with by an inefficient + data copying to a new table. In any case, renaming is performed at the backend. + It's good idea to keep the operation within a transaction. + \return true on success. + + Moved to public for KexiProject. + @todo fix this after refactoring + */ + virtual bool drv_alterTableName(TableSchema& tableSchema, const QString& newName); + + /*! Physically drops table named with \a name. + Default impelmentation executes "DROP TABLE.." command, + so you rarely want to change this. + + Moved to public for KexiMigrate + @todo fix this after refatoring + */ + virtual bool drv_dropTable( const QString& name ); + + /*! Prepare a SQL statement and return a \a PreparedStatement instance. */ + virtual PreparedStatement::Ptr prepareStatement(PreparedStatement::StatementType type, + FieldList& fields) = 0; + + bool isInternalTableSchema(const QString& tableName); + + /*! Setups schema data for object that owns sdata (e.g. table, query) + using \a cursor opened on 'kexi__objects' table, pointing to a record + corresponding to given object. + + Moved to public for KexiMigrate + @todo fix this after refatoring + */ + bool setupObjectSchemaData( const RowData &data, SchemaData &sdata ); + + /*! \return a new field table schema for a table retrieved from \a data. + Used internally by tableSchema(). + + Moved to public for KexiMigrate + @todo fix this after refatoring + */ + KexiDB::Field* setupField( const RowData &data ); + + protected: + /*! Used by Driver */ + Connection( Driver *driver, ConnectionData &conn_data ); + + /*! Method to be called form Connection's subclass destructor. + \sa ~Connection() */ + void destroy(); + + /*! @internal drops table \a tableSchema physically, but destroys + \a tableSchema object only if \a alsoRemoveSchema is true. + Used (alsoRemoveSchema==false) on table altering: + if recreating table can failed we're giving up and keeping + the original table schema (even if it is no longer points to any real data). */ + tristate dropTable( KexiDB::TableSchema* tableSchema, bool alsoRemoveSchema); + + /*! For reimplemenation: connects to database. \a version should be set to real + server's version. + \return true on success. */ + virtual bool drv_connect(KexiDB::ServerVersionInfo& version) = 0; + + /*! For reimplemenation: disconnects database + \return true on success. */ + virtual bool drv_disconnect() = 0; + + /*! Executes query \a statement, but without returning resulting + rows (used mostly for functional queries). + Only use this method if you really need. */ + virtual bool drv_executeSQL( const QString& statement ) = 0; + + /*! For reimplemenation: loads list of databases' names available for this connection + and adds these names to \a list. If your server is not able to offer such a list, + consider reimplementing drv_databaseExists() instead. + The method should return true only if there was no error on getting database names + list from the server. + Default implementation puts empty list into \a list and returns true. */ + virtual bool drv_getDatabasesList( QStringList &list ); + +//! @todo move this somewhere to low level class (MIGRATION?) + /*! LOW LEVEL METHOD. For reimplemenation: loads low-level list of table names + available for this connection. The names are in lower case. + The method should return true only if there was no error on getting database names + list from the server. */ + virtual bool drv_getTablesList( QStringList &list ) = 0; + + /*! For optional reimplemenation: asks server if database \a dbName exists. + This method is used internally in databaseExists(). The default implementation + calls databaseNames and checks if that list contains \a dbName. If you need to + ask the server specifically if a database exists, eg. if you can't retrieve a list + of all available database names, please reimplement this method and do all + needed checks. + + See databaseExists() description for details about ignoreErrors argument. + You should use this appropriately in your implementation. + + Note: This method should also work if there is already database used (with useDatabase()); + in this situation no changes should be made in current database selection. */ + virtual bool drv_databaseExists( const QString &dbName, bool ignoreErrors = true ); + + /*! For reimplemenation: creates new database using connection */ + virtual bool drv_createDatabase( const QString &dbName = QString::null ) = 0; + + /*! For reimplemenation: opens existing database using connection + \return true on success, false on failure and cancelled if user has cancelled this action. */ + virtual bool drv_useDatabase( const QString &dbName = QString::null, bool *cancelled = 0, + MessageHandler* msgHandler = 0 ) = 0; + + /*! For reimplemenation: closes previously opened database + using connection. */ + virtual bool drv_closeDatabase() = 0; + + /*! \return true if internal driver's structure is still in opened/connected + state and database is used. + Note for driver developers: Put here every test that you can do using your + internal engine's database API, + eg (a bit schematic): my_connection_struct->isConnected()==true. + Do not check things like Connection::isDatabaseUsed() here or other things + that "KexiDB already knows" at its level. + If you cannot test anything, just leave default implementation (that returns true). + + Result of this method is used as an addtional chance to check for isDatabaseUsed(). + Do not call this method from your driver's code, it should be used at KexiDB + level only. + */ + virtual bool drv_isDatabaseUsed() const { return true; } + + /*! For reimplemenation: drops database from the server + using connection. After drop, database shouldn't be accessible + anymore. */ + virtual bool drv_dropDatabase( const QString &dbName = QString::null ) = 0; + + /*! \return "CREATE TABLE ..." statement string needed for \a tableSchema + creation in the database. + + Note: The statement string can be specific for this connection's driver database, + and thus not reusable in general. + */ + QString createTableStatement( const TableSchema& tableSchema ) const; + + + /*! \return "SELECT ..." statement's string needed for executing query + defined by "select * from table_name" where <i>table_name</i> is \a tableSchema's name. + This method's variant can be useful when there is no appropriate QuerySchema defined. + + Note: The statement string can be specific for this connection's driver database, + and thus not reusable in general. + */ + QString selectStatement( TableSchema& tableSchema, + const SelectStatementOptions& options = SelectStatementOptions() ) const; + + /*! + Creates table named by \a tableSchemaName. Schema object must be on + schema tables' list before calling this method (otherwise false if returned). + Just uses drv_createTable( const KexiDB::TableSchema& tableSchema ). + Used internally, e.g. in createDatabase(). + \return true on success + */ + virtual bool drv_createTable( const QString& tableSchemaName ); + +// /*! Executes query \a statement and returns resulting rows +// (used mostly for SELECT query). */ +// virtual bool drv_executeQuery( const QString& statement ) = 0; + + /*! \return unique identifier of last inserted row. + Typically this is just primary key value. + This identifier could be reused when we want to reference + just inserted row. + Note for driver developers: contact js (at) iidea.pl + if your engine do not offers this information. */ + virtual Q_ULLONG drv_lastInsertRowID() = 0; + + /*! Note for driver developers: begins new transaction + and returns handle to it. Default implementation just + executes "BEGIN" sql statement and returns just empty data (TransactionData object). + + Drivers that do not support transactions (see Driver::features()) + do never call this method. + Reimplement this method if you need to do something more + (e.g. if you driver will support multiple transactions per connection). + Make subclass of TransactionData (declared in transaction.h) + and return object of this subclass. + You should return NULL if any error occurred. + Do not check anything in connection (isConnected(), etc.) - all is already done. + */ + virtual TransactionData* drv_beginTransaction(); + + /*! Note for driver developers: begins new transaction + and returns handle to it. Default implementation just + executes "COMMIT" sql statement and returns true on success. + + \sa drv_beginTransaction() + */ + virtual bool drv_commitTransaction(TransactionData* trans); + + /*! Note for driver developers: begins new transaction + and returns handle to it. Default implementation just + executes "ROLLBACK" sql statement and returns true on success. + + \sa drv_beginTransaction() + */ + virtual bool drv_rollbackTransaction(TransactionData* trans); + + /*! Changes autocommiting option for established connection. + \return true on success. + + Note for driver developers: reimplement this only if your engine + allows to set special auto commit option (like "SET AUTOCOMMIT=.." in MySQL). + If not, auto commit behaviour will be simulated if at least single + transactions per connection are supported by the engine. + Do not set any internal flags for autocommiting -- it is already done inside + setAutoCommit(). + + Default implementation does nothing with connection, just returns true. + + \sa drv_beginTransaction(), autoCommit(), setAutoCommit() + */ + virtual bool drv_setAutoCommit(bool on); + + /*! Internal, for handling autocommited transactions: + begins transaction if one is supported. + \return true if new transaction started + successfully or no transactions are supported at all by the driver + or if autocommit option is turned off. + A handle to a newly created transaction (or null on error) is passed + to \a tg parameter. + + Special case when used database driver has only single transaction support + (Driver::SingleTransactions): + and there is already transaction started, it is committed before + starting a new one, but only if this transaction has been started inside Connection object. + (i.e. by beginAutoCommitTransaction()). Otherwise, a new transaction will not be started, + but true will be returned immediately. + */ + bool beginAutoCommitTransaction(TransactionGuard& tg); + + /*! Internal, for handling autocommited transactions: + Commits transaction prevoiusly started with beginAutoCommitTransaction(). + \return true on success or when no transactions are supported + at all by the driver. + + Special case when used database driver has only single transaction support + (Driver::SingleTransactions): if \a trans has been started outside Connection object + (i.e. not by beginAutoCommitTransaction()), the transaction will not be committed. + */ + bool commitAutoCommitTransaction(const Transaction& trans); + + /*! Internal, for handling autocommited transactions: + Rollbacks transaction prevoiusly started with beginAutoCommitTransaction(). + \return true on success or when no transactions are supported + at all by the driver. + + Special case when used database driver has only single transaction support + (Driver::SingleTransactions): \a trans will not be rolled back + if it has been started outside this Connection object. + */ + bool rollbackAutoCommitTransaction(const Transaction& trans); + + /*! Creates cursor data and initializes cursor + using \a statement for later data retrieval. */ +// virtual CursorData* drv_createCursor( const QString& statement ) = 0; + /*! Closes and deletes cursor data. */ +// virtual bool drv_deleteCursor( CursorData *data ) = 0; + + /*! Helper: checks if connection is established; + if not: error message is set up and false returned */ + bool checkConnected(); + + /*! Helper: checks both if connection is established and database any is used; + if not: error message is set up and false returned */ + bool checkIsDatabaseUsed(); + + /*! \return a full table schema for a table retrieved using 'kexi__*' system tables. + Used internally by tableSchema() methods. */ + TableSchema* setupTableSchema( const RowData &data ); + + /*! \return a full query schema for a query using 'kexi__*' system tables. + Used internally by querySchema() methods. */ + QuerySchema* setupQuerySchema( const RowData &data ); + + /*! Update a row. */ + bool updateRow(QuerySchema &query, RowData& data, RowEditBuffer& buf, bool useROWID = false); + /*! Insert a new row. */ + bool insertRow(QuerySchema &query, RowData& data, RowEditBuffer& buf, bool getROWID = false); + /*! Delete an existing row. */ + bool deleteRow(QuerySchema &query, RowData& data, bool useROWID = false); + /*! Delete all existing rows. */ + bool deleteAllRows(QuerySchema &query); + + /*! Allocates all needed table KexiDB system objects for kexi__* KexiDB liblary's + system tables schema. + These objects are used internally in this connection + and are added to list of tables (by name, + not by id because these have no ids). + */ + bool setupKexiDBSystemSchema(); + + /*! used internally by setupKexiDBSystemSchema(): + Allocates single table KexiDB system object named \a tsname + and adds this to list of such objects (for later removal on closeDatabase()). + */ + TableSchema* newKexiDBSystemTableSchema(const QString& tsname); + + //! Identifier escaping function in the associated Driver. + /*! Calls the identifier escaping function in the associated Driver to + escape table and column names. This should be used when explicitly + constructing SQL strings (e.g. "FROM " + escapeIdentifier(tablename)). + It should not be used for other functions (e.g. don't do + useDatabase(escapeIdentifier(database))), because the identifier will + be escaped when the called function generates, for example, "USE " + + escapeIdentifier(database). + + For efficiency, kexi__* system tables and columns therein are not escaped + - we assume these are valid identifiers for all drivers. + */ + inline QString escapeIdentifier(const QString& id, + int escaping = Driver::EscapeDriver|Driver::EscapeAsNecessary ) const { + return m_driver->escapeIdentifier(id, escaping); + } + + /*! Called by TableSchema -- signals destruction to Connection object + To avoid having deleted table object on its list. */ + void removeMe(TableSchema *ts); + + /*! @internal + \return true if the cursor \a cursor contains column \a column, + else, sets appropriate error with a message and returns false. */ + bool checkIfColumnExists(Cursor *cursor, uint column); + + /*! @internal used by querySingleRecord() methods. + Note: "LIMIT 1" is appended to \a sql statement if \a addLimitTo1 is true (the default). */ + tristate querySingleRecordInternal(RowData &data, const QString* sql, + QuerySchema* query, bool addLimitTo1 = true); + + /*! @internal used by Driver::createConnection(). + Only works if connection is not yet established. */ + void setReadOnly(bool set); + + /*! Loads extended schema information for table \a tableSchema, + if present (see ExtendedTableSchemaInformation in Kexi Wiki). + \return true on success */ + bool loadExtendedTableSchemaData(TableSchema& tableSchema); + + /*! Stores extended schema information for table \a tableSchema, + (see ExtendedTableSchemaInformation in Kexi Wiki). + The action is performed within the current transaction, + so it's up to you to commit. + Used, e.g. by createTable(), within its transaction. + \return true on success */ + bool storeExtendedTableSchemaData(TableSchema& tableSchema); + + /*! @internal + Stores main field's schema information for field \a field. + Used in table altering code when information in kexi__fields has to be updated. + \return true on success and false on failure. */ + bool storeMainFieldSchema(Field *field); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! This is a part of alter table interface implementing lower-level operations + used to perform table schema altering. Used by AlterTableHandler. + + Changes value of field property. + \return true on success, false on failure, cancelled if the action has been cancelled. + + Note for driver developers: implement this if the driver has to support the altering. */ + virtual tristate drv_changeFieldProperty(TableSchema &table, Field& field, + const QString& propertyName, const QVariant& value) { + Q_UNUSED(table); Q_UNUSED(field); Q_UNUSED(propertyName); Q_UNUSED(value); + return cancelled; } + + //! cursors created for this connection + QPtrDict<KexiDB::Cursor> m_cursors; + + private: + ConnectionPrivate* d; //!< @internal d-pointer class. + Driver* const m_driver; //!< The driver this \a Connection instance uses. + bool m_destructor_started : 1; //!< helper: true if destructor is started. + + friend class KexiDB::Driver; + friend class KexiDB::Cursor; + friend class KexiDB::TableSchema; //!< for removeMe() + friend class KexiDB::DatabaseProperties; //!< for setError() + friend class ConnectionPrivate; + friend class KexiDB::AlterTableHandler; +}; + +} //namespace KexiDB + +#endif + diff --git a/kexi/kexidb/connection_p.h b/kexi/kexidb/connection_p.h new file mode 100644 index 00000000..f3b80fce --- /dev/null +++ b/kexi/kexidb/connection_p.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_CONNECTION_P_H +#define KEXIDB_CONNECTION_P_H + +#include "connection.h" + +namespace KexiDB { + +//! Interface for connection's internals, implemented within drivers +class KEXI_DB_EXPORT ConnectionInternal +{ + public: + ConnectionInternal(Connection *conn); + virtual ~ConnectionInternal(); + virtual void storeResult() = 0; + + Connection* connection; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/connectiondata.cpp b/kexi/kexidb/connectiondata.cpp new file mode 100644 index 00000000..a74237cc --- /dev/null +++ b/kexi/kexidb/connectiondata.cpp @@ -0,0 +1,114 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/connectiondata.h> + +#include <kexidb/drivermanager.h> + +#include <qfileinfo.h> +#include <qdir.h> + +#include <klocale.h> + +using namespace KexiDB; + +namespace KexiDB { +//! @internal +class ConnectionData::Private { +public: + Private() { + dummy=false; + } + ~Private() {} + bool dummy; +}; +} + +/*================================================================*/ + +ConnectionDataBase::ConnectionDataBase() + : id(-1), port(0), useLocalSocketFile(true), savePassword(false) +{ +} + +/*================================================================*/ + +ConnectionData::ConnectionData() +: QObject() +, ConnectionDataBase() +, formatVersion(0) +, priv(new ConnectionData::Private()) +{ +} + +ConnectionData::ConnectionData(const ConnectionData& cd) +: QObject() +, ConnectionDataBase() +, priv(0) +{ + static_cast<ConnectionData&>(*this) = static_cast<const ConnectionData&>(cd);//copy data members +} + +ConnectionData::~ConnectionData() +{ + delete priv; + priv = 0; +} + +ConnectionData& ConnectionData::operator=(const ConnectionData& cd) +{ + if (this != &cd) { + delete priv; //this is old + static_cast<ConnectionDataBase&>(*this) = static_cast<const ConnectionDataBase&>(cd);//copy data members + priv = new ConnectionData::Private(); + *priv = *cd.priv; + } + return *this; +} + +void ConnectionData::setFileName( const QString& fn ) +{ + QFileInfo file(fn); + if (!fn.isEmpty() && m_fileName != file.absFilePath()) { + m_fileName = QDir::convertSeparators(file.absFilePath()); + m_dbPath = QDir::convertSeparators(file.dirPath(true)); + m_dbFileName = file.fileName(); + } +} + +QString ConnectionData::serverInfoString(bool addUser) const +{ + const QString& i18nFile = i18n("file"); + + if (!m_dbFileName.isEmpty()) + return i18nFile+": "+(m_dbPath.isEmpty() ? "" : m_dbPath + + QDir::separator()) + m_dbFileName; + + DriverManager man; + if (!driverName.isEmpty()) { + Driver::Info info = man.driverInfo(driverName); + if (!info.name.isEmpty() && info.fileBased) + return QString("<")+i18nFile+">"; + } + + return ( (userName.isEmpty() || !addUser) ? QString("") : (userName+"@")) + + (hostName.isEmpty() ? QString("localhost") : hostName) + + (port!=0 ? (QString(":")+QString::number(port)) : QString::null); +} + diff --git a/kexi/kexidb/connectiondata.h b/kexi/kexidb/connectiondata.h new file mode 100644 index 00000000..cd3c1537 --- /dev/null +++ b/kexi/kexidb/connectiondata.h @@ -0,0 +1,239 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_CONNECTION_DATA_H +#define KEXIDB_CONNECTION_DATA_H + +#include <kexidb/kexidb_export.h> + +#include <qobject.h> +#include <qstring.h> +#include <qptrlist.h> + +namespace KexiDB { + +/*! ConnectionDataBase is a helper class for ConnectionData. It + is not intended to be instantiated explicitly. Instead, use the + ConnectionData class. */ +/*! @internal + Used by ConnectionData. + It is easier to internally operate on non-QObject-derived object, + e.g.: to copy data members in ConnectionData ctor. */ +class ConnectionDataBase +{ + public: + ConnectionDataBase(); + + /*! + \brief The caption of the connection. + + Captions are optional for identyfying given connection + by name eg. for users. + */ + QString caption; + + /*! + \brief The additional description for the connection + */ + QString description; + + /*! + \brief Used for identifying a single piece of data in a set + + Optional ID used for identifying a single piece data in a set. + ConnectionData::ConstList for example) This is set automatically + when needed. By default: -1. + */ + int id; + + /*! + \brief the name of the driver that should be used to create a connection + + Name (unique, not i18n'd) of driver that is used (or should be used) to + create a connection. If you pass this ConnectionData object to + KexiDB::Driver::createConnection() to create connection, the @a driverName member + will be updated with a valid KexiDB driver name. + In other situations the @a driverName member may be used to store information what + driver should be used to perform connection, before we get an appropriate + driver object from DriverManager. + */ + QString driverName; + + /*! + \brief Host name used for the remote connection. + + Can be empty if the connection is not remote. If it is empty "localhost" is used. + */ + QString hostName; + + /*! + \brief Port used for the remote connection. + + The default is 0, what means we use don't change the database engine's default port. + */ + unsigned short int port; + + /*! + \brief True if local socket file should be used instead of TCP/IP port. + + Only meaningful for connections with localhost as server. + True by default, so local communication can be optimized, and users can avoid problems + with TCP/IP connections disabled by firewalls. + + If true, @a hostName and @a port will be ignored and @a localSocketFileName will be used. + On MS Windows this option is often ignored and TCP/IP connection to the localhost is performed. + */ + bool useLocalSocketFile; + + /*! + \brief Name of local (named) socket file. + + For local connections only. If empty, it's driver will try to locate existing local socket + file. Empty by default. + */ + QString localSocketFileName; + + /*! + \brief Password used for the connection. + + Can be empty string or null. If it is empty (equal to ""), empty password is passed to the driver. + If it is null (QString::null), no password is passed to the driver. + In this case, applications using KexiDB should ask for the password. */ + QString password; + + /*! + \brief True if password should be saved to a file for the connection. + + False by default, in most cases can be set to true when nonempty + password has been loaded from a file. + For instance, this flag can be then shown for a user as a checkbox. + */ + bool savePassword; + + /*! + \brief Username used for the connection. + + Can be empty. */ + QString userName; + + protected: + /*! + \brief The filename for file-based databases + + For file-based database engines like SQLite, \a fileName is used + instead hostName and port + */ + QString m_fileName; + + /*! + \brief Absolute path to the database file + + Will be empty if database is not file-based + */ + QString m_dbPath; + + /*! + \brief Filename of the database file + + Will be empty if database is not file-based + */ + QString m_dbFileName; +}; + +//! Database specific connection data, e.g. host, port. +/*! Connection data, once configured, can be later stored for reuse. +*/ +class KEXI_DB_EXPORT ConnectionData : public QObject, public ConnectionDataBase +{ + public: + typedef QPtrList<ConnectionData> List; + typedef QPtrListIterator<ConnectionData> ListIterator; + + ConnectionData(); + + ConnectionData(const ConnectionData&); + + ~ConnectionData(); + + ConnectionData& operator=(const ConnectionData& cd); + + /*! + \brief Set the filename used by the connection + + For file-based database engines, like SQLite, you should use this + function to set the file name of the database to use. + \a fn can be either absolute or relative path to the file. + */ + void setFileName( const QString& fn ); + + /*! + \brief Get the filename used by the connection + + For file-based database engines like SQLite, \a fileName is used + instead hostName and port. + @return An absolute path to the database file being used + */ + QString fileName() const { return m_fileName; } + + /*! + \brief The directory the database file is in + + \return file path (for file-based engines) but without a file name + */ + QString dbPath() const { return m_dbPath; } + + /*! + \brief The file name (without path) of the database file + + \return The file name (for file-based engines) but without a full path + */ + QString dbFileName() const { return m_dbFileName; } + + /*! + \brief A user-friendly string for the server + + \return a user-friendly string like: + - "myhost.org:12345" if a host and port is specified; + - "localhost:12345" of only port is specified; + - "[email protected]:12345" if also user is specified + - "<file>" if file-based driver is assigned but no filename is assigned + - "file: pathto/mydb.kexi" if file-based driver is assigned and + filename is assigned + + User's name is added if \a addUser is true (the default). + */ + QString serverInfoString(bool addUser = true) const; + + /*! @internal + Format version used when saving the data to a shortcut file. + This is set to 0 by default what means KexiDBShortcutFile_version should be used on saving. + If KexiDBConnShortcutFile was used to create this KexiProjectData object, + the version information is be retrieved from the file. */ + uint formatVersion; + + protected: + class Private; + Private *priv; + + friend class Connection; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/cursor.cpp b/kexi/kexidb/cursor.cpp new file mode 100644 index 00000000..4b9cdea3 --- /dev/null +++ b/kexi/kexidb/cursor.cpp @@ -0,0 +1,571 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/cursor.h> + +#include <kexidb/driver.h> +#include <kexidb/driver_p.h> +#include <kexidb/error.h> +#include <kexidb/roweditbuffer.h> +#include <kexiutils/utils.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <assert.h> +#include <stdlib.h> + +using namespace KexiDB; + +#ifdef KEXI_DEBUG_GUI + +#endif + +Cursor::Cursor(Connection* conn, const QString& statement, uint options) + : QObject() + , m_conn(conn) + , m_query(0) + , m_rawStatement(statement) + , m_options(options) +{ +#ifdef KEXI_DEBUG_GUI + KexiUtils::addKexiDBDebug(QString("Create cursor: ")+statement); +#endif + init(); +} + +Cursor::Cursor(Connection* conn, QuerySchema& query, uint options ) + : QObject() + , m_conn(conn) + , m_query(&query) + , m_options(options) +{ +#ifdef KEXI_DEBUG_GUI + KexiUtils::addKexiDBDebug(QString("Create cursor for query \"%1\": ").arg(query.name())+query.debugString()); +#endif + init(); +} + +void Cursor::init() +{ + assert(m_conn); + m_conn->m_cursors.insert(this,this); + m_opened = false; +// , m_atFirst(false) +// , m_atLast(false) +// , m_beforeFirst(false) + m_atLast = false; + m_afterLast = false; + m_readAhead = false; + m_at = 0; +//js:todo: if (m_query) +// m_fieldCount = m_query->fieldsCount(); +// m_fieldCount = m_query ? m_query->fieldCount() : 0; //do not know + //<members related to buffering> +// m_cols_pointers_mem_size = 0; + m_records_in_buf = 0; + m_buffering_completed = false; + m_at_buffer = false; + m_result = -1; + + m_containsROWIDInfo = (m_query && m_query->masterTable()) + && m_conn->driver()->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false; + + if (m_query) { + //get list of all fields + m_fieldsExpanded = new QueryColumnInfo::Vector(); + *m_fieldsExpanded = m_query->fieldsExpanded( + m_containsROWIDInfo ? QuerySchema::WithInternalFieldsAndRowID : QuerySchema::WithInternalFields); + m_logicalFieldCount = m_fieldsExpanded->count() + - m_query->internalFields().count() - (m_containsROWIDInfo?1:0); + m_fieldCount = m_fieldsExpanded->count(); + } else { + m_fieldsExpanded = 0; + m_logicalFieldCount = 0; + m_fieldCount = 0; + } + m_orderByColumnList = 0; + m_queryParameters = 0; +} + +Cursor::~Cursor() +{ +#ifdef KEXI_DEBUG_GUI + if (m_query) + KexiUtils::addKexiDBDebug(QString("~ Delete cursor for query")); + else + KexiUtils::addKexiDBDebug(QString("~ Delete cursor: ")+m_rawStatement); +#endif +/* if (!m_query) + KexiDBDbg << "Cursor::~Cursor() '" << m_rawStatement.latin1() << "'" << endl; + else + KexiDBDbg << "Cursor::~Cursor() " << endl;*/ + + //take me if delete was + if (!m_conn->m_destructor_started) + m_conn->m_cursors.take(this); + else { + KexiDBDbg << "Cursor::~Cursor() can be destroyed with Conenction::deleteCursor(), not with delete operator !"<< endl; + exit(1); + } + delete m_fieldsExpanded; + delete m_queryParameters; +} + +bool Cursor::open() +{ + if (m_opened) { + if (!close()) + return false; + } + if (!m_rawStatement.isEmpty()) + m_conn->m_sql = m_rawStatement; + else { + if (!m_query) { + KexiDBDbg << "Cursor::open(): no query statement (or schema) defined!" << endl; + setError(ERR_SQL_EXECUTION_ERROR, i18n("No query statement or schema defined.")); + return false; + } + Connection::SelectStatementOptions options; + options.alsoRetrieveROWID = m_containsROWIDInfo; /*get ROWID if needed*/ + m_conn->m_sql = m_queryParameters + ? m_conn->selectStatement( *m_query, *m_queryParameters, options ) + : m_conn->selectStatement( *m_query, options ); + if (m_conn->m_sql.isEmpty()) { + KexiDBDbg << "Cursor::open(): empty statement!" << endl; + setError(ERR_SQL_EXECUTION_ERROR, i18n("Query statement is empty.")); + return false; + } + } + m_sql = m_conn->m_sql; + m_opened = drv_open(); +// m_beforeFirst = true; + m_afterLast = false; //we are not @ the end + m_at = 0; //we are before 1st rec + if (!m_opened) { + setError(ERR_SQL_EXECUTION_ERROR, i18n("Error opening database cursor.")); + return false; + } + m_validRecord = false; + +//luci: WHAT_EXACTLY_SHOULD_THAT_BE? +// if (!m_readAhead) // jowenn: to ensure before first state, without cluttering implementation code + if (m_conn->driver()->beh->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY) { +// KexiDBDbg << "READ AHEAD:" << endl; + m_readAhead = getNextRecord(); //true if any record in this query +// KexiDBDbg << "READ AHEAD = " << m_readAhead << endl; + } + m_at = 0; //we are still before 1st rec + return !error(); +} + +bool Cursor::close() +{ + if (!m_opened) + return true; + bool ret = drv_close(); + + clearBuffer(); + + m_opened = false; +// m_beforeFirst = false; + m_afterLast = false; + m_readAhead = false; + m_fieldCount = 0; + m_logicalFieldCount = 0; + m_at = -1; + +// KexiDBDbg<<"Cursor::close() == "<<ret<<endl; + return ret; +} + +bool Cursor::reopen() +{ + if (!m_opened) + return open(); + return close() && open(); +} + +bool Cursor::moveFirst() +{ + if (!m_opened) + return false; +// if (!m_beforeFirst) { //cursor isn't @ first record now: reopen + if (!m_readAhead) { + if (m_options & Buffered) { + if (m_records_in_buf==0 && m_buffering_completed) { + //eof and bof should now return true: + m_afterLast = true; + m_at = 0; + return false; //buffering completed and there is no records! + } + if (m_records_in_buf>0) { + //set state as we would be before first rec: + m_at_buffer = false; + m_at = 0; + //..and move to next, ie. 1st record +// m_afterLast = m_afterLast = !getNextRecord(); + m_afterLast = !getNextRecord(); + return !m_afterLast; + } + } + if (m_afterLast && m_at==0) //failure if already no records + return false; + if (!reopen()) //try reopen + return false; + if (m_afterLast) //eof + return false; + } + else { + //we have a record already read-ahead: we now point @ that: + m_at = 1; + } +// if (!m_atFirst) { //cursor isn't @ first record now: reopen +// reopen(); +// } +// if (m_validRecord) { +// return true; //there is already valid record retrieved +// } + //get first record +// if (drv_moveFirst() && drv_getRecord()) { +// m_beforeFirst = false; + m_afterLast = false; + m_readAhead = false; //1st record had been read +// } + return m_validRecord; +} + +bool Cursor::moveLast() +{ + if (!m_opened) + return false; + if (m_afterLast || m_atLast) { + return m_validRecord; //we already have valid last record retrieved + } + if (!getNextRecord()) { //at least next record must be retrieved +// m_beforeFirst = false; + m_afterLast = true; + m_validRecord = false; + m_atLast = false; + return false; //no records + } + while (getNextRecord()) //move after last rec. + ; +// m_beforeFirst = false; + m_afterLast = false; + //cursor shows last record data + m_atLast = true; +// m_validRecord = true; + +/* + //we are before or @ last record: +// if (m_atLast && m_validRecord) //we're already @ last rec. +// return true; + if (m_validRecord) { + if (drv_getRecord()) + } + if (!m_validRecord) { + if (drv_getRecord() && m_validRecord) + return true; + reopen(); + } + */ + return true; +} + +bool Cursor::moveNext() +{ + if (!m_opened || m_afterLast) + return false; + if (getNextRecord()) { +// m_validRecord = true; + return true; + } + return false; +} + +bool Cursor::movePrev() +{ + if (!m_opened /*|| m_beforeFirst*/ || !(m_options & Buffered)) + return false; + + //we're after last record and there are records in the buffer + //--let's move to last record + if (m_afterLast && (m_records_in_buf>0)) { + drv_bufferMovePointerTo(m_records_in_buf-1); + m_at=m_records_in_buf; + m_at_buffer = true; //now current record is stored in the buffer + m_validRecord=true; + m_afterLast=false; + return true; + } + //we're at first record: go BOF + if ((m_at <= 1) || (m_records_in_buf <= 1/*sanity*/)) { + m_at=0; + m_at_buffer = false; + m_validRecord=false; + return false; + } + + m_at--; + if (m_at_buffer) {//we already have got a pointer to buffer + drv_bufferMovePointerPrev(); //just move to prev record in the buffer + } else {//we have no pointer + //compute a place in the buffer that contain next record's data + drv_bufferMovePointerTo(m_at-1); + m_at_buffer = true; //now current record is stored in the buffer + } + m_validRecord=true; + m_afterLast=false; + return true; +} + +bool Cursor::eof() const +{ + return m_afterLast; +} + +bool Cursor::bof() const +{ + return m_at==0; +} + +Q_LLONG Cursor::at() const +{ + if (m_readAhead) + return 0; + return m_at - 1; +} + +bool Cursor::isBuffered() const +{ + return m_options & Buffered; +} + +void Cursor::setBuffered(bool buffered) +{ + if (!m_opened) + return; + if (isBuffered()==buffered) + return; + m_options ^= Buffered; +} + +void Cursor::clearBuffer() +{ + if ( !isBuffered() || m_fieldCount==0) + return; + + drv_clearBuffer(); + + m_records_in_buf=0; + m_at_buffer=false; +} + +bool Cursor::getNextRecord() +{ + m_result = -1; //by default: invalid result of row fetching + + if ((m_options & Buffered)) {//this cursor is buffered: +// KexiDBDbg << "m_at < m_records_in_buf :: " << (long)m_at << " < " << m_records_in_buf << endl; +//js if (m_at==-1) m_at=0; + if (m_at < m_records_in_buf) {//we have next record already buffered: +/// if (m_at < (m_records_in_buf-1)) {//we have next record already buffered: +//js if (m_at_buffer && (m_at!=0)) {//we already have got a pointer to buffer + if (m_at_buffer) {//we already have got a pointer to buffer + drv_bufferMovePointerNext(); //just move to next record in the buffer + } else {//we have no pointer + //compute a place in the buffer that contain next record's data + drv_bufferMovePointerTo(m_at-1+1); +// drv_bufferMovePointerTo(m_at+1); + m_at_buffer = true; //now current record is stored in the buffer + } + } + else {//we are after last retrieved record: we need to physically fetch next record: + if (!m_readAhead) {//we have no record that was read ahead + if (!m_buffering_completed) { + //retrieve record only if we are not after + //the last buffer's item (i.e. when buffer is not fully filled): +// KexiDBDbg<<"==== buffering: drv_getNextRecord() ===="<<endl; + drv_getNextRecord(); + } + if ((FetchResult) m_result != FetchOK) {//there is no record + m_buffering_completed = true; //no more records for buffer +// KexiDBDbg<<"m_result != FetchOK ********"<<endl; + m_validRecord = false; + m_afterLast = true; +//js m_at = m_records_in_buf; + m_at = -1; //position is invalid now and will not be used + if ((FetchResult) m_result == FetchEnd) { + return false; + } + setError(ERR_CURSOR_RECORD_FETCHING, i18n("Cannot fetch next record.")); + return false; + } + //we have a record: store this record's values in the buffer + drv_appendCurrentRecordToBuffer(); + m_records_in_buf++; + } + else //we have a record that was read ahead: eat this + m_readAhead = false; + } + } + else {//we are after last retrieved record: we need to physically fetch next record: + if (!m_readAhead) {//we have no record that was read ahead +// KexiDBDbg<<"==== no prefetched record ===="<<endl; + drv_getNextRecord(); + if ((FetchResult)m_result != FetchOK) {//there is no record +// KexiDBDbg<<"m_result != FetchOK ********"<<endl; + m_validRecord = false; + m_afterLast = true; + m_at = -1; + if ((FetchResult) m_result == FetchEnd) { + return false; + } + setError(ERR_CURSOR_RECORD_FETCHING, i18n("Cannot fetch next record.")); + return false; + } + } + else //we have a record that was read ahead: eat this + m_readAhead = false; + } + + m_at++; + +// if (m_data->curr_colname && m_data->curr_coldata) +// for (int i=0;i<m_data->curr_cols;i++) { +// KexiDBDbg<<i<<": "<< m_data->curr_colname[i]<<" == "<< m_data->curr_coldata[i]<<endl; +// } +// KexiDBDbg<<"m_at == "<<(long)m_at<<endl; + + m_validRecord = true; + return true; +} + +bool Cursor::updateRow(RowData& data, RowEditBuffer& buf, bool useROWID) +{ +//! @todo doesn't update cursor's buffer YET! + clearError(); + if (!m_query) + return false; + return m_conn->updateRow(*m_query, data, buf, useROWID); +} + +bool Cursor::insertRow(RowData& data, RowEditBuffer& buf, bool getROWID) +{ +//! @todo doesn't update cursor's buffer YET! + clearError(); + if (!m_query) + return false; + return m_conn->insertRow(*m_query, data, buf, getROWID); +} + +bool Cursor::deleteRow(RowData& data, bool useROWID) +{ +//! @todo doesn't update cursor's buffer YET! + clearError(); + if (!m_query) + return false; + return m_conn->deleteRow(*m_query, data, useROWID); +} + +bool Cursor::deleteAllRows() +{ +//! @todo doesn't update cursor's buffer YET! + clearError(); + if (!m_query) + return false; + return m_conn->deleteAllRows(*m_query); +} + +QString Cursor::debugString() const +{ + QString dbg = "CURSOR( "; + if (!m_query) { + dbg += "RAW STATEMENT: '"; + dbg += m_rawStatement; + dbg += "'\n"; + } + else { + dbg += "QuerySchema: '"; + dbg += m_conn->selectStatement( *m_query ); + dbg += "'\n"; + } + if (isOpened()) + dbg += " OPENED"; + else + dbg += " NOT_OPENED"; + if (isBuffered()) + dbg += " BUFFERED"; + else + dbg += " NOT_BUFFERED"; + dbg += " AT="; + dbg += QString::number((unsigned long)at()); + dbg += " )"; + return dbg; +} + +void Cursor::debug() const +{ + KexiDBDbg << debugString() << endl; +} + +void Cursor::setOrderByColumnList(const QStringList& columnNames) +{ + Q_UNUSED(columnNames); +//! @todo implement this: +// all field names should be fooun, exit otherwise .......... + + // OK +//TODO if (!m_orderByColumnList) +//TODO +} + +/*! Convenience method, similar to setOrderBy(const QStringList&). */ +void Cursor::setOrderByColumnList(const QString& column1, const QString& column2, + const QString& column3, const QString& column4, const QString& column5) +{ + Q_UNUSED(column1); + Q_UNUSED(column2); + Q_UNUSED(column3); + Q_UNUSED(column4); + Q_UNUSED(column5); +//! @todo implement this, like above +//! @todo add ORDER BY info to debugString() +} + +QueryColumnInfo::Vector Cursor::orderByColumnList() const +{ + return m_orderByColumnList ? *m_orderByColumnList: QueryColumnInfo::Vector(); +} + +QValueList<QVariant> Cursor::queryParameters() const +{ + return m_queryParameters ? *m_queryParameters : QValueList<QVariant>(); +} + +void Cursor::setQueryParameters(const QValueList<QVariant>& params) +{ + if (!m_queryParameters) + m_queryParameters = new QValueList<QVariant>(params); + else + *m_queryParameters = params; +} + +#include "cursor.moc" diff --git a/kexi/kexidb/cursor.h b/kexi/kexidb/cursor.h new file mode 100644 index 00000000..6ea64dd9 --- /dev/null +++ b/kexi/kexidb/cursor.h @@ -0,0 +1,365 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_CURSOR_H +#define KEXIDB_CURSOR_H + +#include <qstring.h> +#include <qvariant.h> +#include <qptrvector.h> +#include <qvaluevector.h> + +#include <kexidb/connection.h> +#include <kexidb/object.h> + +namespace KexiDB { + +class RowEditBuffer; + +//! Provides database cursor functionality. +/*! + Cursor can be defined in two ways: + + -# by passing QuerySchema object to Connection::executeQuery() or Connection::prepareQuery(); + then query is defined for in engine-independent way -- this is recommended usage + + -# by passing raw query statement string to Connection::executeQuery() or Connection::prepareQuery(); + then query may be defined for in engine-dependent way -- this is not recommended usage, + but convenient when we can't or do not want to allocate QuerySchema object, while we + know that the query statement is syntactically and logically ok in our context. + + You can move cursor to next record with moveNext() and move back with movePrev(). + The cursor is always positioned on record, not between records, with exception that + ofter open() it is positioned before first record (if any) -- then bof() equals true, + and can be positioned after the last record (if any) with moveNext() -- then eof() equals true, + For example, if you have four records 1, 2, 3, 4, then after calling moveNext(), + moveNext(), moveNext(), movePrev() you are going through records: 1, 2, 3, 2. + + Cursor can be buffered or unbuferred. + Buffering in this class is not related to any SQL engine capatibilities for server-side cursors + (eg. like 'DECLARE CURSOR' statement) - buffered data is at client (application) side. + Any record retrieved in buffered cursor will be stored inside an internal buffer + and reused when needed. Unbuffered cursor always requires one record fetching from + db connection at every step done with moveNext(), movePrev(), etc. + + Notes: + - Do not use delete operator for Cursor objects - this will fail; use Connection::deleteCursor() + instead. + - QuerySchema object is not owned by Cursor object that uses it. +*/ +class KEXI_DB_EXPORT Cursor: public QObject, public Object +{ + Q_OBJECT + + public: + //! Cursor options that describes its behaviour + enum Options { + NoOptions = 0, + Buffered = 1 + }; + + virtual ~Cursor(); + + /*! \return connection used for the cursor */ + inline Connection* connection() const { return m_conn; } + + /*! Opens the cursor using data provided on creation. + The data might be either QuerySchema or raw sql statement. */ + bool open(); + + /*! Closes and then opens again the same cursor. + If the cursor is not opened it is just opened and result of this open is returned. + Otherwise, true is returned if cursor is successfully closed and then opened. */ + bool reopen(); + +// /*! Opens the cursor using \a statement. +// Omit \a statement if cursor is already initialized with statement +// at creation time. If \a statement is not empty, existing statement +// (if any) is overwritten. */ +// bool open( const QString& statement = QString::null ); + + /*! Closes previously opened cursor. + If the cursor is closed, nothing happens. */ + virtual bool close(); + + /*! \return query schema used to define this cursor + or NULL if the cursor is not defined by a query schema but by a raw statement. */ + inline QuerySchema *query() const { return m_query; } + + //! \return query parameters assigned to this cursor + QValueList<QVariant> queryParameters() const; + + //! Sets query parameters \a params for this cursor. + void setQueryParameters(const QValueList<QVariant>& params); + + /*! \return raw query statement used to define this cursor + or null string if raw statement instead (but QuerySchema is defined instead). */ + inline QString rawStatement() const { return m_rawStatement; } + + /*! \return logically or'd cursor's options, + selected from Cursor::Options enum. */ + inline uint options() const { return m_options; } + + /*! \return true if the cursor is opened. */ + inline bool isOpened() const { return m_opened; } + + /*! \return true if the cursor is buffered. */ + bool isBuffered() const; + + /*! Sets this cursor to buffered type or not. See description + of buffered and nonbuffered cursors in class description. + This method only works if cursor is not opened (isOpened()==false). + You can close already opened cursor and then switch this option on/off. + */ + void setBuffered(bool buffered); + + /*! Moves current position to the first record and retrieves it. + \return true if the first record was retrieved. + False could mean that there was an error or there is no record available. */ + bool moveFirst(); + + /*! Moves current position to the last record and retrieves it. + \return true if the last record was retrieved. + False could mean that there was an error or there is no record available. */ + virtual bool moveLast(); + + /*! Moves current position to the next record and retrieves it. */ + virtual bool moveNext(); + + /*! Moves current position to the next record and retrieves it. + Currently it's only supported for buffered cursors. */ + virtual bool movePrev(); + + /*! \return true if current position is after last record. */ + bool eof() const; + + /*! \return true if current position is before first record. */ + bool bof() const; + + /*! \return current internal position of the cursor's query. + We are counting records from 0. + Value -1 means that cursor does not point to any valid record + (this happens eg. after open(), close(), + and after moving after last record or before first one. */ + Q_LLONG at() const; + + /*! \return number of fields available for this cursor. + This never includes ROWID column or other internal coluns (e.g. lookup). */ + inline uint fieldCount() const { return m_query ? m_logicalFieldCount : m_fieldCount; } + + /*! \return true if ROWID information is appended with every row. + ROWID information is available + if DriverBehaviour::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false + for a KexiDB database driver and the master table has no primary key defined. + Phisically, ROWID value is returned after last returned field, + so data vector's length is expanded by one. */ + inline bool containsROWIDInfo() const { return m_containsROWIDInfo; } + + /*! \return a value stored in column number \a i (counting from 0). + Is has unspecified behaviour if the cursor is not at valid record. + Note for driver developers: + If \a i is >= than m_fieldCount, null QVariant value should be returned. + To return a value typically you can use a pointer to internal structure + that contain current row data (buffered or unbuffered). */ + virtual QVariant value(uint i) = 0; + + /*! [PROTOTYPE] \return current record data or NULL if there is no current records. */ + virtual const char ** rowData() const = 0; + + /*! Sets a list of columns for ORDER BY section of the query. + Only works when the cursor has been created using QuerySchema object + (i.e. when query()!=0; does not work with raw statements). + Each name on the list must be a field or alias present within the query + and must not be covered by aliases. If one or more names cannot be found within + the query, the method will have no effect. Any previous ORDER BY settings will be removed. + + The order list provided here has priority over a list defined in the QuerySchema + object itseld (using QuerySchema::setOrderByColumnList()). + The QuerySchema object itself is not modifed by this method: only order of records retrieved + by this cursor is affected. + + Use this method before calling open(). You can also call reopen() after calling this method + to see effects of applying records order. */ + void setOrderByColumnList(const QStringList& columnNames); + + /*! Convenience method, similar to setOrderByColumnList(const QStringList&). */ + void setOrderByColumnList(const QString& column1, const QString& column2 = QString::null, + const QString& column3 = QString::null, const QString& column4 = QString::null, + const QString& column5 = QString::null); + + /*! \return a list of fields contained in ORDER BY section of the query. + @see setOrderBy(const QStringList&) */ + QueryColumnInfo::Vector orderByColumnList() const; + + /*! Puts current record's data into \a data (makes a deep copy). + This have unspecified behaviour if the cursor is not at valid record. + Note: For reimplementation in driver's code. Shortly, this method translates + a row data from internal representation (probably also used in buffer) + to simple public RecordData representation. */ + virtual void storeCurrentRow(RowData &data) const = 0; + + bool updateRow(RowData& data, RowEditBuffer& buf, bool useROWID = false); + + bool insertRow(RowData& data, RowEditBuffer& buf, bool getROWID = false); + + bool deleteRow(RowData& data, bool useROWID = false); + + bool deleteAllRows(); + + /*! \return a code of last executed operation's result at the server side. + This code is engine dependent and may be even engine-version dependent. + It can be visible in applications mainly after clicking a "Details>>" button + or something like that -- this just can be useful for advanced users and + for testing. + Note for driver developers: Return here the value you usually store as result + of most lower-level operations. By default this method returns 0. */ + virtual int serverResult() { return 0; } + + /*! \return (not i18n'd) name of last executed operation's result at the server side. + Sometimes engines have predefined its result names that can be used e.g. + to refer a documentation. SQLite is one of such engines. + Note for driver developers: Leave the default implementation (null + string is returned ) if your engine has no such capability. */ + virtual QString serverResultName() { return QString::null; } + + /*! \return (not i18n'd) description text (message) of last operation's error/result. + In most cases engines do return such a messages, any user can then use this + to refer a documentation. + Note for driver developers: Leave the default implementation (null + string is returned ) if your engine has no such capability. */ + virtual QString serverErrorMsg() { return QString::null; } + + /*! \return Debug information. */ + QString debugString() const; + + //! Outputs debug information. + void debug() const; + + protected: + //! possible results of row fetching, used for m_result + typedef enum FetchResult { FetchError=0, FetchOK=1, FetchEnd=2 }; + + /*! Cursor will operate on \a conn, raw \a statement will be used to execute query. */ + Cursor(Connection* conn, const QString& statement, uint options = NoOptions ); + + /*! Cursor will operate on \a conn, \a query schema will be used to execute query. */ + Cursor(Connection* conn, QuerySchema& query, uint options = NoOptions ); + + void init(); + + /*! Internal: cares about proper flag setting depending on result of drv_getNextRecord() + and depending on wherher a cursor is buffered. */ + bool getNextRecord(); + + /* Note for driver developers: this method should initialize engine-specific cursor's + resources using m_sql statement. It is not required to store \a statement somewhere + in your Cursor subclass (it is already stored in m_query or m_rawStatement, + depending query type) - only pass it to proper engine's function. */ + virtual bool drv_open() = 0; + + virtual bool drv_close() = 0; +// virtual bool drv_moveFirst() = 0; + virtual void drv_getNextRecord() = 0; +//unused virtual bool drv_getPrevRecord() = 0; + + /*! Stores currently fetched record's values in appropriate place of the buffer. + Note for driver developers: + This place can be computed using m_at. Do not change value of m_at or any other + Cursor members, only change your internal structures like pointer to current + row, etc. If your database engine's API function (for record fetching) + do not allocates such a space, you want to allocate a space for current + record. Otherwise, reuse existing structure, what could be more efficient. + All functions like drv_appendCurrentRecordToBuffer() operates on the buffer, + i.e. array of stored rows. You are not forced to have any particular + fixed structure for buffer item or buffer itself - the structure is internal and + only methods like storeCurrentRecord() visible to public. + */ + virtual void drv_appendCurrentRecordToBuffer() = 0; + /*! Moves pointer (that points to the buffer) -- to next item in this buffer. + Note for driver developers: probably just execute "your_pointer++" is enough. + */ + virtual void drv_bufferMovePointerNext() = 0; + /*! Like drv_bufferMovePointerNext() but execute "your_pointer--". */ + virtual void drv_bufferMovePointerPrev() = 0; + /*! Moves pointer (that points to the buffer) to a new place: \a at. + */ + virtual void drv_bufferMovePointerTo(Q_LLONG at) = 0; + + /*DISABLED: ! This is called only once in open(), after successful drv_open(). + Reimplement this if you need (or not) to do get the first record after drv_open(), + eg. to know if there are any records in table. Value returned by this method + will be assigned to m_readAhead. + Default implementation just calls drv_getNextRecord(). */ + + /*! Clears cursor's buffer if this was allocated (only for buffered cursor type). + Otherwise do nothing. For reimplementing. Default implementation does nothing. */ + virtual void drv_clearBuffer() {} + + //! @internal clears buffer with reimplemented drv_clearBuffer(). */ + void clearBuffer(); + + /*! Clears an internal member that is used to storing last result code, + the same that is returend by serverResult(). */ + virtual void drv_clearServerResult() = 0; + + QGuardedPtr<Connection> m_conn; + QuerySchema *m_query; +// CursorData *m_data; + QString m_rawStatement; + bool m_opened : 1; +//js (m_at==0 is enough) bool m_beforeFirst : 1; + bool m_atLast : 1; + bool m_afterLast : 1; +// bool m_atLast; + bool m_validRecord : 1; //!< true if valid record is currently retrieved @ current position + bool m_containsROWIDInfo : 1; + Q_LLONG m_at; + uint m_fieldCount; //!< cached field count information + uint m_logicalFieldCount; //!< logical field count, i.e. without intrernal values like ROWID or lookup + uint m_options; //!< cursor options that describes its behaviour + char m_result; //!< result of a row fetching + + //<members related to buffering> + int m_records_in_buf; //!< number of records currently stored in the buffer + bool m_buffering_completed : 1; //!< true if we already have all records stored in the buffer + //</members related to buffering> + + //! Useful e.g. for value(int) method when we need access to schema def. + QueryColumnInfo::Vector* m_fieldsExpanded; + + //! Used by setOrderByColumnList() + QueryColumnInfo::Vector* m_orderByColumnList; + + QValueList<QVariant>* m_queryParameters; + + private: + bool m_readAhead : 1; + + //<members related to buffering> + bool m_at_buffer : 1; //!< true if we already point to the buffer with curr_coldata + //</members related to buffering> + + + class Private; + Private *d; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/cursor_p.h b/kexi/kexidb/cursor_p.h new file mode 100644 index 00000000..c03eba66 --- /dev/null +++ b/kexi/kexidb/cursor_p.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_CURSOR_P_H +#define KEXIDB_CURSOR_P_H + +#include <qstring.h> + +#include "connection.h" + +namespace KexiDB { + +#if 0 +/*PRIVATE*/ class /*KEXI_DB_EXPORT*/ CursorData +{ + public: + CursorData() {}; + ~CursorData() {}; +}; +#endif + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/dbobjectnamevalidator.cpp b/kexi/kexidb/dbobjectnamevalidator.cpp new file mode 100644 index 00000000..77ed0e55 --- /dev/null +++ b/kexi/kexidb/dbobjectnamevalidator.cpp @@ -0,0 +1,51 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2005 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "dbobjectnamevalidator.h" + +#include "driver.h" + +using namespace KexiDB; +using namespace KexiUtils; + +ObjectNameValidator::ObjectNameValidator( + KexiDB::Driver *drv, QObject * parent, const char * name) +: Validator(parent,name) +{ + m_drv = drv; +} + +ObjectNameValidator::~ObjectNameValidator() +{ +} + +Validator::Result ObjectNameValidator::internalCheck( + const QString & /*valueName*/, const QVariant& v, + QString &message, QString &details) +{ + + if (m_drv.isNull() ? !KexiDB::Driver::isKexiDBSystemObjectName(v.toString()) + : !m_drv->isSystemObjectName(v.toString())) + return Validator::Ok; + message = i18n("You cannot use name \"%1\" for your object.\n" + "It is reserved for internal Kexi objects. Please choose another name.") + .arg(v.toString()); + details = i18n("Names of internal Kexi objects are starting with \"kexi__\"."); + return Validator::Error; +} diff --git a/kexi/kexidb/dbobjectnamevalidator.h b/kexi/kexidb/dbobjectnamevalidator.h new file mode 100644 index 00000000..9b8ac617 --- /dev/null +++ b/kexi/kexidb/dbobjectnamevalidator.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2005 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDBOBJECTNAMEVALIDATOR_H +#define KEXIDBOBJECTNAMEVALIDATOR_H + +#include <kexiutils/validator.h> +#include <qstring.h> +#include <qguardedptr.h> + +namespace KexiDB { + + class Driver; + + /*! Validates input: + accepts if the name is not reserved for internal kexi objects. */ + class KEXI_DB_EXPORT ObjectNameValidator : public KexiUtils::Validator + { + public: + /*! \a drv is a KexiDB driver on which isSystemObjectName() will be + called inside check(). If \a drv is 0, KexiDB::Driver::isKexiDBSystemObjectName() + static function is called instead. */ + ObjectNameValidator(KexiDB::Driver *drv, QObject * parent = 0, const char * name = 0); + virtual ~ObjectNameValidator(); + + protected: + virtual KexiUtils::Validator::Result internalCheck(const QString &valueName, const QVariant& v, + QString &message, QString &details); + QGuardedPtr<KexiDB::Driver> m_drv; + }; +} + +#endif diff --git a/kexi/kexidb/dbproperties.cpp b/kexi/kexidb/dbproperties.cpp new file mode 100644 index 00000000..c5780542 --- /dev/null +++ b/kexi/kexidb/dbproperties.cpp @@ -0,0 +1,148 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "dbproperties.h" +#include <klocale.h> + +using namespace KexiDB; + +DatabaseProperties::DatabaseProperties(Connection *conn) + : KexiDB::Object() + , m_conn(conn) +{ +} + +DatabaseProperties::~DatabaseProperties() +{ +} + +bool DatabaseProperties::setValue( const QString& _name, const QVariant& value ) +{ + QString name(_name.stripWhiteSpace()); + bool ok; + //we need to know whether update or insert + bool exists = m_conn->resultExists( + QString::fromLatin1("SELECT 1 FROM kexi__db WHERE db_property=%1") + .arg(m_conn->driver()->escapeString(name)), ok); + if (!ok) { + setError(m_conn, i18n("Could not set value of database property \"%1\".").arg(name)); + return false; + } + + if (exists) { + if (!m_conn->executeSQL( + QString::fromLatin1("UPDATE kexi__db SET db_value=%1 WHERE db_property=%2") + .arg(m_conn->driver()->escapeString(value.toString())) + .arg(m_conn->driver()->escapeString(name)))) + { + setError(m_conn, i18n("Could not set value of database property \"%1\".").arg(name)); + return false; + } + return true; + } + + if (!m_conn->executeSQL( + QString::fromLatin1("INSERT INTO kexi__db (db_property, db_value) VALUES (%1, %2)") + .arg(m_conn->driver()->escapeString(name)) + .arg(m_conn->driver()->escapeString(value.toString())))) + { + setError(m_conn, i18n("Could not set value of database property \"%1\".").arg(name)); + return false; + } + return true; +} + +bool DatabaseProperties::setCaption( const QString& _name, const QString& caption ) +{ + QString name(_name.stripWhiteSpace()); + //captions have ' ' prefix + name.prepend(" "); + bool ok; + //we need to know whether update or insert + bool exists = m_conn->resultExists( + QString::fromLatin1("SELECT 1 FROM kexi__db WHERE db_property=%1") + .arg(m_conn->driver()->escapeString(name)), ok); + if (!ok) { + setError(m_conn, i18n("Could not set caption for database property \"%1\".").arg(name)); + return false; + } + + if (exists) { + if (!m_conn->executeSQL( + QString::fromLatin1("UPDATE kexi__db SET db_value=%1 WHERE db_property=%2") + .arg(m_conn->driver()->escapeString(caption)) + .arg(m_conn->driver()->escapeString(name)))) + { + setError(m_conn, i18n("Could not set caption for database property \"%1\".").arg(name)); + return false; + } + return true; + } + + if (!m_conn->executeSQL( + QString::fromLatin1("INSERT INTO kexi__db (db_property, db_value) VALUES (%1, %2)") + .arg(m_conn->driver()->escapeString(name)) + .arg(m_conn->driver()->escapeString(caption)))) + { + setError(m_conn, i18n("Could not set caption for database property \"%1\".").arg(name)); + return false; + } + return true; +} + +QVariant DatabaseProperties::value( const QString& _name ) +{ + QString result; + QString name(_name.stripWhiteSpace()); + if (true!=m_conn->querySingleString( + QString::fromLatin1("SELECT db_value FROM kexi__db WHERE db_property=") + + m_conn->driver()->escapeString(name), result)) { + m_conn->setError(ERR_NO_DB_PROPERTY, i18n("Could not read database property \"%1\".").arg(name)); + return QVariant(); + } + return result; +} + +QString DatabaseProperties::caption( const QString& _name ) +{ + QString result; + QString name(_name.stripWhiteSpace()); + //captions have ' ' prefix + name.prepend(" "); + if (true!=m_conn->querySingleString( + QString::fromLatin1("SELECT db_value FROM kexi__db WHERE db_property=") + + m_conn->driver()->escapeString(name), result)) { + setError(m_conn, i18n("Could not read database property \"%1\".").arg(name)); + return QString::null; + } + return result; +} + +QStringList DatabaseProperties::names() +{ + QStringList result; + if (true!=m_conn->queryStringList( + QString::fromLatin1("SELECT db_value FROM kexi__db WHERE db_property NOT LIKE ") + + m_conn->driver()->escapeString(QString::fromLatin1(" %%")), result, 0 /*0-th*/)) { + // ^^ exclude captions + setError(m_conn, i18n("Could not read database properties.")); + return QStringList(); + } + return result; +} diff --git a/kexi/kexidb/dbproperties.h b/kexi/kexidb/dbproperties.h new file mode 100644 index 00000000..91aed7e2 --- /dev/null +++ b/kexi/kexidb/dbproperties.h @@ -0,0 +1,67 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_DBPROPERTIES_H +#define KEXIDB_DBPROPERTIES_H + +#include "connection.h" + +namespace KexiDB { + +//! @todo implement KConfigBase interface here? + +//! A set of storable database properties. +/*! This is a convenience class that allows to store global dabatase properties without a need + for creating and maintain custom table. + DatabaseProperties object is accessible only using KexiDB::Connection::databaseProperties() method. + */ +class KEXI_DB_EXPORT DatabaseProperties : public KexiDB::Object +{ + public: + /*! Sets \a value for property \a name. Optional caption can be also set. + If there's no such property defined, it will be added. Existing value will be overwritten. + Note that to execute this method, database must be opened in read-write mode. + \return true on successful data. Connection */ + bool setValue( const QString& name, const QVariant& value ); + + /*! Sets \a caption for for property \a name. + Usually it shouldn't be translated: trnaslation can be performed before displaying. */ + bool setCaption( const QString& name, const QString& caption ); + + //! \return property value for \a propeName available for this driver. + //! If there's no such property defined for driver, Null QVariant value is returned. + QVariant value( const QString& name ); + + //! \return translated property caption for \a name. + //! If there's no such property defined for driver, empty string value is returned. + QString caption( const QString& name ); + + //! \return a list of available property names. + QStringList names(); + + protected: + DatabaseProperties(Connection *conn); + ~DatabaseProperties(); + + QGuardedPtr<Connection> m_conn; + friend class Connection; +}; +} + +#endif diff --git a/kexi/kexidb/driver.cpp b/kexi/kexidb/driver.cpp new file mode 100644 index 00000000..6e82c080 --- /dev/null +++ b/kexi/kexidb/driver.cpp @@ -0,0 +1,367 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/driver.h> +#include <kexidb/driver_p.h> +#include <kexidb/drivermanager.h> +#include <kexidb/drivermanager_p.h> +#include "error.h" +#include "drivermanager.h" +#include "connection.h" +#include "connectiondata.h" +#include "admin.h" + +#include <qfileinfo.h> + +#include <klocale.h> +#include <kdebug.h> + +#include <assert.h> + +using namespace KexiDB; + +/*! used when we do not have Driver instance yet, + or when we cannot get one */ +QValueVector<QString> dflt_typeNames; + + +//--------------------------------------------- + + +DriverBehaviour::DriverBehaviour() + : UNSIGNED_TYPE_KEYWORD("UNSIGNED") + , AUTO_INCREMENT_FIELD_OPTION("AUTO_INCREMENT") + , AUTO_INCREMENT_PK_FIELD_OPTION("AUTO_INCREMENT PRIMARY KEY") + , SPECIAL_AUTO_INCREMENT_DEF(false) + , AUTO_INCREMENT_REQUIRES_PK(false) + , ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE(false) + , QUOTATION_MARKS_FOR_IDENTIFIER('"') + , USING_DATABASE_REQUIRED_TO_CONNECT(true) + , _1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY(false) + , SELECT_1_SUBQUERY_SUPPORTED(false) + , SQL_KEYWORDS(0) +{ +} + +//--------------------------------------------- + +Driver::Info::Info() + : fileBased(false) + , allowImportingTo(true) +{ +} + +//--------------------------------------------- + +Driver::Driver( QObject *parent, const char *name, const QStringList & ) + : QObject( parent, name ) + , Object() + , beh( new DriverBehaviour() ) + , d( new DriverPrivate() ) +{ + d->connections.setAutoDelete(false); + //TODO: reasonable size + d->connections.resize(101); + d->typeNames.resize(Field::LastType + 1); + + d->initKexiKeywords(); +} + + +Driver::~Driver() +{ + DriverManagerInternal::self()->aboutDelete( this ); +// KexiDBDbg << "Driver::~Driver()" << endl; + QPtrDictIterator<Connection> it( d->connections ); + Connection *conn; + while ( (conn = it.toFirst()) ) { + delete conn; + } + delete beh; + delete d; +// KexiDBDbg << "Driver::~Driver() ok" << endl; +} + +bool Driver::isValid() +{ + clearError(); + if (KexiDB::version().major != version().major + || KexiDB::version().minor != version().minor) + { + setError(ERR_INCOMPAT_DRIVER_VERSION, + i18n("Incompatible database driver's \"%1\" version: found version %2, expected version %3.") + .arg(name()) + .arg(QString("%1.%2").arg(version().major).arg(version().minor)) + .arg(QString("%1.%2").arg(KexiDB::version().major).arg(KexiDB::version().minor))); + return false; + } + + QString inv_impl = i18n("Invalid database driver's \"%1\" implementation:\n").arg(name()); + QString not_init = i18n("Value of \"%1\" is not initialized for the driver."); + if (beh->ROW_ID_FIELD_NAME.isEmpty()) { + setError(ERR_INVALID_DRIVER_IMPL, inv_impl + not_init.arg("DriverBehaviour::ROW_ID_FIELD_NAME")); + return false; + } + + return true; +} + +const QPtrList<Connection> Driver::connectionsList() const +{ + QPtrList<Connection> clist; + QPtrDictIterator<Connection> it( d->connections ); + for( ; it.current(); ++it ) + clist.append( &(*it) ); + return clist; +} + +QString Driver::fileDBDriverMimeType() const +{ return d->fileDBDriverMimeType; } + +QString Driver::defaultFileBasedDriverMimeType() +{ return QString::fromLatin1("application/x-kexiproject-sqlite3"); } + +QString Driver::defaultFileBasedDriverName() +{ + DriverManager dm; + return dm.lookupByMime(Driver::defaultFileBasedDriverMimeType()).lower(); +} + +const KService* Driver::service() const +{ return d->service; } + +bool Driver::isFileDriver() const +{ return d->isFileDriver; } + +int Driver::features() const +{ return d->features; } + +bool Driver::transactionsSupported() const +{ return d->features & (SingleTransactions | MultipleTransactions); } + +AdminTools& Driver::adminTools() const +{ + if (!d->adminTools) + d->adminTools = drv_createAdminTools(); + return *d->adminTools; +} + +AdminTools* Driver::drv_createAdminTools() const +{ + return new AdminTools(); //empty impl. +} + +QString Driver::sqlTypeName(int id_t, int /*p*/) const +{ + if (id_t > Field::InvalidType && id_t <= Field::LastType) + return d->typeNames[(id_t>0 && id_t<=Field::LastType) ? id_t : Field::InvalidType /*sanity*/]; + + return d->typeNames[Field::InvalidType]; +} + +Connection *Driver::createConnection( ConnectionData &conn_data, int options ) +{ + clearError(); + if (!isValid()) + return 0; + + if (d->isFileDriver) { + if (conn_data.fileName().isEmpty()) { + setError(ERR_MISSING_DB_LOCATION, i18n("File name expected for file-based database driver.") ); + return 0; + } + } +// Connection *conn = new Connection( this, conn_data ); + Connection *conn = drv_createConnection( conn_data ); + + conn->setReadOnly(options & ReadOnlyConnection); + + conn_data.driverName = name(); + d->connections.insert( conn, conn ); + return conn; +} + +Connection* Driver::removeConnection( Connection *conn ) +{ + clearError(); + return d->connections.take( conn ); +} + +QString Driver::defaultSQLTypeName(int id_t) +{ + if (id_t>=Field::Null) + return "Null"; + if (dflt_typeNames.isEmpty()) { + dflt_typeNames.resize(Field::LastType + 1); + dflt_typeNames[Field::InvalidType]="InvalidType"; + dflt_typeNames[Field::Byte]="Byte"; + dflt_typeNames[Field::ShortInteger]="ShortInteger"; + dflt_typeNames[Field::Integer]="Integer"; + dflt_typeNames[Field::BigInteger]="BigInteger"; + dflt_typeNames[Field::Boolean]="Boolean"; + dflt_typeNames[Field::Date]="Date"; + dflt_typeNames[Field::DateTime]="DateTime"; + dflt_typeNames[Field::Time]="Time"; + dflt_typeNames[Field::Float]="Float"; + dflt_typeNames[Field::Double]="Double"; + dflt_typeNames[Field::Text]="Text"; + dflt_typeNames[Field::LongText]="LongText"; + dflt_typeNames[Field::BLOB]="BLOB"; + } + return dflt_typeNames[id_t]; +} + +bool Driver::isSystemObjectName( const QString& n ) const +{ + return Driver::isKexiDBSystemObjectName(n); +} + +bool Driver::isKexiDBSystemObjectName( const QString& n ) +{ + if (!n.lower().startsWith("kexi__")) + return false; + const QStringList list( Connection::kexiDBSystemTableNames() ); + return list.find(n.lower())!=list.constEnd(); +} + +bool Driver::isSystemFieldName( const QString& n ) const +{ + if (!beh->ROW_ID_FIELD_NAME.isEmpty() && n.lower()==beh->ROW_ID_FIELD_NAME.lower()) + return true; + return drv_isSystemFieldName(n); +} + +QString Driver::valueToSQL( uint ftype, const QVariant& v ) const +{ + if (v.isNull()) + return "NULL"; + switch (ftype) { + case Field::Text: + case Field::LongText: { + QString s = v.toString(); + return escapeString(s); //QString("'")+s.replace( '"', "\\\"" ) + "'"; + } + case Field::Byte: + case Field::ShortInteger: + case Field::Integer: + case Field::BigInteger: + return v.toString(); + case Field::Float: + case Field::Double: { + if (v.type()==QVariant::String) { + //workaround for values stored as string that should be casted to floating-point + QString s(v.toString()); + return s.replace(',', "."); + } + return v.toString(); + } +//TODO: here special encoding method needed + case Field::Boolean: + return QString::number(v.toInt()?1:0); //0 or 1 + case Field::Time: + return QString("\'")+v.toTime().toString(Qt::ISODate)+"\'"; + case Field::Date: + return QString("\'")+v.toDate().toString(Qt::ISODate)+"\'"; + case Field::DateTime: + return dateTimeToSQL( v.toDateTime() ); + case Field::BLOB: { + if (v.toByteArray().isEmpty()) + return QString::fromLatin1("NULL"); + if (v.type()==QVariant::String) + return escapeBLOB(v.toString().utf8()); + return escapeBLOB(v.toByteArray()); + } + case Field::InvalidType: + return "!INVALIDTYPE!"; + default: + KexiDBDbg << "Driver::valueToSQL(): UNKNOWN!" << endl; + return QString::null; + } + return QString::null; +} + +QVariant Driver::propertyValue( const QCString& propName ) const +{ + return d->properties[propName.lower()]; +} + +QString Driver::propertyCaption( const QCString& propName ) const +{ + return d->propertyCaptions[propName.lower()]; +} + +QValueList<QCString> Driver::propertyNames() const +{ + QValueList<QCString> names = d->properties.keys(); + qHeapSort(names); + return names; +} + +QString Driver::escapeIdentifier(const QString& str, int options) const +{ + QCString cstr = str.latin1(); + return QString(escapeIdentifier(cstr, options)); +} + +QCString Driver::escapeIdentifier(const QCString& str, int options) const +{ + bool needOuterQuotes = false; + +// Need to use quotes if ... +// ... we have been told to, or ... + if(options & EscapeAlways) + needOuterQuotes = true; + +// ... or if the driver does not have a list of keywords, + else if(!d->driverSQLDict) + needOuterQuotes = true; + +// ... or if it's a keyword in Kexi's SQL dialect, + else if(d->kexiSQLDict->find(str)) + needOuterQuotes = true; + +// ... or if it's a keyword in the backends SQL dialect, +// (have already checked !d->driverSQLDict) + else if((options & EscapeDriver) && d->driverSQLDict->find(str)) + needOuterQuotes = true; + +// ... or if the identifier has a space in it... + else if(str.find(' ') != -1) + needOuterQuotes = true; + + if(needOuterQuotes && (options & EscapeKexi)) { + const char quote = '"'; + return quote + QCString(str).replace( quote, "\"\"" ) + quote; + } + else if (needOuterQuotes) { + const char quote = beh->QUOTATION_MARKS_FOR_IDENTIFIER.latin1(); + return quote + drv_escapeIdentifier(str) + quote; + } else { + return drv_escapeIdentifier(str); + } +} + +void Driver::initSQLKeywords(int hashSize) { + + if(!d->driverSQLDict && beh->SQL_KEYWORDS != 0) { + d->initDriverKeywords(beh->SQL_KEYWORDS, hashSize); + } +} + +#include "driver.moc" diff --git a/kexi/kexidb/driver.h b/kexi/kexidb/driver.h new file mode 100644 index 00000000..ef946e65 --- /dev/null +++ b/kexi/kexidb/driver.h @@ -0,0 +1,375 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_DRIVER_H +#define KEXIDB_DRIVER_H + +#include <qobject.h> +#include <qdatetime.h> +#include <qdict.h> + +#include <kexidb/global.h> +#include <kexidb/object.h> +#include <kexidb/field.h> + +class KService; + +namespace KexiDB { + +class AdminTools; +class Connection; +class ConnectionData; +class ConnectionInternal; +class DriverManager; +class DriverBehaviour; +class DriverPrivate; + +//! Generic database abstraction. +/*! This class is a prototype of the database driver for implementations. + Driver allows new connections to be created, and groups + these as a parent. + Before destruction, all connections are destructed. + + Notes: + - driver must be provided within KDE module file named with "kexidb_" prefix + - following line should be placed in driver's implementation: + \code + KEXIDB_DRIVER_INFO( CLASS_NAME, INTERNAL_NAME ); + \endcode + where: + - CLASS_NAME is actual driver's class name, e.g. MySqlDriver + - INTERNAL_NAME is driver name's most significant part (without quotation marks), e.g. mysql + Above information uses K_EXPORT_COMPONENT_FACTORY macro for KTrader to find the module's entry point. + For example, this line declares kexidb_mysqldriver.so module's entry point: + \code + KEXIDB_DRIVER_INFO( MySqlDriver, mysql ); + \endcode + + \sa SQLiteDriver MySqlDriver, pqxxSqlDriver +*/ +class KEXI_DB_EXPORT Driver : public QObject, public KexiDB::Object +{ + Q_OBJECT + public: + /*! Helpful for retrieving info about driver from using + KexiDB::DriverManager::driversInfo() without loading driver libraries. */ + class Info { + public: + Info(); + QString name, caption, comment, fileDBMimeType; + //! true is the driver is for file-based database backend + bool fileBased : 1; + /*! true is the driver is for a backend that allows importing. + Defined by X-Kexi-DoNotAllowProjectImportingTo in "kexidb_driver" service type. + Used for migration. */ + bool allowImportingTo : 1; + }; + typedef QMap<QString,Info> InfoMap; + + /*! Features supported by driver (sum of few Features enum items). */ + enum Features { + NoFeatures = 0, + //! single trasactions are only supported + SingleTransactions = 1, + //! multiple concurrent trasactions are supported + //! (this implies !SingleTransactions) + MultipleTransactions = 2, +//(js) NOT YET IN USE: + /*! nested trasactions are supported + (this should imply !SingleTransactions and MultipleTransactions) */ + NestedTransactions = 4, + /*! forward moving is supported for cursors + (if not available, no cursors available at all) */ + CursorForward = 8, + /*! backward moving is supported for cursors (this implies CursorForward) */ + CursorBackward = (CursorForward+16), + /*! compacting database supported (aka VACUUM) */ + CompactingDatabaseSupported = 32, + //-- temporary options: can be removed later, use at your own risk -- + /*! If set, actions related to transactions will be silently bypassed + with success. Set this if your driver does not support transactions at all + Currently, this is only way to get it working with KexiDB. + Keep in mind that this hack do not provide data integrity! + This flag is currently used for MySQL driver. */ + IgnoreTransactions = 1024 + }; + + //! Options used for createConnection() + enum CreateConnectionOptions { + ReadOnlyConnection = 1 //!< set to perform read only connection + }; + + virtual ~Driver(); + + /*! Creates connection using \a conn_data as parameters. + \return 0 and sets error message on error. + driverName member of \a conn_data will be updated with this driver name. + \a options can be a combination of CreateConnectionOptions enum values. + */ + Connection *createConnection( ConnectionData &conn_data, int options = 0 ); + + /*! \return List of created connections. */ + const QPtrList<Connection> connectionsList() const; + +// /*! \return a name equal to the service name (X-Kexi-DriverName) +// stored in given service .desktop file. */ +// QString driverName() { return m_driverName; } + + /*! \return a name of MIME type of files handled by this driver + if it is a file-based database's driver + (equal X-Kexi-FileDBDriverMime service property) + otherwise returns null string. \sa isFileDriver() + */ + QString fileDBDriverMimeType() const; + + /*! \return default file-based driver mime type + (typically something like "application/x-kexiproject-sqlite") */ + static QString defaultFileBasedDriverMimeType(); + + /*! \return default file-based driver name (currently, "sqlite3"). */ + static QString defaultFileBasedDriverName(); + + /*! Info about the driver as a service. */ + const KService* service() const; + + /*! \return true if this driver is file-based */ + bool isFileDriver() const; + + /*! \return true if \a n is a system object's name, + eg. name of build-in system table that cannot be used or created by a user, + and in most cases user even shouldn't see this. The list is specific for + a given driver implementation. + By default calls Driver::isKexiDBSystemObjectName() static method. + Note for driver developers: Also call Driver::isSystemObjectName() + from your reimplementation. + \sa isSystemFieldName(). + */ + virtual bool isSystemObjectName( const QString& n ) const; + + /*! \return true if \a n is a kexibd-related 'system' object's + name, i.e. when \a n starts with "kexi__" prefix. + */ + static bool isKexiDBSystemObjectName( const QString& n ); + + /*! \return true if \a n is a system database's name, + eg. name of build-in, system database that cannot be used or created by a user, + and in most cases user even shouldn't see this. The list is specific for + a given driver implementation. For implementation. + \sa isSystemObjectName(). + */ + virtual bool isSystemDatabaseName( const QString& n ) const = 0; + + /*! \return true if \a n is a system field's name, build-in system + field that cannot be used or created by a user, + and in most cases user even shouldn't see this. The list is specific for + a given driver implementation. + \sa isSystemObjectName(). + */ + bool isSystemFieldName( const QString& n ) const; + + /*! \return Driver's features that are combination of Driver::Features + enum. */ + int features() const; + + /*! \return true if transaction are supported (single or + multiple). */ + bool transactionsSupported() const; + + /*! \return admin tools object providing a number of database administration + tools for the driver. Tools availablility varies from driver to driver. + You can check it using features(). */ + AdminTools& adminTools() const; + + /*! SQL-implementation-dependent name of given type */ + virtual QString sqlTypeName(int id_t, int p=0) const; + + /*! used when we do not have Driver instance yet */ + static QString defaultSQLTypeName(int id_t); + + /*! \return true if this driver's implementation is valid. + Just few constriants are checked to ensure that driver + developer didn't forget about something. + This method is called automatically on createConnection(), + and proper error message is set properly on any error. */ + virtual bool isValid(); + + /*! Driver's static version information (major part), it is automatically defined + in implementation by KEXIDB_DRIVER macro (see driver_p.h) + It's usually compared to drivers' and KexiDB library version. */ + virtual DatabaseVersionInfo version() const = 0; + + /*! Escapes and converts value \a v (for type \a ftype) + to string representation required by SQL commands. + Reimplement this if you need other behaviour (eg. for 'date' type handling) + This implementation return date, datetime and time values in ISO format, + what seems to be accepted by SQL servers. + @see Qt::DateFormat */ + virtual QString valueToSQL( uint ftype, const QVariant& v ) const; + + //! Like above but with the fildtype as string. + inline QString valueToSQL( const QString& ftype, const QVariant& v ) const { + return valueToSQL(Field::typeForString(ftype), v); + } + + //! Like above method, for \a field. + inline QString valueToSQL( const Field *field, const QVariant& v ) const { + return valueToSQL( (field ? field->type() : Field::InvalidType), v ); + } + + /*! not compatible with all drivers - reimplement */ + inline virtual QString dateTimeToSQL(const QDateTime& v) const { + + /*! (was compatible with SQLite: http://www.sqlite.org/cvstrac/wiki?p=DateAndTimeFunctions) + Now it's ISO 8601 DateTime format - with "T" delimiter: + http://www.w3.org/TR/NOTE-datetime + (e.g. "1994-11-05T13:15:30" not "1994-11-05 13:15:30") + @todo add support for time zones? + */ +//old const QDateTime dt( v.toDateTime() ); +//old return QString("\'")+dt.date().toString(Qt::ISODate)+" "+dt.time().toString(Qt::ISODate)+"\'"; + return QString("\'")+v.toString(Qt::ISODate)+"\'"; + } + + /*! Driver-specific SQL string escaping. + Implement escaping for any character like " or ' as your + database engine requires. Prepend and append quotation marks. + */ + virtual QString escapeString( const QString& str ) const = 0; + + /*! This is overloaded version of escapeString( const QString& str ) + to be implemented in the same way. + */ + virtual QCString escapeString( const QCString& str ) const = 0; + + /*! Driver-specific SQL BLOB value escaping. + Implement escaping for any character like " or ' and \\0 as your + database engine requires. Prepend and append quotation marks. + */ + virtual QString escapeBLOB(const QByteArray& array) const = 0; + +//todo enum EscapeType { EscapeDriver = 0x00, EscapeKexi = 0x01}; +//todo enum EscapePolicy { EscapeAsNecessary = 0x00, EscapeAlways = 0x02 }; + + enum EscapeType { EscapeDriver = 0x01, EscapeKexi = 0x02}; + + enum EscapePolicy { EscapeAsNecessary = 0x04, EscapeAlways = 0x08 }; + + //! Driver-specific identifier escaping (e.g. for a table name, db name, etc.) + /*! Escape database identifier (\a str) in order that keywords + can be used as table names, column names, etc. + \a options is the union of the EscapeType and EscapePolicy types. + If no escaping options are given, defaults to driver escaping as + necessary. */ + QString escapeIdentifier( const QString& str, + int options = EscapeDriver|EscapeAsNecessary) const; + + QCString escapeIdentifier( const QCString& str, + int options = EscapeDriver|EscapeAsNecessary) const; + + //! \return property value for \a propeName available for this driver. + //! If there's no such property defined for driver, Null QVariant value is returned. + QVariant propertyValue( const QCString& propName ) const; + + //! \return translated property caption for \a propeName. + //! If there's no such property defined for driver, empty string value is returned. + QString propertyCaption( const QCString& propName ) const; + + //! \return a list of property names available for this driver. + QValueList<QCString> propertyNames() const; + + protected: + /*! Used by DriverManager. + Note for driver developers: Reimplement this. + In your reimplementation you should initialize: + - d->typeNames - to types accepted by your engine + - d->isFileDriver - to true or false depending if your driver is file-based + - d->features - to combination of selected values from Features enum + + You may also want to change options in DriverBehaviour *beh member. + See drivers/mySQL/mysqldriver.cpp for usage example. + */ + Driver( QObject *parent, const char *name, const QStringList &args = QStringList() ); + + /*! For reimplemenation: creates and returns connection object + with additional structures specific for a given driver. + Connection object should inherit Connection and have a destructor + that descructs all allocated driver-dependent connection structures. */ + virtual Connection *drv_createConnection( ConnectionData &conn_data ) = 0; +//virtual ConnectionInternal* createConnectionInternalObject( Connection& conn ) = 0; + + /*! Driver-specific SQL string escaping. + This method is used by escapeIdentifier(). + Implement escaping for any character like " or ' as your + database engine requires. Do not append or prepend any quotation + marks characters - it is automatically done by escapeIdentifier() using + DriverBehaviour::QUOTATION_MARKS_FOR_IDENTIFIER. + */ + virtual QString drv_escapeIdentifier( const QString& str ) const = 0; + + /*! This is overloaded version of drv_escapeIdentifier( const QString& str ) + to be implemented in the same way. + */ + virtual QCString drv_escapeIdentifier( const QCString& str ) const = 0; + + /*! \return true if \a n is a system field's name, build-in system + field that cannot be used or created by a user, + and in most cases user even shouldn't see this. The list is specific for + a given driver implementation. For implementation.*/ + virtual bool drv_isSystemFieldName( const QString& n ) const = 0; + + /* Creates admin tools object providing a number of database administration + tools for the driver. This is called once per driver. + + Note for driver developers: Reimplement this method by returning + KexiDB::AdminTools-derived object. Default implementation creates + empty admin tools. + @see adminTools() */ + virtual AdminTools* drv_createAdminTools() const; + + /*! \return connection \a conn , do not deletes it nor affect. + Returns 0 if \a conn is not owned by this driver. + After this, you are owner of \a conn object, so you should + eventually delete it. Better use Connection destructor. */ + Connection* removeConnection( Connection *conn ); + + friend class Connection; + friend class Cursor; + friend class DriverManagerInternal; + + + /*! Used to initialise the dictionary of driver-specific keywords. + Should be called by the Driver's constructor. + \a hashSize is the number of buckets to use in the dictionary. + \sa DriverPrivate::SQL_KEYWORDS. */ + void initSQLKeywords(int hashSize = 17); + + DriverBehaviour *beh; + DriverPrivate *d; +}; + +} //namespace KexiDB + +/*! Driver's static version information, automatically impemented for KexiDB drivers. + Put this into driver class declaration just like Q_OBJECT macro. */ +#define KEXIDB_DRIVER \ + public: \ + virtual DatabaseVersionInfo version() const; + +#endif + diff --git a/kexi/kexidb/driver_p.cpp b/kexi/kexidb/driver_p.cpp new file mode 100644 index 00000000..2ad5f9ce --- /dev/null +++ b/kexi/kexidb/driver_p.cpp @@ -0,0 +1,129 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + Copyright (C) 2004 Martin Ellis <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kdebug.h> +#include <qdict.h> +#include <qvaluevector.h> +#include "driver_p.h" + +using namespace KexiDB; + +namespace KexiDB { + QAsciiDict<bool>* DriverPrivate::kexiSQLDict = 0; + + /*! QAsciiDict keys need to be a pointer to *something*. Used + for SQL keyword dictionaries + */ + static bool _dummy; +} + + +DriverPrivate::DriverPrivate() + : isFileDriver(false) + , isDBOpenedAfterCreate(false) + , features(Driver::NoFeatures) +{ + kexiSQLDict = 0; + driverSQLDict = 0; + adminTools = 0; + + properties["client_library_version"] = ""; + propertyCaptions["client_library_version"] = + i18n("Client library version"); + + properties["default_server_encoding"] = ""; + propertyCaptions["default_server_encoding"] = + i18n("Default character encoding on server"); +} + +void DriverPrivate::initInternalProperties() +{ + properties["is_file_database"] = QVariant(isFileDriver, 1); + propertyCaptions["is_file_database"] = i18n("File-based database driver"); + if (isFileDriver) { + properties["file_database_mimetype"] = fileDBDriverMimeType; + propertyCaptions["file_database_mimetype"] = i18n("File-based database's MIME type"); + } + +#if 0 + QString str; + if (features & Driver::SingleTransactions) + str = i18n("Single transactions"); + else if (features & Driver::MultipleTransactions) + str = i18n("Multiple transactions"); + else if (features & Driver::NestedTransactions) + str = i18n("Nested transactions"); + else if (features & Driver::IgnoreTransactions) + str = i18n("Ignored"); + else + str = i18n("None"); +#endif +// properties["transaction_support"] = features & Driver::TransactionsMask; +// propertyCaptions["transaction_support"] = i18n("Transaction support"); + properties["transaction_single"] = QVariant(features & Driver::SingleTransactions, 1); + propertyCaptions["transaction_single"] = i18n("Single transactions support"); + properties["transaction_multiple"] = QVariant(features & Driver::MultipleTransactions, 1); + propertyCaptions["transaction_multiple"] = i18n("Multiple transactions support"); + properties["transaction_nested"] = QVariant(features & Driver::NestedTransactions, 1); + propertyCaptions["transaction_nested"] = i18n("Nested transactions support"); + + properties["kexidb_driver_version"] = + QString("%1.%2").arg(version().major).arg(version().minor); + propertyCaptions["kexidb_driver_version"] = + i18n("KexiDB driver version"); +} + +DriverPrivate::~DriverPrivate() +{ + delete driverSQLDict; + delete adminTools; +} + + +void DriverPrivate::initKexiKeywords() { + // QAsciiDict constructor args: + // size (preferable prime) + // case sensitive flag (false) + // copy strings (false) + if(!kexiSQLDict) { + kexiSQLDict = new QAsciiDict<bool>(79, false, false); + initKeywords(kexiSQLKeywords, *kexiSQLDict); + } +} + +void DriverPrivate::initDriverKeywords(const char* keywords[], int hashSize) { + driverSQLDict = new QAsciiDict<bool>(hashSize, false, false); + initKeywords(keywords, *driverSQLDict); +} + +void DriverPrivate::initKeywords(const char* keywords[], + QAsciiDict<bool>& dict) { + for(int i = 0; keywords[i] != 0; i++) { + dict.insert(keywords[i], &_dummy); + } +} + +AdminTools::Private::Private() +{ +} + +AdminTools::Private::~Private() +{ +} diff --git a/kexi/kexidb/driver_p.h b/kexi/kexidb/driver_p.h new file mode 100644 index 00000000..44ecd617 --- /dev/null +++ b/kexi/kexidb/driver_p.h @@ -0,0 +1,262 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_DRIVER_P_H +#define KEXIDB_DRIVER_P_H + +#ifndef __KEXIDB__ +# error "Do not include: this is KexiDB internal file" +#endif + +#include <qstring.h> +#include <qvariant.h> +#include <qmap.h> +#include <qptrdict.h> +#include <qasciidict.h> +#include <qvaluevector.h> +#include <kgenericfactory.h> + +#include "connection.h" +#include "admin.h" + +class KService; + +namespace KexiDB { + +/*! Detailed definition of driver's default behaviour. + Note for driver developers: + Change these defaults in you Driver subclass + constructor, if needed. +*/ +class KEXI_DB_EXPORT DriverBehaviour +{ + public: + DriverBehaviour(); + + //! "UNSIGNED" by default + QString UNSIGNED_TYPE_KEYWORD; + + //! "AUTO_INCREMENT" by default, used as add-in word to field definition + //! May be also used as full definition if SPECIAL_AUTO_INCREMENT_DEF is true. + QString AUTO_INCREMENT_FIELD_OPTION; + + //! "AUTO_INCREMENT PRIMARY KEY" by default, used as add-in word to field definition + //! May be also used as full definition if SPECIAL_AUTO_INCREMENT_DEF is true. + QString AUTO_INCREMENT_PK_FIELD_OPTION; + + //! "" by default, used as type string for autoinc. field definition + //! pgsql defines it as "SERIAL", sqlite defines it as "INTEGER" + QString AUTO_INCREMENT_TYPE; + + /*! True if autoincrement field has special definition + e.g. like "INTEGER PRIMARY KEY" for SQLite. + Special definition string should be stored in AUTO_INCREMENT_FIELD_OPTION. + False by default. */ + bool SPECIAL_AUTO_INCREMENT_DEF : 1; + + /*! True if autoincrement requires field to be declared as primary key. + This is true for SQLite. False by default. */ + bool AUTO_INCREMENT_REQUIRES_PK : 1; + + /*! Name of a field (or built-in function) with autoincremented unique value, + typically returned by Connection::drv_lastInsertRowID(). + + Examples: + - PostgreSQL and SQLite engines use 'OID' field + - MySQL uses LAST_INSERT_ID() built-in function + */ + QString ROW_ID_FIELD_NAME; + + /*! True if the value (fetched from field or function, + defined by ROW_ID_FIELD_NAME member) is EXACTLY the value of autoincremented field, + not an implicit (internal) row number. Default value is false. + + Examples: + - PostgreSQL and SQLite engines have this flag set to false ('OID' field has + it's own implicit value) + - MySQL engine has this flag set to true (LAST_INSERT_ID() returns real value + of last autoincremented field). + + Notes: + If it's false, we have a convenient way for identifying row even when there's + no primary key defined. So, as '_ROWID' column in MySQL is really + just a synonym for the primary key, this engine needs to have primary keys always + defined if we want to use interactive editing features like row updating and deleting. + */ + bool ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE : 1; + + /*! Name of any (e.g. first found) database for this connection that + typically always exists. This can be not set if we want to do some magic checking + what database name is availabe by reimplementing + Connection::anyAvailableDatabaseName(). + Example: for PostgreSQL this is "template1". + + \sa Connection::SetAvailableDatabaseName() + */ + QString ALWAYS_AVAILABLE_DATABASE_NAME; + + /*! Quotation marks used for escaping identifier (see Driver::escapeIdentifier()). + Default value is '"'. Change it for your driver. + */ + QChar QUOTATION_MARKS_FOR_IDENTIFIER; + + /*! True if using database is requied to perform real connection. + This is true for may engines, e.g. for PostgreSQL, where connections + string should contain a database name. + This flag is unused for file-based db drivers, + by default set to true and used for all other db drivers. + */ + bool USING_DATABASE_REQUIRED_TO_CONNECT : 1; + + /*! True if before we know whether the fetched result of executed query + is empty or not, we need to fetch first record. Particularly, it's true for SQLite. + The flag is used in Cursor::open(). By default this flag is false. */ + bool _1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY : 1; + + /*! True if "SELECT 1 from (subquery)" is supported. False by default. + Used in Connection::resultExists() for optimization. It's set to true for SQLite driver. */ + bool SELECT_1_SUBQUERY_SUPPORTED : 1; + + /*! Keywords that need to be escaped for the driver. Set this before calling + Driver::initSQLKeywords. */ + const char** SQL_KEYWORDS; +}; + +/*! Private driver's data members. Available for implementation. */ +class DriverPrivate +{ + public: + DriverPrivate(); + virtual ~DriverPrivate(); + + QPtrDict<Connection> connections; + +//(js)now QObject::name() is reused: +// /*! The name equal to the service name (X-Kexi-DriverName) +// stored in given service .desktop file. Set this in subclasses. */ +// QString m_driverName; + + /*! Name of MIME type of files handled by this driver + if it is a file-based database's driver + (equal X-Kexi-FileDBDriverMime service property) */ + QString fileDBDriverMimeType; + + /*! Info about the driver as a service. */ + KService *service; + + /*! Internal constant flag: Set this in subclass if driver is a file driver */ + bool isFileDriver : 1; + + /*! Internal constant flag: Set this in subclass if after successful + drv_createDatabased() database is in opened state (as after useDatabase()). + For most engines this is not true. */ + bool isDBOpenedAfterCreate : 1; + + /*! List of system objects names, eg. build-in system tables that + cannot be used by user, and in most cases user even shouldn't see these. + The list contents is driver dependent (by default is empty) + - fill this in subclass ctor. */ +// QStringList m_systemObjectNames; + + /*! List of system fields names, build-in system fields that cannot be used by user, + and in most cases user even shouldn't see these. + The list contents is driver dependent (by default is empty) - fill this in subclass ctor. */ +// QStringList m_systemFieldNames; + + /*! Features (like transactions, etc.) supported by this driver + (sum of selected Features enum items). + This member should be filled in driver implementation's constructor + (by default m_features==NoFeatures). */ + int features; + + //! real type names for this engine + QValueVector<QString> typeNames; + + /*! Driver properties dictionary (indexed by name), + useful for presenting properties to the user. + Set available properties here in driver implementation. */ + QMap<QCString,QVariant> properties; + + /*! i18n'd captions for properties. You do not need + to set predefined properties' caption in driver implementation + -it's done automatically. */ + QMap<QCString,QString> propertyCaptions; + + /*! Provides a number of database administration tools for the driver. */ + AdminTools *adminTools; + + /*! Kexi SQL keywords that need to be escaped if used as an identifier (e.g. + for a table or column name). These keywords will be escaped by the + front-end, even if they are not recognised by the backend to provide + UI consistency and to allow DB migration without changing the queries. + \sa DriverPrivate::initKexiKeywords(), KexiDB::kexiSQLKeywords. + */ + static QAsciiDict<bool>* kexiSQLDict; + static const char *kexiSQLKeywords[]; + + /*! Driver-specific SQL keywords that need to be escaped if used as an + identifier (e.g. for a table or column name) that aren't also Kexi SQL + keywords. These don't neccesarily need to be escaped when displayed by + the front-end, because they won't confuse the parser. However, they do + need to be escaped before sending to the DB-backend which will have + it's own parser. + + \sa DriverBehaviour::SQL_KEYWORDS. + */ + QAsciiDict<bool>* driverSQLDict; + + /*! Initialise the dictionary of Kexi SQL keywords used for escaping. */ + void initKexiKeywords(); + /*! Initialise the dictionary of driver-specific keywords used for escaping. + \a hashSize is the number of buckets to use in the dictionary. + \sa Driver::initSQLKeywords(). */ + void initDriverKeywords(const char* keywords[], int hashSize); + + protected: + /*! Used by driver manager to initialize properties taken using internal + driver flags. */ + void initInternalProperties(); + + private: + void initKeywords(const char* keywords[], QAsciiDict<bool>& dict); + + friend class DriverManagerInternal; +}; + +// escaping types for Driver::escapeBLOBInternal() +#define BLOB_ESCAPING_TYPE_USE_X 0 //!< escaping like X'abcd0', used by sqlite +#define BLOB_ESCAPING_TYPE_USE_0x 1 //!< escaping like 0xabcd0, used by mysql +#define BLOB_ESCAPING_TYPE_USE_OCTAL 2 //!< escaping like 'abcd\\000', used by pgsql + +class KEXI_DB_EXPORT AdminTools::Private +{ + public: + Private(); + ~Private(); +}; + +} + +//! Driver's static version information (implementation), +//! with KLibFactory symbol declaration. +#define KEXIDB_DRIVER_INFO( class_name, internal_name ) \ + DatabaseVersionInfo class_name::version() const { return KEXIDB_VERSION; } \ + K_EXPORT_COMPONENT_FACTORY(kexidb_ ## internal_name ## driver, KGenericFactory<KexiDB::class_name>( "kexidb_" #internal_name )) + +#endif diff --git a/kexi/kexidb/drivermanager.cpp b/kexi/kexidb/drivermanager.cpp new file mode 100644 index 00000000..e7e5b2bb --- /dev/null +++ b/kexi/kexidb/drivermanager.cpp @@ -0,0 +1,435 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Daniel Molkentin <[email protected]> + Copyright (C) 2003 Joseph Wenninger <[email protected]> + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/drivermanager.h> +#include <kexidb/drivermanager_p.h> + +#include <kexidb/driver.h> +#include <kexidb/driver_p.h> +#include <kexidb/error.h> + +#include <klibloader.h> +#include <kparts/componentfactory.h> +#include <ktrader.h> +#include <kdebug.h> +#include <klocale.h> +#include <kservice.h> + +#include <assert.h> + +#include <qapplication.h> + +//remove debug +#undef KexiDBDbg +#define KexiDBDbg if (0) kdDebug() + +using namespace KexiDB; + +DriverManagerInternal* DriverManagerInternal::s_self = 0L; + + +DriverManagerInternal::DriverManagerInternal() /* protected */ + : QObject( 0, "KexiDB::DriverManager" ) + , Object() + , m_drivers(17, false) + , m_refCount(0) + , lookupDriversNeeded(true) +{ + m_drivers.setAutoDelete(true); + m_serverResultNum=0; + +} + +DriverManagerInternal::~DriverManagerInternal() +{ + KexiDBDbg << "DriverManagerInternal::~DriverManagerInternal()" << endl; + m_drivers.clear(); + if ( s_self == this ) + s_self = 0; + KexiDBDbg << "DriverManagerInternal::~DriverManagerInternal() ok" << endl; +} + +void DriverManagerInternal::slotAppQuits() +{ + if (qApp->mainWidget() && qApp->mainWidget()->isVisible()) + return; //what a hack! - we give up when app is still there + KexiDBDbg << "DriverManagerInternal::slotAppQuits(): let's clear drivers..." << endl; + m_drivers.clear(); +} + +DriverManagerInternal *DriverManagerInternal::self() +{ + if (!s_self) + s_self = new DriverManagerInternal(); + + return s_self; +} + +bool DriverManagerInternal::lookupDrivers() +{ + if (!lookupDriversNeeded) + return true; + + if (qApp) { + connect(qApp,SIGNAL(aboutToQuit()),this,SLOT(slotAppQuits())); + } +//TODO: for QT-only version check for KInstance wrapper +// KexiDBWarn << "DriverManagerInternal::lookupDrivers(): cannot work without KInstance (KGlobal::instance()==0)!" << endl; +// setError("Driver Manager cannot work without KInstance (KGlobal::instance()==0)!"); + + lookupDriversNeeded = false; + clearError(); + KTrader::OfferList tlist = KTrader::self()->query("Kexi/DBDriver"); + KTrader::OfferList::ConstIterator it(tlist.constBegin()); + for(; it != tlist.constEnd(); ++it) + { + KService::Ptr ptr = (*it); + if (!ptr->property("Library").toString().startsWith("kexidb_")) { + KexiDBWarn << "DriverManagerInternal::lookupDrivers():" + " X-KDE-Library == " << ptr->property("Library").toString() + << ": no \"kexidb_\" prefix -- skipped to avoid potential conflicts!" << endl; + continue; + } + QString srv_name = ptr->property("X-Kexi-DriverName").toString(); + if (srv_name.isEmpty()) { + KexiDBWarn << "DriverManagerInternal::lookupDrivers():" + " X-Kexi-DriverName must be set for KexiDB driver \"" + << ptr->property("Name").toString() << "\" service!\n -- skipped!" << endl; + continue; + } + if (m_services_lcase.contains(srv_name.lower())) { + KexiDBWarn << "DriverManagerInternal::lookupDrivers(): more than one driver named '" + << srv_name.lower() << "'\n -- skipping this one!" << endl; + continue; + } + + QString srv_ver_str = ptr->property("X-Kexi-KexiDBVersion").toString(); + QStringList lst( QStringList::split(".", srv_ver_str) ); + uint minor_ver, major_ver; + bool ok = (lst.count() == 2); + if (ok) + major_ver = lst[0].toUInt(&ok); + if (ok) + minor_ver = lst[1].toUInt(&ok); + if (!ok) { + KexiDBWarn << "DriverManagerInternal::lookupDrivers(): problem with detecting '" + << srv_name.lower() << "' driver's version -- skipping it!" << endl; + continue; + } + if (major_ver != KexiDB::version().major || minor_ver != KexiDB::version().minor) { + KexiDBWarn << QString("DriverManagerInternal::lookupDrivers(): '%1' driver" + " has version '%2' but required KexiDB driver version is '%3.%4'\n" + " -- skipping this driver!").arg(srv_name.lower()).arg(srv_ver_str) + .arg(KexiDB::version().major).arg(KexiDB::version().minor) << endl; + possibleProblems += QString("\"%1\" database driver has version \"%2\" " + "but required driver version is \"%3.%4\"") + .arg(srv_name.lower()).arg(srv_ver_str) + .arg(KexiDB::version().major).arg(KexiDB::version().minor); + continue; + } + + QString drvType = ptr->property("X-Kexi-DriverType").toString().lower(); + if (drvType=="file") { + //new property: a list of supported mime types + QStringList mimes( ptr->property("X-Kexi-FileDBDriverMimeList").toStringList() ); + //single mime is obsolete, but we're handling it: + { + QString mime( ptr->property("X-Kexi-FileDBDriverMime").toString().lower() ); + if (!mime.isEmpty()) + mimes.append( mime ); + } + + //store association of this driver with all listed mime types + for (QStringList::ConstIterator mime_it = mimes.constBegin(); mime_it!=mimes.constEnd(); ++mime_it) { + QString mime( (*mime_it).lower() ); + if (!m_services_by_mimetype.contains(mime)) { + m_services_by_mimetype.insert(mime, ptr); + } + else { + KexiDBWarn << "DriverManagerInternal::lookupDrivers(): more than one driver for '" + << mime << "' mime type!" << endl; + } + } + } + m_services.insert(srv_name, ptr); + m_services_lcase.insert(srv_name.lower(), ptr); + KexiDBDbg << "KexiDB::DriverManager::lookupDrivers(): registered driver: " << ptr->name() << "(" << ptr->library() << ")" << endl; + } + + if (tlist.isEmpty()) + { + setError(ERR_DRIVERMANAGER, i18n("Could not find any database drivers.") ); + return false; + } + return true; +} + +KexiDB::Driver::Info DriverManagerInternal::driverInfo(const QString &name) +{ + KexiDB::Driver::Info i = m_driversInfo[name.lower()]; + if (!error() && i.name.isEmpty()) + setError(ERR_DRIVERMANAGER, i18n("Could not find database driver \"%1\".").arg(name) ); + return i; +} + +Driver* DriverManagerInternal::driver(const QString& name) +{ + if (!lookupDrivers()) + return 0; + + clearError(); + KexiDBDbg << "DriverManager::driver(): loading " << name << endl; + + Driver *drv = name.isEmpty() ? 0 : m_drivers.find(name.latin1()); + if (drv) + return drv; //cached + + if (!m_services_lcase.contains(name.lower())) { + setError(ERR_DRIVERMANAGER, i18n("Could not find database driver \"%1\".").arg(name) ); + return 0; + } + + KService::Ptr ptr= *(m_services_lcase.find(name.lower())); + QString srv_name = ptr->property("X-Kexi-DriverName").toString(); + + KexiDBDbg << "KexiDBInterfaceManager::load(): library: "<<ptr->library()<<endl; + drv = KParts::ComponentFactory::createInstanceFromService<KexiDB::Driver>(ptr, + this, srv_name.latin1(), QStringList(),&m_serverResultNum); + + if (!drv) { + setError(ERR_DRIVERMANAGER, i18n("Could not load database driver \"%1\".") + .arg(name) ); + if (m_componentLoadingErrors.isEmpty()) {//fill errtable on demand + m_componentLoadingErrors[KParts::ComponentFactory::ErrNoServiceFound]="ErrNoServiceFound"; + m_componentLoadingErrors[KParts::ComponentFactory::ErrServiceProvidesNoLibrary]="ErrServiceProvidesNoLibrary"; + m_componentLoadingErrors[KParts::ComponentFactory::ErrNoLibrary]="ErrNoLibrary"; + m_componentLoadingErrors[KParts::ComponentFactory::ErrNoFactory]="ErrNoFactory"; + m_componentLoadingErrors[KParts::ComponentFactory::ErrNoComponent]="ErrNoComponent"; + } + m_serverResultName=m_componentLoadingErrors[m_serverResultNum]; + return 0; + } + KexiDBDbg << "KexiDBInterfaceManager::load(): loading succeed: " << name <<endl; +// KexiDBDbg << "drv="<<(long)drv <<endl; + +// drv->setName(srv_name.latin1()); + drv->d->service = ptr.data(); //store info + drv->d->fileDBDriverMimeType = ptr->property("X-Kexi-FileDBDriverMime").toString(); + drv->d->initInternalProperties(); + + if (!drv->isValid()) { + setError(drv); + delete drv; + return 0; + } + m_drivers.insert(name.latin1(), drv); //cache it + return drv; +} + +void DriverManagerInternal::incRefCount() +{ + m_refCount++; + KexiDBDbg << "DriverManagerInternal::incRefCount(): " << m_refCount << endl; +} + +void DriverManagerInternal::decRefCount() +{ + m_refCount--; + KexiDBDbg << "DriverManagerInternal::decRefCount(): " << m_refCount << endl; +// if (m_refCount<1) { +// KexiDBDbg<<"KexiDB::DriverManagerInternal::decRefCount(): reached m_refCount<1 -->deletelater()"<<endl; +// s_self=0; +// deleteLater(); +// } +} + +void DriverManagerInternal::aboutDelete( Driver* drv ) +{ + m_drivers.take(drv->name()); +} + + + +// --------------------------- +// --- DriverManager impl. --- +// --------------------------- + +DriverManager::DriverManager() + : QObject( 0, "KexiDB::DriverManager" ) + , Object() + , d_int( DriverManagerInternal::self() ) +{ + d_int->incRefCount(); +// if ( !s_self ) +// s_self = this; +// lookupDrivers(); +} + +DriverManager::~DriverManager() +{ + KexiDBDbg << "DriverManager::~DriverManager()" << endl; +/* Connection *conn; + for ( conn = m_connections.first(); conn ; conn = m_connections.next() ) { + conn->disconnect(); + conn->m_driver = 0; //don't let the connection touch our driver now + m_connections.remove(); + delete conn; + }*/ + + d_int->decRefCount(); + if (d_int->m_refCount==0) { + //delete internal drv manager! + delete d_int; + } +// if ( s_self == this ) + //s_self = 0; + KexiDBDbg << "DriverManager::~DriverManager() ok" << endl; +} + +const KexiDB::Driver::InfoMap DriverManager::driversInfo() +{ + if (!d_int->lookupDrivers()) + return KexiDB::Driver::InfoMap(); + + if (!d_int->m_driversInfo.isEmpty()) + return d_int->m_driversInfo; + ServicesMap::ConstIterator it; + for ( it=d_int->m_services.constBegin() ; it != d_int->m_services.constEnd(); ++it ) { + Driver::Info info; + KService::Ptr ptr = it.data(); + info.name = ptr->property("X-Kexi-DriverName").toString(); + info.caption = ptr->property("Name").toString(); + info.comment = ptr->property("Comment").toString(); + if (info.caption.isEmpty()) + info.caption = info.name; + info.fileBased = (ptr->property("X-Kexi-DriverType").toString().lower()=="file"); + if (info.fileBased) + info.fileDBMimeType = ptr->property("X-Kexi-FileDBDriverMime").toString().lower(); + QVariant v = ptr->property("X-Kexi-DoNotAllowProjectImportingTo"); + info.allowImportingTo = v.isNull() ? true : !v.toBool(); + d_int->m_driversInfo.insert(info.name.lower(), info); + } + return d_int->m_driversInfo; +} + +const QStringList DriverManager::driverNames() +{ + if (!d_int->lookupDrivers()) + return QStringList(); + + if (d_int->m_services.isEmpty() && d_int->error()) + return QStringList(); + return d_int->m_services.keys(); +} + +KexiDB::Driver::Info DriverManager::driverInfo(const QString &name) +{ + driversInfo(); + KexiDB::Driver::Info i = d_int->driverInfo(name); + if (d_int->error()) + setError(d_int); + return i; +} + +KService::Ptr DriverManager::serviceInfo(const QString &name) +{ + if (!d_int->lookupDrivers()) { + setError(d_int); + return KService::Ptr(); + } + + clearError(); + if (d_int->m_services_lcase.contains(name.lower())) { + return *d_int->m_services_lcase.find(name.lower()); + } else { + setError(ERR_DRIVERMANAGER, i18n("No such driver service: \"%1\".").arg(name) ); + return KService::Ptr(); + } +} + +const DriverManager::ServicesMap& DriverManager::services() +{ + d_int->lookupDrivers(); + + return d_int->m_services; +} + +QString DriverManager::lookupByMime(const QString &mimeType) +{ + if (!d_int->lookupDrivers()) { + setError(d_int); + return 0; + } + + KService::Ptr ptr = d_int->m_services_by_mimetype[mimeType.lower()]; + if (!ptr) + return QString::null; + return ptr->property("X-Kexi-DriverName").toString(); +} + +Driver* DriverManager::driver(const QString& name) +{ + Driver *drv = d_int->driver(name); + if (d_int->error()) + setError(d_int); + return drv; +} + +QString DriverManager::serverErrorMsg() +{ + return d_int->m_serverErrMsg; +} + +int DriverManager::serverResult() +{ + return d_int->m_serverResultNum; +} + +QString DriverManager::serverResultName() +{ + return d_int->m_serverResultName; +} + +void DriverManager::drv_clearServerResult() +{ + d_int->m_serverErrMsg=QString::null; + d_int->m_serverResultNum=0; + d_int->m_serverResultName=QString::null; +} + +QString DriverManager::possibleProblemsInfoMsg() const +{ + if (d_int->possibleProblems.isEmpty()) + return QString::null; + QString str; + str.reserve(1024); + str = "<ul>"; + for (QStringList::ConstIterator it = d_int->possibleProblems.constBegin(); + it!=d_int->possibleProblems.constEnd(); ++it) + { + str += (QString::fromLatin1("<li>") + *it + QString::fromLatin1("</li>")); + } + str += "</ul>"; + return str; +} + +#include "drivermanager_p.moc" + diff --git a/kexi/kexidb/drivermanager.h b/kexi/kexidb/drivermanager.h new file mode 100644 index 00000000..fa78c841 --- /dev/null +++ b/kexi/kexidb/drivermanager.h @@ -0,0 +1,104 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Daniel Molkentin <[email protected]> + Copyright (C) 2003 Joseph Wenninger <[email protected]> + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_DRIVER_MNGR_H +#define KEXIDB_DRIVER_MNGR_H + +#include <qobject.h> +#include <qcstring.h> +#include <qmap.h> +#include <qdict.h> + +#include <klibloader.h> +#include <kservice.h> + +#include <kexidb/driver.h> + +namespace KexiDB { + +class DriverManagerInternal; +class Connection; +class ConnectionData; + +//! Database driver management, e.g. finding and loading drivers. +class KEXI_DB_EXPORT DriverManager : public QObject, public KexiDB::Object +{ + public: + typedef QMap<QString, KService::Ptr> ServicesMap; + + DriverManager(); + virtual ~DriverManager(); + + /*! Tries to load db driver with named name \a name. + The name is case insensitive. + \return db driver, or 0 if error (then error message is also set) */ + Driver* driver(const QString& name); + + /*! returns list of available drivers names. + That drivers can be loaded by first use of driver() method. */ + const QStringList driverNames(); + + /*! returns information list of available drivers. + That drivers can be loaded by first use of driver() method. */ + const KexiDB::Driver::InfoMap driversInfo(); + + /*! \return information about driver's named with \a name. + The name is case insensitive. + You can check if driver information is not found calling + Info::name.isEmpty() (then error message is also set). */ + KexiDB::Driver::Info driverInfo(const QString &name); + + /*! \return service information about driver's named with \a name. + The name is case insensitive. + In most cases you can use driverInfo() instead. */ + KService::Ptr serviceInfo(const QString &name); + + /*! \return a map structure of the services. Not necessary for everyday use. */ + const ServicesMap& services(); + + /*! Looks up a drivers list by MIME type of database file. + Only file-based database drivers are checked. + The lookup is case insensitive. + \return driver name or null string if no driver found. + */ + QString lookupByMime(const QString &mimeType); + + //! server error is set if there is error at KService level (useful for debugging) + virtual QString serverErrorMsg(); + virtual int serverResult(); + virtual QString serverResultName(); + + /*! HTML information about possible problems encountered. + It's displayed in 'details' section, if an error encountered. + Currently it contains a list of incompatible db drivers. + Used in KexiStartupHandler::detectDriverForFile(). */ + QString possibleProblemsInfoMsg() const; + + protected: + virtual void drv_clearServerResult(); + + private: + DriverManagerInternal *d_int; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/drivermanager_p.h b/kexi/kexidb/drivermanager_p.h new file mode 100644 index 00000000..ce92dd03 --- /dev/null +++ b/kexi/kexidb/drivermanager_p.h @@ -0,0 +1,94 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_DRIVER_MNGR_P_H +#define KEXIDB_DRIVER_MNGR_P_H + +#include <kexidb/object.h> + +#include <qobject.h> +#include <qasciidict.h> + +namespace KexiDB { + +/*! Internal class of driver manager. +*/ +class KEXI_DB_EXPORT DriverManagerInternal : public QObject, public KexiDB::Object +{ + Q_OBJECT + public: + ~DriverManagerInternal(); + + /*! Tries to load db driver \a name. + \return db driver, or 0 if error (then error message is also set) */ + KexiDB::Driver* driver(const QString& name); + + KexiDB::Driver::Info driverInfo(const QString &name); + + static DriverManagerInternal *self(); + + /*! increments the refcount for the manager */ + void incRefCount(); + + /*! decrements the refcount for the manager + if the refcount reaches a value less than 1 the manager is freed */ + void decRefCount(); + + /*! Called from Driver dtor (because sometimes KLibrary (used by Driver) + is destroyed before DriverManagerInternal) */ + void aboutDelete( Driver* drv ); + + protected slots: + /*! Used to destroy all drivers on QApplication quit, so even if there are + DriverManager's static instances that are destroyed on program + "static destruction", drivers are not kept after QApplication death. + */ + void slotAppQuits(); + + protected: + /*! Used by self() */ + DriverManagerInternal(); + + bool lookupDrivers(); + + static KexiDB::DriverManagerInternal* s_self; + + DriverManager::ServicesMap m_services; //! services map + DriverManager::ServicesMap m_services_lcase; //! as above but service names in lowercase + DriverManager::ServicesMap m_services_by_mimetype; + Driver::InfoMap m_driversInfo; //! used to store drivers information + QAsciiDict<KexiDB::Driver> m_drivers; + ulong m_refCount; + + QString m_serverErrMsg; + int m_serverResultNum; + QString m_serverResultName; + //! result names for KParts::ComponentFactory::ComponentLoadingError + QMap<int,QString> m_componentLoadingErrors; + + QStringList possibleProblems; + + bool lookupDriversNeeded : 1; + + friend class DriverManager; +}; +} + +#endif + diff --git a/kexi/kexidb/drivers/Makefile.am b/kexi/kexidb/drivers/Makefile.am new file mode 100644 index 00000000..642a80f3 --- /dev/null +++ b/kexi/kexidb/drivers/Makefile.am @@ -0,0 +1,11 @@ +if compile_mysql_plugin +mysql_dir=mySQL +endif + + +if compile_pgsql_plugin +pgsql_dir=pqxx +endif + +SUBDIRS = sqlite sqlite2 $(pgsql_dir) $(mysql_dir) + diff --git a/kexi/kexidb/drivers/common.pro b/kexi/kexidb/drivers/common.pro new file mode 100644 index 00000000..4795c454 --- /dev/null +++ b/kexi/kexidb/drivers/common.pro @@ -0,0 +1,11 @@ +CONFIG += kde3lib + +TEMPLATE = lib +include( $(KEXI)/kexidb/common.pro ) + +win32:LIBS += \ +$$KDELIBDESTDIR/kexidb$$KEXILIB_SUFFIX + +#$$KDELIBDESTDIR/kexicore$$KEXILIB_SUFFIX \ +#$$KDELIBDESTDIR/kexidatatable$$KEXILIB_SUFFIX \ + diff --git a/kexi/kexidb/drivers/configure.in.bot b/kexi/kexidb/drivers/configure.in.bot new file mode 100644 index 00000000..02707c44 --- /dev/null +++ b/kexi/kexidb/drivers/configure.in.bot @@ -0,0 +1,99 @@ +if test -z "$MYSQL_INC" -o -z "$MYSQL_LIBS"; then + + echo "----------------------------------------------------------------------" + + echo " + The MySQL development files were not found." + cat <<EOS + These are required for MySQL support in Kexi. + + If you want MySQL support in Kexi, you need to install the MySQL development + files, ensure that mysql-config is in your path, and run this configure script + again, and finally run make; make install. + If you don't need MySQL support, you can simply run make; make install now. +EOS + all_tests=bad +fi + +if test -z "$PG_INCDIR" -o -z "$PG_LIBDIR" -o \ + -z "$PQXX_INCDIR" -o -z "$PQXX_LIBDIR"; then + + echo "----------------------------------------------------------------------" + +# LIBPQ messages + if test -z "$PG_INCDIR"; then + echo " + The PostgreSQL C-API (libpq) headers were not found." + fi + + if test -z "$PG_LIBDIR"; then + echo " + The PostgreSQL C-API (libpq) libraries were not found." + fi + + if test -z "$PG_INCDIR" -a -z "$PG_LIBDIR" ; then + pglib_parts_missing="HEADER or the libpq LIBRARY" + elif test -z "$PG_INCDIR" ; then + pglib_parts_missing="HEADER" + elif test -z "$PG_LIBDIR" ; then + pglib_parts_missing="LIBRARY" + fi + + if test -z "$PG_INCDIR" -o -z "$PG_LIBDIR" ; then + cat <<EOS + Could not find the libpq $pglib_parts_missing files. + These are required by the libpqxx C++ library, which is used by + Kexi's PostgreSQL drivers. + + The PostgreSQL C-API usually ship with PostgreSQL, but if you've + installed from a distros package then these files may be part of + a package called postgresql-devel or libpq-devel" + +EOS + fi + +# LIBPQXX messages + if test -z "$PQXX_INCDIR"; then + echo " + The PostgreSQL C++ API (libpqxx) headers were not found." + fi + + if test -z "$PQXX_LIBDIR"; then + echo " + The PostgreSQL C++ API (libpqxx) shared libraries were not found." + fi + + if test -z "$PQXX_INCDIR" -a -z "$PQXX_LIBDIR" ; then + pqxx_parts_missing="HEADER or the libpqxx LIBRARY" + elif test -z "$PQXX_INCDIR" ; then + pqxx_parts_missing="HEADER" + elif test -z "$PQXX_LIBDIR" ; then + pqxx_parts_missing="LIBRARY" + fi + + if test -z "$PQXX_INCDIR" -o -z "$PQXX_LIBDIR" ; then + cat <<EOS + Could not find the libpqxx $pqxx_parts_missing files. + These are required by Kexi's PostgreSQL drivers. + + Note: Kexi requires the SHARED libpqxx.so library files. + If you build pqxx library on your own, don't forget to use the + --enable-shared option when you run libpqxx's configure script. + This is necessary to compile the SHARED .so library, and + not the STATIC libpqxx.a. + + The PostgreSQL C++ API can be downloaded from pqxx.tk or + http://gborg.postgresql.org/project/libpqxx/projdisplay.php + Grab the latest version (>=2) + +EOS + fi + +# SUMMARY messages + cat <<EOS + These warnings are not critical, but without installing the files + listed above Kexi will be compiled without PostgreSQL support. + + If you want PostgreSQL support in Kexi, you need to install the files + listed above, then run this configure script again, and finally run + make; make install. If you don't, simply run make; make install now. +EOS + + all_tests=bad + echo "----------------------------------------------------------------------" +fi diff --git a/kexi/kexidb/drivers/configure.in.in b/kexi/kexidb/drivers/configure.in.in new file mode 100644 index 00000000..01d426b6 --- /dev/null +++ b/kexi/kexidb/drivers/configure.in.in @@ -0,0 +1,244 @@ +dnl ======================================== +dnl checks for MySQL +dnl taken form KDEDB +dnl ======================================== + +AC_ARG_ENABLE(mysql, + AC_HELP_STRING([--enable-mysql],[build MySQL-plugin [default=yes]]), + mysql_plugin=$enableval, mysql_plugin=yes) + +if test "x$mysql_plugin" = "xyes"; then + compile_mysql_plugin="yes" +else + compile_mysql_plugin="no" +fi + +AC_ARG_WITH(mysql_includes, +AC_HELP_STRING([--with-mysql-includes=DIR],[use MySQL-includes installed in this directory]), +[ + ac_mysql_incdir=$withval +], ac_mysql_incdir= +) + +AC_ARG_WITH(mysql_libraries, +AC_HELP_STRING([--with-mysql-libraries=DIR],[use MySQL-libs installed in this directory ]), +[ + ac_mysql_libdir=$withval +], ac_mysql_libdir= +) + +dnl ============================================== +dnl check whether MySQL should be compiled +dnl and where headers and libraries are installed +dnl if present compile mysql-plugin +dnl ============================================== + +AC_MSG_CHECKING([for MySQL]) + +if test "$compile_mysql_plugin" = "yes"; then + if test -n "$ac_mysql_incdir" -o -n "$ac_mysql_libdir"; then +dnl *** Configure arguments for includes or libs given *** +dnl *** and MySQL not explicitly disabled. *** +dnl *** Check that the paths given to configure are valid *** + AC_MSG_CHECKING([for MySQL headers]) + mysql_incdirs="$ac_mysql_incdir /usr/local/include /usr/include" + AC_FIND_FILE(mysql/mysql.h, $mysql_incdirs, mysql_incdir) + if test -r $mysql_incdir/mysql/mysql.h; then + MYSQL_INC=$mysql_incdir + AC_MSG_RESULT([$MYSQL_INC]) + AC_SUBST(MYSQL_INC) + else + compile_mysql_plugin="no" + AC_MSG_RESULT([not found]) + fi + + AC_MSG_CHECKING([for MySQL libraries]) + mysql_libdirs="$ac_mysql_libdir /usr/local/lib$kdelibsuff /usr/lib$kdelibsuff" + AC_FIND_FILE(mysql/libmysqlclient.so, $mysql_libdirs, mysql_libdir) + if test -r $mysql_libdir/mysql/libmysqlclient.so; then + MYSQL_LIBS=$mysql_libdir + AC_MSG_RESULT([$MYSQL_LIBS]) + AC_SUBST(MYSQL_LIBS) + else + compile_mysql_plugin="no" + AC_MSG_RESULT([not found]) + fi + else +dnl *** No configure arguments for includes or libs given *** +dnl *** and MySQL not explicitly disabled. *** + KDE_FIND_PATH(mysql_config, MYSQL_CONFIG, + [${prefix}/bin ${exec_prefix}/bin /usr/local/bin /usr/bin ], [ + AC_MSG_RESULT([not found]) + ]) + + if test -n "$MYSQL_CONFIG"; then + mysql_incdir=`$MYSQL_CONFIG --cflags| $SED -e "s,-I,,g" | cut -d " " -f 1` + mysql_libdir=`$MYSQL_CONFIG --libs| $SED -e "s,',,g"` + MYSQL_INC=$mysql_incdir + MYSQL_LIBS=$mysql_libdir + AC_SUBST(MYSQL_INC) + AC_SUBST(MYSQL_LIBS) + compile_mypsql_plugin="yes" + AC_MSG_RESULT([headers $mysql_incdir, libraries $mysql_libdir]) + else + compile_mysql_plugin="no" + fi + fi +else +dnl *** MySQL plugin explicitly disabled. *** +dnl *** Show that we are doing as requested. *** + AC_MSG_NOTICE([Not attempting to configure MySQL as requested]) +fi + +AM_CONDITIONAL(compile_mysql_plugin, test "$compile_mysql_plugin" = "yes") + +dnl ======================================== +dnl Checks for PostgreSQL +dnl ======================================== + +dnl ======================================== +dnl libpq +dnl Add configure-args +dnl ======================================== + +dnl Assume we're building until something fails, unless explicitly disabled +AC_ARG_ENABLE(pgsql, +AC_HELP_STRING([--enable-pgsql],[build PostgreSQL-plugin [default=yes]]), + pgsql_plugin=$enableval, pgsql_plugin=yes) + +if test "x$pgsql_plugin" = "xyes"; then + compile_pgsql_plugin="yes" +else + compile_pgsql_plugin="no" +fi + +AC_ARG_WITH(pgsql-includes, +AC_HELP_STRING([--with-pgsql-includes=DIR],[use PostgreSQL(libpq)-includes installed in this directory ]), +[ + ac_pgsql_incdir=$withval +], ac_pgsql_incdir= +) + +AC_ARG_WITH(pgsql-libraries, +AC_HELP_STRING([--with-pgsql-libraries=DIR],[use PostgreSQL(libpq)-libraries installed in this directory ]), +[ + ac_pgsql_libdir=$withval +], ac_pgsql_libdir= +) + + +dnl ======================================== +dnl header/library directories +dnl ======================================== + +if test "$compile_pgsql_plugin" = "yes"; then + if test -n "$ac_pgsql_incdir" -o -n "$ac_pgsql_libdir"; then +dnl *** Configure arguments for includes or libs given *** +dnl *** and PostgreSQL not explicitly disabled. *** +dnl *** Check that the paths given to configure are valid *** + AC_MSG_CHECKING([for PostgreSQL C API headers]) + pgsql_incdirs="$ac_pgsql_incdir /usr/local/include /usr/include" + AC_FIND_FILE(libpq-fe.h, $pgsql_incdirs, pgsql_incdir) + if test -r $pgsql_incdir/libpq-fe.h; then + PG_INCDIR=$pgsql_incdir + AC_MSG_RESULT([$PG_INCDIR]) + AC_SUBST(PG_INCDIR) + else + compile_pgsql_plugin="no" + AC_MSG_RESULT([not found]) + fi + + AC_MSG_CHECKING([for PostgreSQL C API libraries]) + pgsql_libdirs="$ac_pgsql_libdir /usr/local/lib$kdelibsuff /usr/lib$kdelibsuff" + AC_FIND_FILE(libpq.so, $pgsql_libdirs, pgsql_libdir) + if test -r $pgsql_libdir/libpq.so; then + PG_LIBDIR=$pgsql_libdir + AC_MSG_RESULT([$PG_LIBDIR]) + AC_SUBST(PG_LIBDIR) + else + compile_pgsql_plugin="no" + AC_MSG_RESULT([not found]) + fi + else +dnl *** No configure arguments for includes or libs given *** +dnl *** and PostgreSQL not explicitly disabled. *** + KDE_FIND_PATH(pg_config, PG_CONFIG, + [${prefix}/bin ${exec_prefix}/bin /usr/local/bin /usr/bin ], [ + AC_MSG_RESULT([not found]) + ]) + + if test -n "$PG_CONFIG"; then + pgsql_incdir=`$PG_CONFIG --includedir` + pgsql_libdir=`$PG_CONFIG --libdir` + PG_INCDIR=$pgsql_incdir + PG_LIBDIR=$pgsql_libdir + AC_SUBST(PG_LIBDIR) + compile_pgsql_plugin="yes" + AC_MSG_RESULT([headers $pgsql_incdir, libraries $pgsql_libdir]) + else + compile_pgsql_plugin="no" + fi + fi +else +dnl *** PostgreSQL plugin explicitly disabled. *** +dnl *** Show that we are doing as requested. *** + AC_MSG_NOTICE([Not attempting to configure PostgreSQL as requested]) +fi + +AM_CONDITIONAL(compile_pgsql_plugin, test "$compile_pgsql_plugin" = "yes") + + +dnl ======================================== +dnl libpqxx checks +dnl ======================================== + +AC_ARG_WITH(pqxx-includes, +AC_HELP_STRING([--with-pqxx-includes=DIR],[use PostgreSQL(libpqxx)-includes installed in this directory ]), +[ + ac_pqxx_incdir=$withval +], ac_pqxx_incdir= +) + +AC_ARG_WITH(pqxx-libraries, +AC_HELP_STRING([--with-pqxx-libraries=DIR],[use PostgreSQL(libpqxx)-libraries installed in this directory ]), +[ + ac_pqxx_libdir=$withval +], ac_pqxx_libdir= +) + + +dnl ======================================== +dnl libpqxx headers +dnl ======================================== +if test "$compile_pgsql_plugin" = "yes"; then + AC_MSG_CHECKING([for PostgreSQL C++ includes]) + pqxx_incdirs="$ac_pqxx_incdir /usr/local/include /usr/include" + AC_FIND_FILE(pqxx/pqxx, $pqxx_incdirs, pqxx_incdir) + if test -r $pqxx_incdir/pqxx/pqxx; then + PQXX_INCDIR=$pqxx_incdir + AC_MSG_RESULT([$PQXX_INCDIR]) + AC_SUBST(PQXX_INCDIR) + else + compile_pgsql_plugin="no" + AC_MSG_RESULT([not found]) + fi +fi + +dnl ======================================== +dnl libpqxx libraries +dnl ======================================== +if test "$compile_pgsql_plugin" = "yes"; then + AC_MSG_CHECKING([for PostgreSQL C++ libraries]) + pqxx_libdirs="$ac_pqxx_libdir /usr/local/lib$kdelibsuff /usr/lib$kdelibsuff" + AC_FIND_FILE(libpqxx.so, $pqxx_libdirs, pqxx_libdir) + if test -r $pqxx_libdir/libpqxx.so; then + PQXX_LIBDIR=$pqxx_libdir + AC_MSG_RESULT([$PQXX_LIBDIR]) + AC_SUBST(PQXX_LIBDIR) + else + compile_pgsql_plugin="no" + AC_MSG_RESULT([not found]) + fi +fi + +AM_CONDITIONAL(compile_pgsql_plugin, test "$compile_pgsql_plugin" = "yes") diff --git a/kexi/kexidb/drivers/drivers.pro b/kexi/kexidb/drivers/drivers.pro new file mode 100644 index 00000000..1c8b5c34 --- /dev/null +++ b/kexi/kexidb/drivers/drivers.pro @@ -0,0 +1,7 @@ +TEMPLATE = subdirs + +SUBDIRS += \ +sqlite2 \ +sqlite \ +mySQL \ +pqxx diff --git a/kexi/kexidb/drivers/mySQL/Makefile.am b/kexi/kexidb/drivers/mySQL/Makefile.am new file mode 100644 index 00000000..dca6c26e --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/Makefile.am @@ -0,0 +1,33 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexidb_mysqldriver.la + +INCLUDES = -I$(MYSQL_INC) -I$(srcdir)/../../.. \ + -I$(srcdir)/../.. \ + -I$(top_srcdir)/kexi $(all_includes) + +kexidb_mysqldriver_la_METASOURCES = AUTO + +kexidb_mysqldriver_la_SOURCES = \ + mysqldriver.cpp \ + mysqlconnection.cpp \ + mysqlconnection_p.cpp \ + mysqlcursor.cpp \ + mysqlkeywords.cpp \ + mysqlpreparedstatement.cpp + +kexidb_mysqldriver_la_LIBADD = $(LIB_KPARTS) \ + $(LIB_QT) \ + $(MYSQL_LIBS) \ + -lmysqlclient \ + ../../libkexidb.la + +kexidb_mysqldriver_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO) -no-undefined + + +kde_services_DATA = kexidb_mysqldriver.desktop + + +KDE_CXXFLAGS += -DKEXIDB_MYSQL_DRIVER_EXPORT= -D__KEXIDB__= \ + -include $(top_srcdir)/kexi/kexidb/global.h + diff --git a/kexi/kexidb/drivers/mySQL/kexidb_mysqldriver.desktop b/kexi/kexidb/drivers/mySQL/kexidb_mysqldriver.desktop new file mode 100644 index 00000000..066782f1 --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/kexidb_mysqldriver.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=MySQL +Name[ne]=मेरो एसक्यूएल +Name[sk]=mySQL +X-KDE-Library=kexidb_mysqldriver +ServiceTypes=Kexi/DBDriver +Type=Service +InitialPreference=8 +X-Kexi-DriverName=MySQL +X-Kexi-DriverType=Network +X-Kexi-KexiDBVersion=1.8 diff --git a/kexi/kexidb/drivers/mySQL/mySQL.pro b/kexi/kexidb/drivers/mySQL/mySQL.pro new file mode 100644 index 00000000..cc6eddd6 --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mySQL.pro @@ -0,0 +1,28 @@ +include( ../common.pro ) + +INCLUDEPATH += $(MYSQL_INC) $(MYSQL_INC)/mysql + +contains(CONFIG,debug) { + win32:LIBS += $(MYSQL_LIB)/debug/libmysql.lib + win32:QMAKE_LFLAGS += /NODEFAULTLIB:LIBCMTD.LIB +} +!contains(CONFIG,debug) { +# win32:LIBS += $(MYSQL_LIB)/opt/mysqlclient.lib + win32:LIBS += $(MYSQL_LIB)/opt/libmysql.lib +# win32:QMAKE_LFLAGS += /NODEFAULTLIB:MSVCRT.LIB +} + +TARGET = kexidb_mysqldriver$$KDELIBDEBUG + +system( bash kmoc ) + +SOURCES = \ +mysqlconnection_p.cpp \ +mysqlconnection.cpp \ +mysqldriver.cpp \ +mysqlcursor.cpp \ +mysqlkeywords.cpp \ +mysqlpreparedstatement.cpp + +HEADERS = + diff --git a/kexi/kexidb/drivers/mySQL/mysqlconnection.cpp b/kexi/kexidb/drivers/mySQL/mysqlconnection.cpp new file mode 100644 index 00000000..7417b698 --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqlconnection.cpp @@ -0,0 +1,208 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <[email protected]> + Daniel Molkentin <[email protected]> + Copyright (C) 2003 Joseph Wenninger<[email protected]> + Copyright (C) 2004, 2006 Jaroslaw Staniek <[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this program; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <qvariant.h> +#include <qfile.h> +#include <qdict.h> +#include <qregexp.h> + +#include <kgenericfactory.h> +#include <kdebug.h> + +#include "mysqldriver.h" +#include "mysqlconnection.h" +#include "mysqlconnection_p.h" +#include "mysqlcursor.h" +#include "mysqlpreparedstatement.h" +#include <kexidb/error.h> + + +using namespace KexiDB; + +//-------------------------------------------------------------------------- + +MySqlConnection::MySqlConnection( Driver *driver, ConnectionData &conn_data ) + :Connection(driver,conn_data) + ,d(new MySqlConnectionInternal(this)) +{ +} + +MySqlConnection::~MySqlConnection() { + destroy(); +} + +bool MySqlConnection::drv_connect(KexiDB::ServerVersionInfo& version) +{ + const bool ok = d->db_connect(*data()); + if (!ok) + return false; + + version.string = mysql_get_host_info(d->mysql); + + //retrieve server version info +#if 0 //this only works for client version >= 4.1 :( + unsigned long v = mysql_get_server_version(d->mysql); + // v - a number that represents the MySQL server version in this format + // = major_version*10000 + minor_version *100 + sub_version + version.major = v/10000; + version.minor = (v - version.major*10000)/100; + version.release = v - version.major*10000 - version.minor*100; +#else //better way to get the version info: use 'version' built-in variable: +//! @todo this is hardcoded for now; define api for retrieving variables and use this API... + QString versionString; + const tristate res = querySingleString("SELECT @@version", versionString, /*column*/0, false /*!addLimitTo1*/); + QRegExp versionRe("(\\d+)\\.(\\d+)\\.(\\d+)"); + if (res==true && versionRe.exactMatch(versionString)) { // (if querySingleString failed, the version will be 0.0.0... + version.major = versionRe.cap(1).toInt(); + version.minor = versionRe.cap(2).toInt(); + version.release = versionRe.cap(3).toInt(); + } +#endif + return true; +} + +bool MySqlConnection::drv_disconnect() { + return d->db_disconnect(); +} + +Cursor* MySqlConnection::prepareQuery(const QString& statement, uint cursor_options) { + return new MySqlCursor(this,statement,cursor_options); +} + +Cursor* MySqlConnection::prepareQuery( QuerySchema& query, uint cursor_options ) { + return new MySqlCursor( this, query, cursor_options ); +} + +bool MySqlConnection::drv_getDatabasesList( QStringList &list ) { + KexiDBDrvDbg << "MySqlConnection::drv_getDatabasesList()" << endl; + list.clear(); + MYSQL_RES *res; + + if((res=mysql_list_dbs(d->mysql,0)) != 0) { + MYSQL_ROW row; + while ( (row = mysql_fetch_row(res))!=0) { + list<<QString(row[0]); + } + mysql_free_result(res); + return true; + } + + d->storeResult(); +// setError(ERR_DB_SPECIFIC,mysql_error(d->mysql)); + return false; +} + +bool MySqlConnection::drv_createDatabase( const QString &dbName) { + KexiDBDrvDbg << "MySqlConnection::drv_createDatabase: " << dbName << endl; + // mysql_create_db deprecated, use SQL here. + if (drv_executeSQL("CREATE DATABASE " + (dbName))) + return true; + d->storeResult(); + return false; +} + +bool MySqlConnection::drv_useDatabase(const QString &dbName, bool *cancelled, MessageHandler* msgHandler) +{ + Q_UNUSED(cancelled); + Q_UNUSED(msgHandler); +//TODO is here escaping needed? + return d->useDatabase(dbName); +} + +bool MySqlConnection::drv_closeDatabase() { +//TODO free resources +//As far as I know, mysql doesn't support that + return true; +} + +bool MySqlConnection::drv_dropDatabase( const QString &dbName) { +//TODO is here escaping needed + return drv_executeSQL("drop database "+dbName); +} + +bool MySqlConnection::drv_executeSQL( const QString& statement ) { + return d->executeSQL(statement); +} + +Q_ULLONG MySqlConnection::drv_lastInsertRowID() +{ + //! @todo + return (Q_ULLONG)mysql_insert_id(d->mysql); +} + +int MySqlConnection::serverResult() +{ + return d->res; +} + +QString MySqlConnection::serverResultName() +{ + return QString::null; +} + +void MySqlConnection::drv_clearServerResult() +{ + if (!d) + return; + d->res = 0; +} + +QString MySqlConnection::serverErrorMsg() +{ + return d->errmsg; +} + +bool MySqlConnection::drv_containsTable( const QString &tableName ) +{ + bool success; + return resultExists(QString("show tables like %1") + .arg(driver()->escapeString(tableName)), success) && success; +} + +bool MySqlConnection::drv_getTablesList( QStringList &list ) +{ + KexiDB::Cursor *cursor; + m_sql = "show tables"; + if (!(cursor = executeQuery( m_sql ))) { + KexiDBDbg << "Connection::drv_getTablesList(): !executeQuery()" << endl; + return false; + } + list.clear(); + cursor->moveFirst(); + while (!cursor->eof() && !cursor->error()) { + list += cursor->value(0).toString(); + cursor->moveNext(); + } + if (cursor->error()) { + deleteCursor(cursor); + return false; + } + return deleteCursor(cursor); +} + +PreparedStatement::Ptr MySqlConnection::prepareStatement(PreparedStatement::StatementType type, + FieldList& fields) +{ + return new MySqlPreparedStatement(type, *d, fields); +} + +#include "mysqlconnection.moc" diff --git a/kexi/kexidb/drivers/mySQL/mysqlconnection.h b/kexi/kexidb/drivers/mySQL/mysqlconnection.h new file mode 100644 index 00000000..bafb889d --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqlconnection.h @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <[email protected]> + Copyright (C) 2003 Joseph Wenninger<[email protected]> + Copyright (C) 2004 Jaroslaw Staniek <[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this program; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef MYSQLCONNECTION_H +#define MYSQLCONNECTION_H + +#include <qstringlist.h> + +#include <kexidb/connection.h> +#include "mysqlcursor.h" +#include <qdict.h> + +namespace KexiDB { + +class MySqlConnectionInternal; + +/*! + * Should override kexiDB/kexiDB + * all other members are done by the + * base class. + */ +class MySqlConnection : public Connection +{ + Q_OBJECT + + public: + virtual ~MySqlConnection(); + + virtual Cursor* prepareQuery( const QString& statement = QString::null, uint cursor_options = 0 ); + virtual Cursor* prepareQuery( QuerySchema& query, uint cursor_options = 0 ); + + virtual PreparedStatement::Ptr prepareStatement(PreparedStatement::StatementType type, + FieldList& fields); + + protected: + + /*! Used by driver */ + MySqlConnection( Driver *driver, ConnectionData &conn_data ); + + virtual bool drv_connect(KexiDB::ServerVersionInfo& version); + virtual bool drv_disconnect(); + virtual bool drv_getDatabasesList( QStringList &list ); + virtual bool drv_createDatabase( const QString &dbName = QString::null ); + virtual bool drv_useDatabase( const QString &dbName = QString::null, bool *cancelled = 0, + MessageHandler* msgHandler = 0 ); + virtual bool drv_closeDatabase(); + virtual bool drv_dropDatabase( const QString &dbName = QString::null ); + virtual bool drv_executeSQL( const QString& statement ); + virtual Q_ULLONG drv_lastInsertRowID(); + + virtual int serverResult(); + virtual QString serverResultName(); + virtual QString serverErrorMsg(); + virtual void drv_clearServerResult(); + +//TODO: move this somewhere to low level class (MIGRATION?) + virtual bool drv_getTablesList( QStringList &list ); +//TODO: move this somewhere to low level class (MIGRATION?) + virtual bool drv_containsTable( const QString &tableName ); + + MySqlConnectionInternal* d; + + friend class MySqlDriver; + friend class MySqlCursor; +}; + +} + +#endif diff --git a/kexi/kexidb/drivers/mySQL/mysqlconnection_p.cpp b/kexi/kexidb/drivers/mySQL/mysqlconnection_p.cpp new file mode 100644 index 00000000..9797ca96 --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqlconnection_p.cpp @@ -0,0 +1,175 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Jaroslaw Staniek <[email protected]> + Copyright (C) 2004 Martin Ellis <[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this program; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <qcstring.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qfile.h> + +#include <kdebug.h> + +#include "mysqlconnection_p.h" + +#include <kexidb/connectiondata.h> + +#ifdef MYSQLMIGRATE_H +#define NAMESPACE KexiMigration +#else +#define NAMESPACE KexiDB +#endif + +using namespace NAMESPACE; + +/* ************************************************************************** */ +MySqlConnectionInternal::MySqlConnectionInternal(KexiDB::Connection* connection) + : ConnectionInternal(connection) + , mysql(0) + , mysql_owned(true) + , res(0) +{ +} + +MySqlConnectionInternal::~MySqlConnectionInternal() +{ + if (mysql_owned && mysql) { + mysql_close(mysql); + mysql = 0; + } +} + +void MySqlConnectionInternal::storeResult() +{ + res = mysql_errno(mysql); + errmsg = mysql_error(mysql); +} + +/* ************************************************************************** */ +/*! Connects to the MySQL server on host as the given user using the specified + password. If host is "localhost", then a socket on the local file system + can be specified to connect to the server (several defaults will be tried if + none is specified). If the server is on a remote machine, then a port is + the port that the remote server is listening on. + */ +//bool MySqlConnectionInternal::db_connect(QCString host, QCString user, +// QCString password, unsigned short int port, QString socket) +bool MySqlConnectionInternal::db_connect(const KexiDB::ConnectionData& data) +{ + if (!(mysql = mysql_init(mysql))) + return false; + + KexiDBDrvDbg << "MySqlConnectionInternal::connect()" << endl; + QCString localSocket; + QString hostName = data.hostName; + if (hostName.isEmpty() || hostName.lower()=="localhost") { + if (data.useLocalSocketFile) { + if (data.localSocketFileName.isEmpty()) { + //! @todo move the list of default sockets to a generic method + QStringList sockets; + #ifndef Q_WS_WIN + sockets.append("/var/lib/mysql/mysql.sock"); + sockets.append("/var/run/mysqld/mysqld.sock"); + sockets.append("/tmp/mysql.sock"); + + for(QStringList::ConstIterator it = sockets.constBegin(); it != sockets.constEnd(); it++) + { + if(QFile(*it).exists()) { + localSocket = ((QString)(*it)).local8Bit(); + break; + } + } + #endif + } + else + localSocket = QFile::encodeName(data.localSocketFileName); + } + else { + //we're not using local socket + hostName = "127.0.0.1"; //this will force mysql to connect to localhost + } + } + +/*! @todo is latin1() encoding here valid? what about using UTF for passwords? */ + const char *pwd = data.password.isNull() ? 0 : data.password.latin1(); + mysql_real_connect(mysql, hostName.latin1(), data.userName.latin1(), + pwd, 0, data.port, localSocket, 0); + if(mysql_errno(mysql) == 0) + return true; + + storeResult(); //store error msg, if any - can be destroyed after disconnect() + db_disconnect(); +// setError(ERR_DB_SPECIFIC,err); + return false; +} + +/*! Disconnects from the database. + */ +bool MySqlConnectionInternal::db_disconnect() +{ + mysql_close(mysql); + mysql = 0; + KexiDBDrvDbg << "MySqlConnection::disconnect()" << endl; + return true; +} + +/* ************************************************************************** */ +/*! Selects dbName as the active database so it can be used. + */ +bool MySqlConnectionInternal::useDatabase(const QString &dbName) { +//TODO is here escaping needed? + return executeSQL("USE " + dbName); +} + +/*! Executes the given SQL statement on the server. + */ +bool MySqlConnectionInternal::executeSQL(const QString& statement) { +// KexiDBDrvDbg << "MySqlConnectionInternal::executeSQL: " +// << statement << endl; + QCString queryStr=statement.utf8(); + const char *query=queryStr; + if(mysql_real_query(mysql, query, strlen(query)) == 0) + { + return true; + } + + storeResult(); +// setError(ERR_DB_SPECIFIC,mysql_error(m_mysql)); + return false; +} + +QString MySqlConnectionInternal::escapeIdentifier(const QString& str) const { + return QString(str).replace('`', "'"); +} + +//-------------------------------------- + +MySqlCursorData::MySqlCursorData(KexiDB::Connection* connection) +: MySqlConnectionInternal(connection) +, mysqlres(0) +, mysqlrow(0) +, lengths(0) +, numRows(0) +{ + mysql_owned = false; +} + +MySqlCursorData::~MySqlCursorData() +{ +} + diff --git a/kexi/kexidb/drivers/mySQL/mysqlconnection_p.h b/kexi/kexidb/drivers/mySQL/mysqlconnection_p.h new file mode 100644 index 00000000..5bb77487 --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqlconnection_p.h @@ -0,0 +1,101 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Martin Ellis <[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this program; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_MYSQLCLIENT_P_H +#define KEXIDB_MYSQLCLIENT_P_H + +#include <kexidb/connection_p.h> + +#ifdef Q_WS_WIN +#include <my_global.h> +#endif +#include <mysql_version.h> +#include <mysql.h> + +typedef struct st_mysql MYSQL; +#undef bool + +class QCString; +class QString; + +#ifdef MYSQLMIGRATE_H +#define NAMESPACE KexiMigration +#else +#define NAMESPACE KexiDB +#endif + +namespace KexiDB { + class ConnectionData; +} + +namespace NAMESPACE { + +//! Internal MySQL connection data. +/*! Provides a low-level API for accessing MySQL databases, that can + be shared by any module that needs direct access to the underlying + database. Used by the KexiDB and KexiMigration drivers. + */ +class MySqlConnectionInternal : public KexiDB::ConnectionInternal +{ + public: + MySqlConnectionInternal(KexiDB::Connection* connection); + virtual ~MySqlConnectionInternal(); + + //! Connects to a MySQL database + bool db_connect(const KexiDB::ConnectionData& data); + + //! Disconnects from the database + bool db_disconnect(); + + //! Selects a database that is about to be used + bool useDatabase(const QString &dbName = QString::null); + + //! Execute SQL statement on the database + bool executeSQL( const QString& statement ); + + //! Stores last operation's result + virtual void storeResult(); + + //! Escapes a table, database or column name + QString escapeIdentifier(const QString& str) const; + + MYSQL *mysql; + bool mysql_owned; //!< true if mysql pointer should be freed on destruction + QString errmsg; //!< server-specific message of last operation + int res; //!< result code of last operation on server +}; + + +//! Internal MySQL cursor data. +/*! Provides a low-level abstraction for iterating over MySql result sets. */ +class MySqlCursorData : public MySqlConnectionInternal +{ + public: + MySqlCursorData(KexiDB::Connection* connection); + virtual ~MySqlCursorData(); + + MYSQL_RES *mysqlres; + MYSQL_ROW mysqlrow; + unsigned long *lengths; + unsigned long numRows; +}; + +} + +#endif diff --git a/kexi/kexidb/drivers/mySQL/mysqlcursor.cpp b/kexi/kexidb/drivers/mySQL/mysqlcursor.cpp new file mode 100644 index 00000000..7897fa97 --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqlcursor.cpp @@ -0,0 +1,218 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Joseph Wenninger<[email protected]> + Copyright (C) 2005 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "mysqlcursor.h" +#include "mysqlconnection.h" +#include "mysqlconnection_p.h" +#include <kexidb/error.h> +#include <kexidb/utils.h> +#include <klocale.h> +#include <kdebug.h> +#include <limits.h> + +#define BOOL bool + +using namespace KexiDB; + +MySqlCursor::MySqlCursor(KexiDB::Connection* conn, const QString& statement, uint cursor_options) + : Cursor(conn,statement,cursor_options) + , d( new MySqlCursorData(conn) ) +{ + m_options |= Buffered; + d->mysql = static_cast<MySqlConnection*>(conn)->d->mysql; +// KexiDBDrvDbg << "MySqlCursor: constructor for query statement" << endl; +} + +MySqlCursor::MySqlCursor(Connection* conn, QuerySchema& query, uint options ) + : Cursor( conn, query, options ) + , d( new MySqlCursorData(conn) ) +{ + m_options |= Buffered; + d->mysql = static_cast<MySqlConnection*>(conn)->d->mysql; +// KexiDBDrvDbg << "MySqlCursor: constructor for query statement" << endl; +} + +MySqlCursor::~MySqlCursor() { + close(); +} + +bool MySqlCursor::drv_open() { +// KexiDBDrvDbg << "MySqlCursor::drv_open:" << m_sql << endl; + // This can't be right? mysql_real_query takes a length in order that + // queries can have binary data - but strlen does not allow binary data. + if(mysql_real_query(d->mysql, m_sql.utf8(), strlen(m_sql.utf8())) == 0) { + if(mysql_errno(d->mysql) == 0) { + d->mysqlres= mysql_store_result(d->mysql); + m_fieldCount=mysql_num_fields(d->mysqlres); + d->numRows=mysql_num_rows(d->mysqlres); + m_at=0; + + m_opened=true; + m_records_in_buf = d->numRows; + m_buffering_completed = true; + m_afterLast=false; + return true; + } + } + + setError(ERR_DB_SPECIFIC,QString::fromUtf8(mysql_error(d->mysql))); + return false; +} + +bool MySqlCursor::drv_close() { + mysql_free_result(d->mysqlres); + d->mysqlres=0; + d->mysqlrow=0; +//js: done in superclass: m_numFields=0; + d->lengths=0; + m_opened=false; + d->numRows=0; + return true; +} + +/*bool MySqlCursor::drv_moveFirst() { + return true; //TODO +}*/ + +void MySqlCursor::drv_getNextRecord() { +// KexiDBDrvDbg << "MySqlCursor::drv_getNextRecord" << endl; + if (at() < d->numRows && at() >=0) { + d->lengths=mysql_fetch_lengths(d->mysqlres); + m_result=FetchOK; + } + else if (at() >= d->numRows) { + m_result = FetchEnd; + } + else { + m_result = FetchError; + } +} + +// This isn't going to work right now as it uses d->mysqlrow +QVariant MySqlCursor::value(uint pos) { + if (!d->mysqlrow || pos>=m_fieldCount || d->mysqlrow[pos]==0) + return QVariant(); + + KexiDB::Field *f = (m_fieldsExpanded && pos<m_fieldsExpanded->count()) + ? m_fieldsExpanded->at(pos)->field : 0; + +//! @todo js: use MYSQL_FIELD::type here! + + return KexiDB::cstringToVariant(d->mysqlrow[pos], f, d->lengths[pos]); +/* moved to cstringToVariant() + //from most to least frequently used types: + if (!f || f->isTextType()) + return QVariant( QString::fromUtf8((const char*)d->mysqlrow[pos], d->lengths[pos]) ); + else if (f->isIntegerType()) +//! @todo support BigInteger + return QVariant( QCString((const char*)d->mysqlrow[pos], d->lengths[pos]).toInt() ); + else if (f->isFPNumericType()) + return QVariant( QCString((const char*)d->mysqlrow[pos], d->lengths[pos]).toDouble() ); + + //default + return QVariant(QString::fromUtf8((const char*)d->mysqlrow[pos], d->lengths[pos]));*/ +} + + +/* As with sqlite, the DB library returns all values (including numbers) as + strings. So just put that string in a QVariant and let KexiDB deal with it. + */ +void MySqlCursor::storeCurrentRow(RowData &data) const +{ +// KexiDBDrvDbg << "MySqlCursor::storeCurrentRow: Position is " << (long)m_at<< endl; + if (d->numRows<=0) + return; + +//! @todo js: use MYSQL_FIELD::type here! +//! see SQLiteCursor::storeCurrentRow() + + data.resize(m_fieldCount); + const uint fieldsExpandedCount = m_fieldsExpanded ? m_fieldsExpanded->count() : UINT_MAX; + const uint realCount = QMIN(fieldsExpandedCount, m_fieldCount); + for( uint i=0; i<realCount; i++) { + Field *f = m_fieldsExpanded ? m_fieldsExpanded->at(i)->field : 0; + if (m_fieldsExpanded && !f) + continue; + data[i] = KexiDB::cstringToVariant(d->mysqlrow[i], f, d->lengths[i]); +/* moved to cstringToVariant() + if (f && f->type()==Field::BLOB) { + QByteArray ba; + ba.duplicate(d->mysqlrow[i], d->lengths[i]); + data[i] = ba; + KexiDBDbg << data[i].toByteArray().size() << endl; + } +//! @todo more types! +//! @todo look at what type mysql declares! + else { + data[i] = QVariant(QString::fromUtf8((const char*)d->mysqlrow[i], d->lengths[i])); + }*/ + } +} + +void MySqlCursor::drv_appendCurrentRecordToBuffer() { +} + + +void MySqlCursor::drv_bufferMovePointerNext() { + d->mysqlrow=mysql_fetch_row(d->mysqlres); + d->lengths=mysql_fetch_lengths(d->mysqlres); +} + +void MySqlCursor::drv_bufferMovePointerPrev() { + //MYSQL_ROW_OFFSET ro=mysql_row_tell(d->mysqlres); + mysql_data_seek(d->mysqlres,m_at-1); + d->mysqlrow=mysql_fetch_row(d->mysqlres); + d->lengths=mysql_fetch_lengths(d->mysqlres); +} + + +void MySqlCursor::drv_bufferMovePointerTo(Q_LLONG to) { + //MYSQL_ROW_OFFSET ro=mysql_row_tell(d->mysqlres); + mysql_data_seek(d->mysqlres, to); + d->mysqlrow=mysql_fetch_row(d->mysqlres); + d->lengths=mysql_fetch_lengths(d->mysqlres); +} + +const char** MySqlCursor::rowData() const { + //! @todo + return 0; +} + +int MySqlCursor::serverResult() +{ + return d->res; +} + +QString MySqlCursor::serverResultName() +{ + return QString::null; +} + +void MySqlCursor::drv_clearServerResult() +{ + if (!d) + return; + d->res = 0; +} + +QString MySqlCursor::serverErrorMsg() +{ + return d->errmsg; +} diff --git a/kexi/kexidb/drivers/mySQL/mysqlcursor.h b/kexi/kexidb/drivers/mySQL/mysqlcursor.h new file mode 100644 index 00000000..09ace22b --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqlcursor.h @@ -0,0 +1,68 @@ +/* This file is part of the KDE project +Copyright (C) 2003 Joseph Wenninger<[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this program; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef _MYSQLCURSOR_H_ +#define _MYSQLCURSOR_H_ + +#include <kexidb/cursor.h> +#include <kexidb/connection.h> + +namespace KexiDB { + +class MySqlCursorData; + +class MySqlCursor: public Cursor { +public: + MySqlCursor(Connection* conn, const QString& statement = QString::null, uint cursor_options = NoOptions ); + MySqlCursor(Connection* conn, QuerySchema& query, uint options = NoOptions ); + virtual ~MySqlCursor(); + virtual bool drv_open(); + virtual bool drv_close(); +// virtual bool drv_moveFirst(); + virtual void drv_getNextRecord(); + //virtual bool drv_getPrevRecord(); + virtual QVariant value(uint); + + virtual void drv_clearServerResult(); + virtual void drv_appendCurrentRecordToBuffer(); + virtual void drv_bufferMovePointerNext(); + virtual void drv_bufferMovePointerPrev(); + virtual void drv_bufferMovePointerTo(Q_LLONG to); + virtual const char** rowData() const; + virtual void storeCurrentRow(RowData &data) const; +// virtual bool save(RowData& data, RowEditBuffer& buf); + + virtual int serverResult(); + virtual QString serverResultName(); + virtual QString serverErrorMsg(); + +protected: + QVariant pValue(uint pos) const; +// MYSQL_RES *m_res; +// MYSQL_ROW m_row; +// MYSQL *my_conn; +// unsigned long *m_lengths; +//js: int m_numFields; +// unsigned long m_numRows; + MySqlCursorData *d; +}; + +} + +#endif diff --git a/kexi/kexidb/drivers/mySQL/mysqldriver.cpp b/kexi/kexidb/drivers/mySQL/mysqldriver.cpp new file mode 100644 index 00000000..c27681c0 --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqldriver.cpp @@ -0,0 +1,212 @@ +/* This file is part of the KDE project +Copyright (C) 2002 Lucijan Busch <[email protected]> +Daniel Molkentin <[email protected]> +Copyright (C) 2003 Joseph Wenninger<[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this program; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifdef Q_WS_WIN +# include <mysql/config-win.h> +#endif +#include <mysql_version.h> +#include <mysql.h> +#define BOOL bool + +#include <qvariant.h> +#include <qfile.h> +#include <qdict.h> + +#include <kgenericfactory.h> +#include <kdebug.h> + +#include "mysqldriver.h" +#include "mysqlconnection.h" +#include <kexidb/field.h> +#include <kexidb/driver_p.h> +#include <kexidb/utils.h> + +using namespace KexiDB; + +KEXIDB_DRIVER_INFO( MySqlDriver, mysql ) + +/* TODO: Implement buffered/unbuffered, rather than buffer everything. + Each MYSQL connection can only handle at most one unbuffered cursor, + so MySqlConnection should keep count? + */ + +/*! + * Constructor sets database features and + * maps the types in KexiDB::Field::Type to the MySQL types. + * + * See: http://dev.mysql.com/doc/mysql/en/Column_types.html + */ +MySqlDriver::MySqlDriver(QObject *parent, const char *name, const QStringList &args) : Driver(parent, name,args) +{ +// KexiDBDrvDbg << "MySqlDriver::MySqlDriver()" << endl; + + d->isFileDriver=false; + d->features=IgnoreTransactions | CursorForward; + + beh->ROW_ID_FIELD_NAME="LAST_INSERT_ID()"; + beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE=true; + beh->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY=false; + beh->USING_DATABASE_REQUIRED_TO_CONNECT=false; + beh->QUOTATION_MARKS_FOR_IDENTIFIER='`'; + beh->SQL_KEYWORDS = keywords; + initSQLKeywords(331); + + //predefined properties +#if MYSQL_VERSION_ID < 40000 + d->properties["client_library_version"] = MYSQL_SERVER_VERSION; //nothing better + d->properties["default_server_encoding"] = MYSQL_CHARSET; //nothing better +#elif MYSQL_VERSION_ID < 50000 +//OK? d->properties["client_library_version"] = mysql_get_client_version(); +#endif + + d->typeNames[Field::Byte]="TINYINT"; + d->typeNames[Field::ShortInteger]="SMALLINT"; + d->typeNames[Field::Integer]="INT"; + d->typeNames[Field::BigInteger]="BIGINT"; + // Can use BOOLEAN here, but BOOL has been in MySQL longer + d->typeNames[Field::Boolean]="BOOL"; + d->typeNames[Field::Date]="DATE"; + d->typeNames[Field::DateTime]="DATETIME"; + d->typeNames[Field::Time]="TIME"; + d->typeNames[Field::Float]="FLOAT"; + d->typeNames[Field::Double]="DOUBLE"; + d->typeNames[Field::Text]="VARCHAR"; + d->typeNames[Field::LongText]="LONGTEXT"; + d->typeNames[Field::BLOB]="BLOB"; +} + +MySqlDriver::~MySqlDriver() +{ +} + +KexiDB::Connection* +MySqlDriver::drv_createConnection( ConnectionData &conn_data ) +{ + return new MySqlConnection( this, conn_data ); +} + +bool MySqlDriver::isSystemDatabaseName(const QString &n) const +{ + return n.lower()=="mysql" || Driver::isSystemObjectName(n); +} + +bool MySqlDriver::drv_isSystemFieldName(const QString&) const { + return false; +} + +QString MySqlDriver::escapeString(const QString& str) const +{ + //escape as in http://dev.mysql.com/doc/refman/5.0/en/string-syntax.html +//! @todo support more characters, like %, _ + + const int old_length = str.length(); + int i; + for ( i = 0; i < old_length; i++ ) { //anything to escape? + const unsigned int ch = str[i].unicode(); + if (ch == '\\' || ch == '\'' || ch == '"' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\b' || ch == '\0') + break; + } + if (i >= old_length) { //no characters to escape + return QString::fromLatin1("'") + str + QString::fromLatin1("'"); + } + + QChar *new_string = new QChar[ old_length * 3 + 1 ]; // a worst case approximation +//! @todo move new_string to Driver::m_new_string or so... + int new_length = 0; + new_string[new_length++] = '\''; //prepend ' + for ( i = 0; i < old_length; i++, new_length++ ) { + const unsigned int ch = str[i].unicode(); + if (ch == '\\') { + new_string[new_length++] = '\\'; + new_string[new_length] = '\\'; + } + else if (ch <= '\'') {//check for speedup + if (ch == '\'') { + new_string[new_length++] = '\\'; + new_string[new_length] = '\''; + } + else if (ch == '"') { + new_string[new_length++] = '\\'; + new_string[new_length] = '"'; + } + else if (ch == '\n') { + new_string[new_length++] = '\\'; + new_string[new_length] = 'n'; + } + else if (ch == '\r') { + new_string[new_length++] = '\\'; + new_string[new_length] = 'r'; + } + else if (ch == '\t') { + new_string[new_length++] = '\\'; + new_string[new_length] = 't'; + } + else if (ch == '\b') { + new_string[new_length++] = '\\'; + new_string[new_length] = 'b'; + } + else if (ch == '\0') { + new_string[new_length++] = '\\'; + new_string[new_length] = '0'; + } + else + new_string[new_length] = str[i]; + } + else + new_string[new_length] = str[i]; + } + + new_string[new_length++] = '\''; //append ' + QString result(new_string, new_length); + delete [] new_string; + return result; +} + +QString MySqlDriver::escapeBLOB(const QByteArray& array) const +{ + return KexiDB::escapeBLOB(array, KexiDB::BLOBEscape0xHex); +} + +QCString MySqlDriver::escapeString(const QCString& str) const +{ +//! @todo optimize using mysql_real_escape_string()? +//! see http://dev.mysql.com/doc/refman/5.0/en/string-syntax.html + + return QCString("'")+QCString(str) + .replace( '\\', "\\\\" ) + .replace( '\'', "\\''" ) + .replace( '"', "\\\"" ) + + QCString("'"); +} + +/*! Add back-ticks to an identifier, and replace any back-ticks within + * the name with single quotes. + */ +QString MySqlDriver::drv_escapeIdentifier( const QString& str) const { + return QString(str).replace('`', "'"); +} + +QCString MySqlDriver::drv_escapeIdentifier( const QCString& str) const { + return QCString(str).replace('`', "'"); +} + +#include "mysqldriver.moc" + diff --git a/kexi/kexidb/drivers/mySQL/mysqldriver.h b/kexi/kexidb/drivers/mySQL/mysqldriver.h new file mode 100644 index 00000000..8282e215 --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqldriver.h @@ -0,0 +1,59 @@ +/* This file is part of the KDE project +Copyright (C) 2002 Lucijan Busch <[email protected]> +Daniel Molkentin <[email protected]> +Copyright (C) 2003 Joseph Wenninger<[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this program; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef MYSQLDB_H +#define MYSQLDB_H + +#include <kexidb/driver.h> + +namespace KexiDB { + +//! MySQL database driver. +class MySqlDriver : public Driver +{ + Q_OBJECT + KEXIDB_DRIVER + + public: + MySqlDriver(QObject *parent, const char *name, const QStringList &args=QStringList()); + virtual ~MySqlDriver(); + + virtual bool isSystemDatabaseName( const QString &n ) const; + + //! Escape a string for use as a value + virtual QString escapeString(const QString& str) const; + virtual QCString escapeString(const QCString& str) const; + + //! Escape BLOB value \a array + virtual QString escapeBLOB(const QByteArray& array) const; + + protected: + virtual QString drv_escapeIdentifier( const QString& str) const; + virtual QCString drv_escapeIdentifier( const QCString& str) const; + virtual Connection *drv_createConnection( ConnectionData &conn_data ); + virtual bool drv_isSystemFieldName( const QString& n ) const; + + private: + static const char *keywords[]; +}; +} + +#endif diff --git a/kexi/kexidb/drivers/mySQL/mysqlkeywords.cpp b/kexi/kexidb/drivers/mySQL/mysqlkeywords.cpp new file mode 100644 index 00000000..e06adb5e --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqlkeywords.cpp @@ -0,0 +1,338 @@ + /* + * This file has been automatically generated from + * koffice/kexi/tools/sql_keywords/sql_keywords.sh and + * mysql-4.1.7/sql/lex.h. + * + * Please edit the sql_keywords.sh, not this file! + */ +#include <mysqldriver.h> + +namespace KexiDB { + const char* MySqlDriver::keywords[] = { + "ACTION", + "ADD", + "AGAINST", + "AGGREGATE", + "ALTER", + "ANALYZE", + "ANY", + "ASCII", + "AUTO_INCREMENT", + "AVG", + "AVG_ROW_LENGTH", + "BACKUP", + "BDB", + "BERKELEYDB", + "BIGINT", + "BINARY", + "BINLOG", + "BIT", + "BLOB", + "BOOL", + "BOOLEAN", + "BOTH", + "BTREE", + "BYTE", + "CACHE", + "CHANGE", + "CHANGED", + "CHAR", + "CHARACTER", + "CHARSET", + "CHECKSUM", + "CIPHER", + "CLIENT", + "CLOSE", + "COLLATION", + "COLUMN", + "COLUMNS", + "COMMENT", + "COMMITTED", + "COMPRESSED", + "CONCURRENT", + "CONVERT", + "CUBE", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "CURRENT_USER", + "DATA", + "DATABASES", + "DATE", + "DATETIME", + "DAY", + "DAY_HOUR", + "DAY_MICROSECOND", + "DAY_MINUTE", + "DAY_SECOND", + "DEALLOCATE", + "DEC", + "DECIMAL", + "DELAYED", + "DELAY_KEY_WRITE", + "DESCRIBE", + "DES_KEY_FILE", + "DIRECTORY", + "DISABLE", + "DISCARD", + "DISTINCTROW", + "DIV", + "DO", + "DOUBLE", + "DUAL", + "DUMPFILE", + "DUPLICATE", + "DYNAMIC", + "ENABLE", + "ENCLOSED", + "ENGINE", + "ENGINES", + "ENUM", + "ERRORS", + "ESCAPE", + "ESCAPED", + "EVENTS", + "EXECUTE", + "EXISTS", + "EXPANSION", + "EXTENDED", + "FALSE", + "FAST", + "FIELDS", + "FILE", + "FIRST", + "FIXED", + "FLOAT", + "FLOAT4", + "FLOAT8", + "FLUSH", + "FORCE", + "FULLTEXT", + "FUNCTION", + "GEOMETRY", + "GEOMETRYCOLLECTION", + "GET_FORMAT", + "GLOBAL", + "GRANT", + "GRANTS", + "HANDLER", + "HASH", + "HELP", + "HIGH_PRIORITY", + "HOSTS", + "HOUR", + "HOUR_MICROSECOND", + "HOUR_MINUTE", + "HOUR_SECOND", + "IDENTIFIED", + "IF", + "IMPORT", + "INDEXES", + "INFILE", + "INNOBASE", + "INNODB", + "INSERT_METHOD", + "INT", + "INT1", + "INT2", + "INT3", + "INT4", + "INT8", + "INTERVAL", + "IO_THREAD", + "ISOLATION", + "ISSUER", + "KEYS", + "KILL", + "LAST", + "LEADING", + "LEAVES", + "LEVEL", + "LINES", + "LINESTRING", + "LOAD", + "LOCAL", + "LOCALTIME", + "LOCALTIMESTAMP", + "LOCK", + "LOCKS", + "LOGS", + "LONG", + "LONGBLOB", + "LONGTEXT", + "LOW_PRIORITY", + "MASTER", + "MASTER_CONNECT_RETRY", + "MASTER_HOST", + "MASTER_LOG_FILE", + "MASTER_LOG_POS", + "MASTER_PASSWORD", + "MASTER_PORT", + "MASTER_SERVER_ID", + "MASTER_SSL", + "MASTER_SSL_CA", + "MASTER_SSL_CAPATH", + "MASTER_SSL_CERT", + "MASTER_SSL_CIPHER", + "MASTER_SSL_KEY", + "MASTER_USER", + "MAX_CONNECTIONS_PER_HOUR", + "MAX_QUERIES_PER_HOUR", + "MAX_ROWS", + "MAX_UPDATES_PER_HOUR", + "MEDIUM", + "MEDIUMBLOB", + "MEDIUMINT", + "MEDIUMTEXT", + "MICROSECOND", + "MIDDLEINT", + "MINUTE", + "MINUTE_MICROSECOND", + "MINUTE_SECOND", + "MIN_ROWS", + "MOD", + "MODE", + "MODIFY", + "MONTH", + "MULTILINESTRING", + "MULTIPOINT", + "MULTIPOLYGON", + "NAMES", + "NATIONAL", + "NDB", + "NDBCLUSTER", + "NCHAR", + "NEW", + "NEXT", + "NO", + "NONE", + "NO_WRITE_TO_BINLOG", + "NUMERIC", + "NVARCHAR", + "OLD_PASSWORD", + "ONE_SHOT", + "OPEN", + "OPTIMIZE", + "OPTION", + "OPTIONALLY", + "OUTFILE", + "PACK_KEYS", + "PARTIAL", + "PASSWORD", + "POINT", + "POLYGON", + "PRECISION", + "PREPARE", + "PREV", + "PRIVILEGES", + "PROCEDURE", + "PROCESS", + "PROCESSLIST", + "PURGE", + "QUERY", + "QUICK", + "RAID0", + "RAID_CHUNKS", + "RAID_CHUNKSIZE", + "RAID_TYPE", + "READ", + "REAL", + "REGEXP", + "RELAY_LOG_FILE", + "RELAY_LOG_POS", + "RELAY_THREAD", + "RELOAD", + "RENAME", + "REPAIR", + "REPEATABLE", + "REPLICATION", + "REQUIRE", + "RESET", + "RESTORE", + "RETURNS", + "REVOKE", + "RLIKE", + "ROLLUP", + "ROWS", + "ROW_FORMAT", + "RTREE", + "SAVEPOINT", + "SECOND", + "SECOND_MICROSECOND", + "SEPARATOR", + "SERIAL", + "SERIALIZABLE", + "SESSION", + "SHARE", + "SHOW", + "SHUTDOWN", + "SIGNED", + "SIMPLE", + "SLAVE", + "SMALLINT", + "SOME", + "SONAME", + "SOUNDS", + "SPATIAL", + "SQL_BIG_RESULT", + "SQL_BUFFER_RESULT", + "SQL_CACHE", + "SQL_CALC_FOUND_ROWS", + "SQL_NO_CACHE", + "SQL_SMALL_RESULT", + "SQL_THREAD", + "SSL", + "START", + "STARTING", + "STATUS", + "STOP", + "STORAGE", + "STRAIGHT_JOIN", + "STRING", + "STRIPED", + "SUBJECT", + "SUPER", + "TABLES", + "TABLESPACE", + "TERMINATED", + "TEXT", + "TIME", + "TIMESTAMP", + "TINYBLOB", + "TINYINT", + "TINYTEXT", + "TRAILING", + "TRUE", + "TRUNCATE", + "TYPE", + "TYPES", + "UNCOMMITTED", + "UNICODE", + "UNLOCK", + "UNSIGNED", + "UNTIL", + "USAGE", + "USE", + "USER", + "USER_RESOURCES", + "USE_FRM", + "UTC_DATE", + "UTC_TIME", + "UTC_TIMESTAMP", + "VALUE", + "VARBINARY", + "VARCHAR", + "VARCHARACTER", + "VARIABLES", + "VARYING", + "WARNINGS", + "WITH", + "WORK", + "WRITE", + "X509", + "YEAR", + "YEAR_MONTH", + "ZEROFILL", + 0 + }; +} diff --git a/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.cpp b/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.cpp new file mode 100644 index 00000000..2702626a --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.cpp @@ -0,0 +1,298 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "mysqlpreparedstatement.h" +#include <kdebug.h> +#include <errmsg.h> + +using namespace KexiDB; + +// For example prepared MySQL statement code see: +// http://dev.mysql.com/doc/refman/4.1/en/mysql-stmt-execute.html + +MySqlPreparedStatement::MySqlPreparedStatement(StatementType type, ConnectionInternal& conn, + FieldList& fields) + : KexiDB::PreparedStatement(type, conn, fields) + , MySqlConnectionInternal(conn.connection) +#ifdef KEXI_USE_MYSQL_STMT + , m_statement(0) + , m_mysqlBind(0) +#endif + , m_resetRequired(false) +{ +// KexiDBDrvDbg << "MySqlPreparedStatement: Construction" << endl; + + mysql_owned = false; + mysql = dynamic_cast<KexiDB::MySqlConnectionInternal&>(conn).mysql; //copy + m_tempStatementString = generateStatementString(); + + if (!init()) + done(); +} + +bool MySqlPreparedStatement::init() +{ + if (m_tempStatementString.isEmpty()) + return false; +#ifdef KEXI_USE_MYSQL_STMT + m_statement = mysql_stmt_init(mysql); + if (!m_statement) { +//! @todo err 'out of memory' + return false; + } + res = mysql_stmt_prepare(m_statement, + (const char*)m_tempStatementString, m_tempStatementString.length()); + if (0 != res) { +//! @todo use mysql_stmt_error(stmt); to show error + return false; + } + + m_realParamCount = mysql_stmt_param_count(m_statement); + if (m_realParamCount<=0) { +//! @todo err + return false; + } + m_mysqlBind = new MYSQL_BIND[ m_realParamCount ]; + memset(m_mysqlBind, 0, sizeof(MYSQL_BIND)*m_realParamCount); //safe? +#endif + return true; +} + + +MySqlPreparedStatement::~MySqlPreparedStatement() +{ + done(); +} + +void MySqlPreparedStatement::done() +{ +#ifdef KEXI_USE_MYSQL_STMT + if (m_statement) { +//! @todo handle errors of mysql_stmt_close()? + mysql_stmt_close(m_statement); + m_statement = 0; + } + delete m_mysqlBind; + m_mysqlBind = 0; +#endif +} + +#ifdef KEXI_USE_MYSQL_STMT +#define BIND_NULL { \ + m_mysqlBind[arg].buffer_type = MYSQL_TYPE_NULL; \ + m_mysqlBind[arg].buffer = 0; \ + m_mysqlBind[arg].buffer_length = 0; \ + m_mysqlBind[arg].is_null = &dummyNull; \ + m_mysqlBind[arg].length = &str_length; } +#endif + +bool MySqlPreparedStatement::execute() +{ +#ifdef KEXI_USE_MYSQL_STMT + if (!m_statement || m_realParamCount<=0) + return false; + if ( mysql_stmt_errno(m_statement) == CR_SERVER_LOST ) { + //sanity: connection lost: reconnect +//! @todo KexiDB::Connection should be reconnected as well! + done(); + if (!init()) { + done(); + return false; + } + } + + if (m_resetRequired) { + mysql_stmt_reset(m_statement); + res = sqlite3_reset(prepared_st_handle); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + m_resetRequired = false; + } + + int arg = 0; + bool dummyNull = true; + unsigned long str_length; + KexiDB::Field *field; + + Field::List _dummy; + Field::ListIterator itFields(_dummy); + //for INSERT, we're iterating over inserting values + //for SELECT, we're iterating over WHERE conditions + if (m_type == SelectStatement) + itFields = *m_whereFields; + else if (m_type == InsertStatement) + itFields = m_fields->fieldsIterator(); + else + assert(0); //impl. error + + for (QValueListConstIterator<QVariant> it = m_args.constBegin(); + (field = itFields.current()) && arg < m_realParamCount; ++it, ++itFields, arg++) + { + if (it==m_args.constEnd() || (*it).isNull()) {//no value to bind or the value is null: bind NULL + BIND_NULL; + continue; + } + if (field->isTextType()) { +//! @todo optimize +m_stringBuffer[ 1024 ]; ??? + char *str = qstrncpy(m_stringBuffer, (const char*)(*it).toString().utf8(), 1024); + m_mysqlBind[arg].buffer_type = MYSQL_TYPE_STRING; + m_mysqlBind[arg].buffer = m_stringBuffer; + m_mysqlBind[arg].is_null = (my_bool*)0; + m_mysqlBind[arg].buffer_length = 1024; //? + m_mysqlBind[arg].length = &str_length; + } + else switch (field->type()) { + case KexiDB::Field::Byte: + case KexiDB::Field::ShortInteger: + case KexiDB::Field::Integer: + { +//! @todo what about unsigned > INT_MAX ? + bool ok; + const int value = (*it).toInt(&ok); + if (ok) { + if (field->type()==KexiDB::Field::Byte) + m_mysqlBind[arg].buffer_type = MYSQL_TYPE_TINY; + else if (field->type()==KexiDB::Field::ShortInteger) + m_mysqlBind[arg].buffer_type = MYSQL_TYPE_SHORT; + else if (field->type()==KexiDB::Field::Integer) + m_mysqlBind[arg].buffer_type = MYSQL_TYPE_LONG; + + m_mysqlBind[arg].is_null = (my_bool*)0; + m_mysqlBind[arg].length = 0; + + res = sqlite3_bind_int(prepared_st_handle, arg, value); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + } + else + BIND_NULL; + break; + } + case KexiDB::Field::Float: + case KexiDB::Field::Double: + res = sqlite3_bind_double(prepared_st_handle, arg, (*it).toDouble()); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + case KexiDB::Field::BigInteger: + { +//! @todo what about unsigned > LLONG_MAX ? + bool ok; + Q_LLONG value = (*it).toLongLong(&ok); + if (ok) { + res = sqlite3_bind_int64(prepared_st_handle, arg, value); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + } + else { + res = sqlite3_bind_null(prepared_st_handle, arg); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + } + break; + } + case KexiDB::Field::Boolean: + res = sqlite3_bind_text(prepared_st_handle, arg, + QString::number((*it).toBool() ? 1 : 0).latin1(), + 1, SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + case KexiDB::Field::Time: + res = sqlite3_bind_text(prepared_st_handle, arg, + (*it).toTime().toString(Qt::ISODate).latin1(), + sizeof("HH:MM:SS"), SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + case KexiDB::Field::Date: + res = sqlite3_bind_text(prepared_st_handle, arg, + (*it).toDate().toString(Qt::ISODate).latin1(), + sizeof("YYYY-MM-DD"), SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + case KexiDB::Field::DateTime: + res = sqlite3_bind_text(prepared_st_handle, arg, + (*it).toDateTime().toString(Qt::ISODate).latin1(), + sizeof("YYYY-MM-DDTHH:MM:SS"), SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + case KexiDB::Field::BLOB: + { + const QByteArray byteArray((*it).toByteArray()); + res = sqlite3_bind_blob(prepared_st_handle, arg, + (const char*)byteArray, byteArray.size(), SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + } + default: + KexiDBWarn << "PreparedStatement::execute(): unsupported field type: " + << field->type() << " - NULL value bound to column #" << arg << endl; + res = sqlite3_bind_null(prepared_st_handle, arg); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + } //switch + } + + //real execution + res = sqlite3_step(prepared_st_handle); + m_resetRequired = true; + if (m_type == InsertStatement && res == SQLITE_DONE) { + return true; + } + if (m_type == SelectStatement) { + //fetch result + + //todo + } +#else + m_resetRequired = true; + if (connection->insertRecord(*m_fields, m_args)) { + return true; + } + +#endif //KEXI_USE_MYSQL_STMT + return false; +} diff --git a/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.h b/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.h new file mode 100644 index 00000000..01478e9e --- /dev/null +++ b/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.h @@ -0,0 +1,56 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef MYSQLPREPAREDSTATEMENT_H +#define MYSQLPREPAREDSTATEMENT_H + +#include <kexidb/preparedstatement.h> +#include "mysqlconnection_p.h" + +//todo 1.1 - unfinished: #define KEXI_USE_MYSQL_STMT +// for 1.0 we're using unoptimized version + +namespace KexiDB +{ + +/*! Implementation of prepared statements for MySQL driver. */ +class MySqlPreparedStatement : public PreparedStatement, public MySqlConnectionInternal +{ + public: + MySqlPreparedStatement(StatementType type, ConnectionInternal& conn, FieldList& fields); + + virtual ~MySqlPreparedStatement(); + + virtual bool execute(); + + QCString m_tempStatementString; + +#ifdef KEXI_USE_MYSQL_STMT + int m_realParamCount; + MYSQL_STMT *m_statement; + MYSQL_BIND *m_mysqlBind; +#endif + bool m_resetRequired : 1; + + protected: + bool init(); + void done(); +}; +} +#endif diff --git a/kexi/kexidb/drivers/odbc/Makefile.am b/kexi/kexidb/drivers/odbc/Makefile.am new file mode 100644 index 00000000..2570181a --- /dev/null +++ b/kexi/kexidb/drivers/odbc/Makefile.am @@ -0,0 +1,21 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexidb_odbcdriver.la + +INCLUDES = -I$(srcdir)/../.. -I$(top_srcdir)/kexi $(all_includes) + +kexidb_odbcdriver_la_METASOURCES = AUTO + +kexidb_odbcdriver_la_SOURCES = odbcdriver.cpp odbcconnection.cpp + +kexidb_odbcdriver_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) -lodbc $(top_builddir)/kexi/kexidb/libkexidb.la + +kexidb_odbcdriver_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO) -no-undefined + + +kde_services_DATA = kexidb_odbcdriver.desktop + + +KDE_CXXFLAGS += -DKEXIDB_ODBC_DRIVER_EXPORT= -D__KEXIDB__= \ + -include $(top_srcdir)/kexi/kexidb/global.h + diff --git a/kexi/kexidb/drivers/odbc/kexidb_odbcdriver.desktop b/kexi/kexidb/drivers/odbc/kexidb_odbcdriver.desktop new file mode 100644 index 00000000..acad3c64 --- /dev/null +++ b/kexi/kexidb/drivers/odbc/kexidb_odbcdriver.desktop @@ -0,0 +1,54 @@ +[Desktop Entry] +Name=ODBC +Name[ta]=B0 +Comment=Kexi Open Database Connectivity Driver +Comment[bg]=Драйвер ODBC за Kexi +Comment[ca]=Controlador ODBC per Kexi +Comment[cy]=Gyrrydd Cysylltedd Cronfa Ddata Agored Kexi +Comment[da]=Kexi-driver for åben databaseforbindelse +Comment[de]=Kexi ODBC-Treiber +Comment[el]=Οδηγός σύνδεσης Open Database με το Kexi +Comment[eo]=Kexi "Open Database Connectivity"-pelilo +Comment[es]=Controlador «Open Database Connectivity» para Kexi +Comment[et]=Kexi ODBC (Open Database Connectivity) draiver +Comment[eu]=Kexi-ren ODBC kontrolatzailea +Comment[fa]=گردانندۀ اتصال دادگان Kexi Open +Comment[fi]=Kexin ODBC-ajuri +Comment[fr]=Pilote ODBC (connectivité de bases de données ouvertes) de Kexi +Comment[fy]=Kexi Open Databank Connectivity-stjoerprogramma +Comment[gl]=Controlador de ODBC de Kexi +Comment[he]=מנהל התקן של Kexi לחיבור למסד נתונים פתוח (ODBC) +Comment[hr]=Upravljački program za povezivanje s Kexi otvorenom bazom podataka +Comment[hu]=Kexi ODBC-meghajtó +Comment[is]=Kexi Open Database tengirekill +Comment[it]=Scorciatoia verso un progetto Kexi sul server della banca dati +Comment[ja]=Kexi ODBC (Open Database Connectivity) ドライバ +Comment[km]=កម្មវិធីបញ្ជាសម្រាប់តភ្ជាប់មូលដ្ឋានទិន្នន័យបើកចំហរបស់ Kexi +Comment[lv]=Kexi atvērtā datu bāzu savienojamības (ODBC) draiveris +Comment[ms]=Pemacu Kesambungan Pangkalan Data Terbuka Kexi +Comment[nb]=Tilkoblingsdriver for Kexis åpne database +Comment[nds]=ODBC-Driever för Kexi +Comment[ne]=केक्सी खुला डाटाबेस जडित ड्राइभर +Comment[nl]=Kexi Open Database Connectivity-stuurprogramma +Comment[pl]=Sterownik ODBC dla Kexi +Comment[pt]=Controlador de ODBC do Kexi +Comment[pt_BR]=Driver para ODBC do Kexi +Comment[ru]=Драйвер Kexi Open Database +Comment[sk]=Ovládač Kexi Open Database Connectivity +Comment[sl]=Gonilnik povezovanja odprte zbirke podatkov za Kexi +Comment[sr]=Kexi-јев управљачки програм за Open Database Connectivity (ODBC) +Comment[sr@Latn]=Kexi-jev upravljački program za Open Database Connectivity (ODBC) +Comment[sv]=Kexi-drivrutin för öppen databasanslutning +Comment[ta]=குறுவழி kexi திட்டப்பணிக் தரவுத்தள சேவையகம் +Comment[tg]=Драйвери Kexi Open Database +Comment[tr]=Kexi Açık Veritabanı Bağlanabilirlik Sürücüsü +Comment[uk]=Драйвер з'єднання Kexi Open Database +Comment[zh_CN]=Kexi 开放数据库连接驱动 +Comment[zh_TW]=Kexi 開放資料庫連線驅動程式 +X-KDE-Library=kexidb_odbcdriver +ServiceTypes=Kexi/DBDriver +Type=Service +InitialPreference=8 +MimeType=text/plain +X-Kexi-DriverType=Network +X-Kexi-DriverName=ODBC diff --git a/kexi/kexidb/drivers/odbc/odbcconnection.cpp b/kexi/kexidb/drivers/odbc/odbcconnection.cpp new file mode 100644 index 00000000..ec5a7cdf --- /dev/null +++ b/kexi/kexidb/drivers/odbc/odbcconnection.cpp @@ -0,0 +1,153 @@ +/* + This file is part of the KDE project + Copyright (C) 2004 Matt Rogers <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ +//unixODBC Includes +#include <sql.h> +#include <sqlext.h> +#include <sqltypes.h> + +//QT Includes +#include <qfile.h> +#include <qdir.h> + +//KDE Includes +#include <kgenericfactory.h> +#include <kdebug.h> + +//Kexi Includes +#include <kexidb/driver.h> +#include <kexidb/cursor.h> +#include <kexidb/error.h> + +//Local Includes +#include "odbcconnection.h" + +using namespace KexiDB; + +//! @internal +class ODBCConnectionPrivate +{ + public: + ConnectionData connData; + QString currentDB; + SQLHENV envHandle; + SQLHDBC connectionHandle; +}; + +ODBCConnection::ODBCConnection( Driver *driver, ConnectionData &conn_data ) + : Connection( driver, conn_data ) +{ + d = new ODBCConnectionPrivate; + //d->connData = conn_data; +} + +Cursor* ODBCConnection::prepareQuery(const QString& statement, uint cursor_options) +{ + Q_UNUSED( statement ); + Q_UNUSED( cursor_options ); + return 0; +} + +QString ODBCConnection::escapeString(const QString& str) const +{ + return str; +} + +QCString ODBCConnection::escapeString(const QCString& str) const +{ + return str; +} + +bool ODBCConnection::drv_connect() +{ + long result; + + result = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &d->envHandle ); + if ( result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO ) + return false; + + //We'll use ODBC 3.5 by default, so just get connection handle + result = SQLAllocHandle( SQL_HANDLE_DBC, d->envHandle, &d->connectionHandle ); + if ( result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO ) + { + SQLFreeHandle( SQL_HANDLE_ENV, d->envHandle ); + return false; + } + + result = SQLConnect( d->connectionHandle, (unsigned char*) d->connData.hostName.latin1(), + d->connData.hostName.length(), (unsigned char*) d->connData.userName.latin1(), + d->connData.userName.length(), (unsigned char*) d->connData.password.latin1(), + d->connData.password.length() ); + if ( result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO ) + { + SQLFreeHandle( SQL_HANDLE_DBC, d->connectionHandle ); + SQLFreeHandle( SQL_HANDLE_ENV, d->envHandle ); + return false; + } + + return true; +} + +bool ODBCConnection::drv_disconnect() +{ + SQLDisconnect( d->connectionHandle ); + SQLFreeHandle( SQL_HANDLE_DBC, d->connectionHandle ); + SQLFreeHandle( SQL_HANDLE_ENV, d->envHandle ); + return true; +} + +bool ODBCConnection::drv_getDatabasesList(QStringList &) +{ + return false; +} + +bool ODBCConnection::drv_createDatabase(const QString &) +{ + return false; +} + +bool ODBCConnection::drv_useDatabase(const QString &) +{ + return false; +} + +bool ODBCConnection::drv_closeDatabase() +{ + return false; +} + +bool ODBCConnection::drv_dropDatabase(const QString &) +{ + return false; +} + +bool ODBCConnection::drv_executeSQL(const QString &) +{ + return false; +} + +ODBCConnection::~ODBCConnection() +{ + drv_disconnect(); + destroy(); + delete d; +} + +#include "odbcconnection.moc" + diff --git a/kexi/kexidb/drivers/odbc/odbcconnection.h b/kexi/kexidb/drivers/odbc/odbcconnection.h new file mode 100644 index 00000000..8f551905 --- /dev/null +++ b/kexi/kexidb/drivers/odbc/odbcconnection.h @@ -0,0 +1,92 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Matt Rogers <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_CONN_ODBC_H +#define KEXIDB_CONN_ODBC_H + +#include <qstringlist.h> + +#include <kexidb/connection.h> +#include <kexidb/tableschema.h> + +#include <sql.h> +#include <sqlext.h> +#include <sqltypes.h> + +class ODBCConnectionPrivate; + +namespace KexiDB +{ +class Driver; + + +class ODBCConnection : public Connection +{ + Q_OBJECT + + public: + ~ODBCConnection(); + + virtual Cursor* prepareQuery( const QString& statement = QString::null, uint cursor_options = 0 ); + virtual QString escapeString(const QString& str) const; + virtual QCString escapeString(const QCString& str) const; + + protected: + /*! Used by driver */ + ODBCConnection( Driver *driver, ConnectionData &conn_data ); + + virtual bool drv_connect(); + + virtual bool drv_disconnect(); + + virtual bool drv_getDatabasesList( QStringList &list ); + + /*! Creates new database using connection. Note: Do not pass \a dbName + arg because for file-based engine (that has one database per connection) + it is defined during connection. */ + virtual bool drv_createDatabase( const QString &dbName = QString::null ); + + /*! Opens existing database using connection. Do not pass \a dbName + arg because for file-based engine (that has one database per connection) + it is defined during connection. If you pass it, + database file name will be changed. */ + virtual bool drv_useDatabase( const QString &dbName = QString::null ); + + virtual bool drv_closeDatabase(); + + /*! Drops database from the server using connection. + After drop, database shouldn't be accessible + anymore, so database file is just removed. See note from drv_useDatabase(). */ + virtual bool drv_dropDatabase( const QString &dbName = QString::null ); + + //virtual bool drv_createTable( const KexiDB::Table& table ); + + virtual bool drv_executeSQL( const QString& statement ); + + friend class ODBCDriver; + + private: + ODBCConnectionPrivate *d; +}; + + + +} + +#endif diff --git a/kexi/kexidb/drivers/odbc/odbcdriver.cpp b/kexi/kexidb/drivers/odbc/odbcdriver.cpp new file mode 100644 index 00000000..aac6a6c9 --- /dev/null +++ b/kexi/kexidb/drivers/odbc/odbcdriver.cpp @@ -0,0 +1,108 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Matt Rogers <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +//QT Includes +#include <qfile.h> +#include <qdir.h> +#include <qstring.h> +#include <qcstring.h> + +//KDE Includes +#include <kdebug.h> + +//Kexi Includes +#include <kexidb/connection.h> +#include <kexidb/drivermanager.h> +#include <kexidb/driver_p.h> + +//ODBC Includes +#include "odbcdriver.h" +#include "odbcconnection.h" + +using namespace KexiDB; + +KEXIDB_DRIVER_INFO( ODBCDriver, odbc ) + +ODBCDriver::ODBCDriver( QObject *parent, const char *name, const QStringList &args ) + : Driver( parent, name, args ) +{ + d->isFileDriver = false; + d->isDBOpenedAfterCreate = true; + d->features = SingleTransactions | CursorForward; + + //predefined properties + d->properties["client_library_version"] = "";//TODO + d->properties["default_server_encoding"] = ""; //TODO + + d->typeNames[ Field::Byte ] = "Byte"; + d->typeNames[ Field::ShortInteger ] = "ShortInteger"; + d->typeNames[ Field::Integer ] = "Integer"; + d->typeNames[ Field::BigInteger ] = "BigInteger"; + d->typeNames[ Field::Boolean ] = "Boolean"; + d->typeNames[ Field::Date ] = "Date"; + d->typeNames[ Field::DateTime ] = "DateTime"; + d->typeNames[ Field::Time ] = "Time"; + d->typeNames[ Field::Float ] = "Float"; + d->typeNames[ Field::Double ] = "Double"; + d->typeNames[ Field::Text ] = "Text"; + d->typeNames[ Field::LongText ] = "CLOB"; + d->typeNames[ Field::BLOB ] = "BLOB"; +} + +ODBCDriver::~ODBCDriver() +{ +} + +KexiDB::Connection* ODBCDriver::drv_createConnection( ConnectionData &conn_data ) +{ + Q_UNUSED( conn_data ); + return 0L; + //return new ODBCConnection( this, conn_data ); +} + +bool ODBCDriver::isSystemDatabaseName( const QString& name ) const +{ + Q_UNUSED( name ); + return false; +} + +bool ODBCDriver::isSystemObjectName( const QString& name ) +{ + Q_UNUSED( name ); + return false; +} + +bool ODBCDriver::isSystemFieldName( const QString& name ) const +{ + Q_UNUSED( name ); + return false; +} + +QString ODBCDriver::escapeString( const QString& str ) const +{ + return str; +} + +QCString ODBCDriver::escapeString( const QCString& str ) const +{ + return str; +} + +#include "odbcdriver.moc" + diff --git a/kexi/kexidb/drivers/odbc/odbcdriver.h b/kexi/kexidb/drivers/odbc/odbcdriver.h new file mode 100644 index 00000000..60681f21 --- /dev/null +++ b/kexi/kexidb/drivers/odbc/odbcdriver.h @@ -0,0 +1,73 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Matt Rogers <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_DRIVER_ODBC_H +#define KEXIDB_DRIVER_ODBC_H + +//Kexi Includes +#include <kexidb/driver.h> + +class QCString; +class QString; + +namespace KexiDB +{ + +class Connection; +class DriverManager; +class ODBCDriverPrivate; + +//! ODBC database driver. +/*! + * This is the ODBC Driver for Kexi. + * @author Matt Rogers <[email protected]> + */ +class ODBCDriver : public Driver +{ + Q_OBJECT + KEXIDB_DRIVER + + public: + ODBCDriver( QObject *parent, const char *name, const QStringList &args = QStringList() ); + ~ODBCDriver(); + + virtual bool isSystemDatabaseName( const QString& name ) const; + /** \return true if n is a system object name; + * \todo Find out what is a system object name and what isn't + */ + virtual bool isSystemObjectName( const QString& name ); + + /** + * \return true if \a n is a system field name; + * There aren't any system fields per tables, unless the table + * is a system table + */ + virtual bool isSystemFieldName( const QString& name ) const; + + virtual QString escapeString( const QString& str ) const; + virtual QCString escapeString( const QCString& str ) const; + + protected: + virtual Connection *drv_createConnection( ConnectionData &conn_data ); +}; + +} + +#endif + diff --git a/kexi/kexidb/drivers/pqxx/Makefile.am b/kexi/kexidb/drivers/pqxx/Makefile.am new file mode 100644 index 00000000..5129c84f --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/Makefile.am @@ -0,0 +1,22 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexidb_pqxxsqldriver.la + +INCLUDES = -I$(srcdir)/../../.. $(all_includes) -I$(PG_INCDIR) -I$(PQXX_INCDIR) + +kexidb_pqxxsqldriver_la_METASOURCES = AUTO + +kexidb_pqxxsqldriver_la_SOURCES = pqxxdriver.cpp pqxxcursor.cpp pqxxconnection.cpp \ + pqxxkeywords.cpp pqxxconnection_p.cpp pqxxpreparedstatement.cpp + +kexidb_pqxxsqldriver_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) -lpqxx ../../libkexidb.la + +kexidb_pqxxsqldriver_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) \ + -L$(PQXX_LIBDIR) -L$(PG_LIBDIR) $(VER_INFO) -no-undefined + +kde_services_DATA = kexidb_pqxxsqldriver.desktop + +noinst_HEADERS = pqxxconnection.h pqxxconnection_p.h + +KDE_CXXFLAGS += -DKEXIDB_PGSQL_DRIVER_EXPORT= -D__KEXIDB__= \ + -include $(top_srcdir)/kexi/kexidb/global.h diff --git a/kexi/kexidb/drivers/pqxx/README b/kexi/kexidb/drivers/pqxx/README new file mode 100644 index 00000000..6a1e6c3e --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/README @@ -0,0 +1,18 @@ +ReadMe For Kexi pqkexidb_pqxxslqdriver.desktop~xx PostgreSQL Driver + +This driver requires libpqxx available from pqxx.tk or gborg.postgresql.org. + +Currently the driver builds against 1.9.4 of libpqxx, but it should always work with the latest version. +When 2.0.0 comes out then that will be the version to use. + +The driver may require PostgreSQL >=7.4. Using the old api this was a requirement, but the rewrite +isnt far enough in to get into those kinds of details, so at the mement it should be happy with earlier versions. +Im using PostgreSQL from CVS so i cant say for sure. + +To build the driver you may need to add 'pqxx' to the list of subdirs in Makefile.am in kexi/drivers/ + +Thats it for now + +Adam Pigg +adampigg.9p.org.uk
\ No newline at end of file diff --git a/kexi/kexidb/drivers/pqxx/kexidb_pqxxsqldriver.desktop b/kexi/kexidb/drivers/pqxx/kexidb_pqxxsqldriver.desktop new file mode 100644 index 00000000..1f38241b --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/kexidb_pqxxsqldriver.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=PostgreSQL +Name[hi]=पोस्टग्रे-एसक्यूएल +Name[ne]=पोस्ट ग्रे एसक्यूएल +X-KDE-Library=kexidb_pqxxsqldriver +ServiceTypes=Kexi/DBDriver +Type=Service +InitialPreference=8 +X-Kexi-DriverName=PostgreSQL +X-Kexi-DriverType=Network +X-Kexi-KexiDBVersion=1.8 diff --git a/kexi/kexidb/drivers/pqxx/pqxxconnection.cpp b/kexi/kexidb/drivers/pqxx/pqxxconnection.cpp new file mode 100644 index 00000000..8465bcf4 --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxconnection.cpp @@ -0,0 +1,448 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Adam Pigg <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "pqxxconnection.h" +#include <qvariant.h> +#include <qfile.h> +#include <kdebug.h> +#include <kexidb/error.h> +#include <kexidb/global.h> +#include <klocale.h> +#include <string> +#include "pqxxpreparedstatement.h" +#include "pqxxconnection_p.h" +using namespace KexiDB; + +pqxxTransactionData::pqxxTransactionData(Connection *conn, bool nontransaction) + : TransactionData(conn) +{ + if (nontransaction) + data = new pqxx::nontransaction(*static_cast<pqxxSqlConnection*>(conn)->d->pqxxsql /* todo: add name? */); + else + data = new pqxx::transaction<>(*static_cast<pqxxSqlConnection*>(conn)->d->pqxxsql /* todo: add name? */); + if (!static_cast<pqxxSqlConnection*>(conn)->m_trans) { + static_cast<pqxxSqlConnection*>(conn)->m_trans = this; + } +} + +pqxxTransactionData::~pqxxTransactionData() +{ + if (static_cast<pqxxSqlConnection*>(m_conn)->m_trans == this) { + static_cast<pqxxSqlConnection*>(m_conn)->m_trans = 0; + } + delete data; + data = 0; +} + +//================================================================================== + +pqxxSqlConnection::pqxxSqlConnection(Driver *driver, ConnectionData &conn_data) + : Connection(driver,conn_data) + , d( new pqxxSqlConnectionInternal(this) ) + , m_trans(0) +{ +} + +//================================================================================== +//Do any tidying up before the object is deleted +pqxxSqlConnection::~pqxxSqlConnection() +{ + //delete m_trans; + destroy(); + delete d; +} + +//================================================================================== +//Return a new query based on a query statment +Cursor* pqxxSqlConnection::prepareQuery( const QString& statement, uint cursor_options) +{ + Q_UNUSED(cursor_options); + return new pqxxSqlCursor(this, statement, 1); //Always used buffered cursor +} + +//================================================================================== +//Return a new query based on a query object +Cursor* pqxxSqlConnection::prepareQuery( QuerySchema& query, uint cursor_options) +{ + Q_UNUSED(cursor_options); + return new pqxxSqlCursor(this, query, 1);//Always used buffered cursor +} + +//================================================================================== +//Properly escaped a database object name +QString pqxxSqlConnection::escapeName(const QString &name) const +{ + return QString("\"" + name + "\""); +} + +//================================================================================== +//Made this a noop +//We tell kexi we are connected, but we wont actually connect until we use a database! +bool pqxxSqlConnection::drv_connect(KexiDB::ServerVersionInfo& version) +{ + KexiDBDrvDbg << "pqxxSqlConnection::drv_connect" << endl; + version.clear(); + d->version = &version; //remember for later... +#ifdef __GNUC__ +#warning pqxxSqlConnection::drv_connect implement setting version info when we drop libpqxx for libpq +#endif + return true; +} + +//================================================================================== +//Made this a noop +//We tell kexi wehave disconnected, but it is actually handled by closeDatabse +bool pqxxSqlConnection::drv_disconnect() +{ + KexiDBDrvDbg << "pqxxSqlConnection::drv_disconnect: " << endl; + return true; +} + +//================================================================================== +//Return a list of database names +bool pqxxSqlConnection::drv_getDatabasesList( QStringList &list ) +{ +// KexiDBDrvDbg << "pqxxSqlConnection::drv_getDatabaseList" << endl; + + if (executeSQL("SELECT datname FROM pg_database WHERE datallowconn = TRUE")) + { + std::string N; + for (pqxx::result::const_iterator c = d->res->begin(); c != d->res->end(); ++c) + { + // Read value of column 0 into a string N + c[0].to(N); + // Copy the result into the return list + list << QString::fromLatin1 (N.c_str()); + } + return true; + } + + return false; +} + +//================================================================================== +//Create a new database +bool pqxxSqlConnection::drv_createDatabase( const QString &dbName ) +{ + KexiDBDrvDbg << "pqxxSqlConnection::drv_createDatabase: " << dbName << endl; + + if (executeSQL("CREATE DATABASE " + escapeName(dbName))) + return true; + + return false; +} + +//================================================================================== +//Use this as our connection instead of connect +bool pqxxSqlConnection::drv_useDatabase( const QString &dbName, bool *cancelled, + MessageHandler* msgHandler ) +{ + Q_UNUSED(cancelled); + Q_UNUSED(msgHandler); + KexiDBDrvDbg << "pqxxSqlConnection::drv_useDatabase: " << dbName << endl; + + QString conninfo; + QString socket; + QStringList sockets; + + if (data()->hostName.isEmpty() || data()->hostName == "localhost") + { + if (data()->localSocketFileName.isEmpty()) + { + sockets.append("/tmp/.s.PGSQL.5432"); + + for(QStringList::ConstIterator it = sockets.constBegin(); it != sockets.constEnd(); it++) + { + if(QFile(*it).exists()) + { + socket = (*it); + break; + } + } + } + else + { + socket=data()->localSocketFileName; //data()->fileName(); + } + } + else + { + conninfo = "host='" + data()->hostName + "'"; + } + + //Build up the connection string + if (data()->port == 0) + data()->port = 5432; + + conninfo += QString::fromLatin1(" port='%1'").arg(data()->port); + + conninfo += QString::fromLatin1(" dbname='%1'").arg(dbName); + + if (!data()->userName.isNull()) + conninfo += QString::fromLatin1(" user='%1'").arg(data()->userName); + + if (!data()->password.isNull()) + conninfo += QString::fromLatin1(" password='%1'").arg(data()->password); + + try + { + d->pqxxsql = new pqxx::connection( conninfo.latin1() ); + drv_executeSQL( "SET DEFAULT_WITH_OIDS TO ON" ); //Postgres 8.1 changed the default to no oids but we need them + + if (d->version) { +//! @todo set version using the connection pointer when we drop libpqxx for libpq + } + return true; + } + catch(const std::exception &e) + { + KexiDBDrvDbg << "pqxxSqlConnection::drv_useDatabase:exception - " << e.what() << endl; + d->errmsg = QString::fromUtf8( e.what() ); + + } + catch(...) + { + d->errmsg = i18n("Unknown error."); + } + return false; +} + +//================================================================================== +//Here we close the database connection +bool pqxxSqlConnection::drv_closeDatabase() +{ + KexiDBDrvDbg << "pqxxSqlConnection::drv_closeDatabase" << endl; +// if (isConnected()) +// { + delete d->pqxxsql; + return true; +// } +/* js: not needed, right? + else + { + d->errmsg = "Not connected to database backend"; + d->res = ERR_NO_CONNECTION; + } + return false;*/ +} + +//================================================================================== +//Drops the given database +bool pqxxSqlConnection::drv_dropDatabase( const QString &dbName ) +{ + KexiDBDrvDbg << "pqxxSqlConnection::drv_dropDatabase: " << dbName << endl; + + //FIXME Maybe should check that dbname is no the currentdb + if (executeSQL("DROP DATABASE " + escapeName(dbName))) + return true; + + return false; +} + +//================================================================================== +//Execute an SQL statement +bool pqxxSqlConnection::drv_executeSQL( const QString& statement ) +{ +// KexiDBDrvDbg << "pqxxSqlConnection::drv_executeSQL: " << statement << endl; + bool ok = false; + + // Clear the last result information... + delete d->res; + d->res = 0; + +// KexiDBDrvDbg << "About to try" << endl; + try + { + //Create a transaction + const bool implicityStarted = !m_trans; + if (implicityStarted) + (void)new pqxxTransactionData(this, true); + + // m_trans = new pqxx::nontransaction(*m_pqxxsql); +// KexiDBDrvDbg << "About to execute" << endl; + //Create a result object through the transaction + d->res = new pqxx::result(m_trans->data->exec(std::string(statement.utf8()))); +// KexiDBDrvDbg << "Executed" << endl; + //Commit the transaction + if (implicityStarted) { + pqxxTransactionData *t = m_trans; + drv_commitTransaction(t); + delete t; +// m_trans = 0; + } + + //If all went well then return true, errors picked up by the catch block + ok = true; + } + catch(const pqxx::sql_error& sqlerr) { + KexiDBDrvDbg << "pqxxSqlConnection::drv_executeSQL: sql_error exception - " << sqlerr.query().c_str() << endl; + } + catch (const pqxx::broken_connection& bcerr) { + KexiDBDrvDbg << "pqxxSqlConnection::drv_executeSQL: broken_connection exception" << endl; + } + catch (const std::exception &e) + { + //If an error ocurred then put the error description into _dbError + d->errmsg = QString::fromUtf8( e.what() ); + KexiDBDrvDbg << "pqxxSqlConnection::drv_executeSQL:exception - " << e.what() << endl; + } + catch(...) + { + d->errmsg = i18n("Unknown error."); + } + //KexiDBDrvDbg << "EXECUTE SQL OK: OID was " << (d->res ? d->res->inserted_oid() : 0) << endl; + return ok; +} + +//================================================================================== +//Return true if currently connected to a database, ignoring the m_is_connected falg. +bool pqxxSqlConnection::drv_isDatabaseUsed() const +{ + if (d->pqxxsql->is_open()) + { + return true; + } + return false; +} + +//================================================================================== +//Return the oid of the last insert - only works if sql was insert of 1 row +Q_ULLONG pqxxSqlConnection::drv_lastInsertRowID() +{ + if (d->res) + { + pqxx::oid theOid = d->res->inserted_oid(); + + if (theOid != pqxx::oid_none) + { + return (Q_ULLONG)theOid; + } + else + { + return 0; + } + } + return 0; +} + +//<queries taken from pqxxMigrate> +bool pqxxSqlConnection::drv_containsTable( const QString &tableName ) +{ + bool success; + return resultExists(QString("select 1 from pg_class where relkind='r' and relname LIKE %1") + .arg(driver()->escapeString(tableName)), success) && success; +} + +bool pqxxSqlConnection::drv_getTablesList( QStringList &list ) +{ + KexiDB::Cursor *cursor; + m_sql = "select lower(relname) from pg_class where relkind='r'"; + if (!(cursor = executeQuery( m_sql ))) { + KexiDBDrvWarn << "pqxxSqlConnection::drv_getTablesList(): !executeQuery()" << endl; + return false; + } + list.clear(); + cursor->moveFirst(); + while (!cursor->eof() && !cursor->error()) { + list += cursor->value(0).toString(); + cursor->moveNext(); + } + if (cursor->error()) { + deleteCursor(cursor); + return false; + } + return deleteCursor(cursor); +} +//</taken from pqxxMigrate> + +TransactionData* pqxxSqlConnection::drv_beginTransaction() +{ + return new pqxxTransactionData(this, false); +} + +bool pqxxSqlConnection::drv_commitTransaction(TransactionData *tdata) +{ + bool result = true; + try { + static_cast<pqxxTransactionData*>(tdata)->data->commit(); + } + catch (const std::exception &e) + { + //If an error ocurred then put the error description into _dbError + d->errmsg = QString::fromUtf8( e.what() ); + result = false; + } + catch (...) { + //! @todo + setError(); + result = false; + } + if (m_trans == tdata) + m_trans = 0; + return result; +} + +bool pqxxSqlConnection::drv_rollbackTransaction(TransactionData *tdata) +{ + bool result = true; + try { + static_cast<pqxxTransactionData*>(tdata)->data->abort(); + } + catch (const std::exception &e) + { + //If an error ocurred then put the error description into _dbError + d->errmsg = QString::fromUtf8( e.what() ); + + result = false; + } + catch (...) { + d->errmsg = i18n("Unknown error."); + result = false; + } + if (m_trans == tdata) + m_trans = 0; + return result; +} + +int pqxxSqlConnection::serverResult() +{ + return d->resultCode; +} + +QString pqxxSqlConnection::serverResultName() +{ + return QString::null; +} + +void pqxxSqlConnection::drv_clearServerResult() +{ + d->resultCode = 0; +} + +QString pqxxSqlConnection::serverErrorMsg() +{ + return d->errmsg; +} + +PreparedStatement::Ptr pqxxSqlConnection::prepareStatement(PreparedStatement::StatementType type, + FieldList& fields) +{ + return new pqxxPreparedStatement(type, *d, fields); +} +#include "pqxxconnection.moc" diff --git a/kexi/kexidb/drivers/pqxx/pqxxconnection.h b/kexi/kexidb/drivers/pqxx/pqxxconnection.h new file mode 100644 index 00000000..85bed42a --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxconnection.h @@ -0,0 +1,104 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Adam Pigg <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef PQXXCONNECTION_H +#define PQXXCONNECTION_H + +#include <qstringlist.h> + +#include <kexidb/connection.h> +#include "pqxxcursor.h" + + + + +namespace KexiDB +{ + +class pqxxSqlConnectionInternal; + +//! @internal +class pqxxTransactionData : public TransactionData +{ + public: + pqxxTransactionData(Connection *conn, bool nontransaction); + ~pqxxTransactionData(); + pqxx::transaction_base *data; +}; + +/** +@author Adam Pigg +*/ +class pqxxSqlConnection : public Connection +{ + Q_OBJECT + + public: + virtual ~pqxxSqlConnection(); + + virtual Cursor* prepareQuery( const QString& statement = QString::null, uint cursor_options = 0 ); + virtual Cursor* prepareQuery( QuerySchema& query, uint cursor_options = 0 ); + virtual PreparedStatement::Ptr prepareStatement(PreparedStatement::StatementType type, + FieldList& fields); + protected: + + pqxxSqlConnection( Driver *driver, ConnectionData &conn_data ); + + virtual bool drv_isDatabaseUsed() const; + virtual bool drv_connect(KexiDB::ServerVersionInfo& version); + virtual bool drv_disconnect(); + virtual bool drv_getDatabasesList( QStringList &list ); + virtual bool drv_createDatabase( const QString &dbName = QString::null ); + virtual bool drv_useDatabase( const QString &dbName = QString::null, bool *cancelled = 0, + MessageHandler* msgHandler = 0 ); + virtual bool drv_closeDatabase(); + virtual bool drv_dropDatabase( const QString &dbName = QString::null ); + virtual bool drv_executeSQL( const QString& statement ); + virtual Q_ULLONG drv_lastInsertRowID(); + +//TODO: move this somewhere to low level class (MIGRATION?) + virtual bool drv_getTablesList( QStringList &list ); +//TODO: move this somewhere to low level class (MIGRATION?) + virtual bool drv_containsTable( const QString &tableName ); + + virtual TransactionData* drv_beginTransaction(); + virtual bool drv_commitTransaction(TransactionData *); + virtual bool drv_rollbackTransaction(TransactionData *); + + //Error reporting + virtual int serverResult(); + virtual QString serverResultName(); + virtual void drv_clearServerResult(); + virtual QString serverErrorMsg(); + + pqxxSqlConnectionInternal *d; + private: + QString escapeName(const QString &tn) const; + // pqxx::transaction_base* m_trans; + //! temporary solution for executeSQL()... + pqxxTransactionData *m_trans; + + + + friend class pqxxSqlDriver; + friend class pqxxSqlCursor; + friend class pqxxTransactionData; +}; +} +#endif diff --git a/kexi/kexidb/drivers/pqxx/pqxxconnection_p.cpp b/kexi/kexidb/drivers/pqxx/pqxxconnection_p.cpp new file mode 100644 index 00000000..b4bc266a --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxconnection_p.cpp @@ -0,0 +1,51 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Adam Pigg <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ +// +// C++ Implementation: pqxxsqlconnectioninternal +// +// Description: +// +// +// Author: Adam Pigg <[email protected]>, (C) 2005 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#include "pqxxconnection_p.h" +#include <kdebug.h> + +using namespace KexiDB; +pqxxSqlConnectionInternal::pqxxSqlConnectionInternal(Connection *conn) + : ConnectionInternal(conn) + , pqxxsql(0) + , res(0) + , version(0) +{ +} + + +pqxxSqlConnectionInternal::~pqxxSqlConnectionInternal() +{ + +} + +void pqxxSqlConnectionInternal::storeResult() +{ + errmsg = ""; +} diff --git a/kexi/kexidb/drivers/pqxx/pqxxconnection_p.h b/kexi/kexidb/drivers/pqxx/pqxxconnection_p.h new file mode 100644 index 00000000..0c78e583 --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxconnection_p.h @@ -0,0 +1,63 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Adam Pigg <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ +// +// C++ Interface: pqxxsqlconnectioninternal +// +// Description: +// +// +// Author: Adam Pigg <[email protected]>, (C) 2005 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#ifndef PQXXSQLCONNECTIONINTERNAL_H +#define PQXXSQLCONNECTIONINTERNAL_H + +#include <kexidb/connection_p.h> +#include <pqxx/pqxx> + +namespace KexiDB +{ + +/** + @internal + @author Adam Pigg <[email protected]> +*/ +class pqxxSqlConnectionInternal : public ConnectionInternal +{ + public: + pqxxSqlConnectionInternal(Connection *conn); + + virtual ~pqxxSqlConnectionInternal(); + + //! stores last result's message + virtual void storeResult(); + + pqxx::connection* pqxxsql; + pqxx::result* res; + + KexiDB::ServerVersionInfo *version; //!< this is set in drv_connect(), so we can use it in drv_useDatabase() + //!< because pgsql really connects after "USE". + + QString errmsg; //!< server-specific message of last operation + int resultCode; //!< result code of last operation on server +}; +} +#endif diff --git a/kexi/kexidb/drivers/pqxx/pqxxcursor.cpp b/kexi/kexidb/drivers/pqxx/pqxxcursor.cpp new file mode 100644 index 00000000..0004cf92 --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxcursor.cpp @@ -0,0 +1,339 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Adam Pigg <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "pqxxcursor.h" +#include "pqxxconnection.h" +#include "pqxxconnection_p.h" + +#include <kexidb/error.h> +#include <kexidb/global.h> + +#include <klocale.h> +#include <kdebug.h> + +#include <cstdlib> + +using namespace KexiDB; + + +unsigned int pqxxSqlCursor_trans_num=0; //!< debug helper + +static QByteArray pgsqlByteaToByteArray(const pqxx::result::field& r) +{ + return KexiDB::pgsqlByteaToByteArray(r.c_str(), r.size()); +} + +//================================================================================== +//Constructor based on query statement +pqxxSqlCursor::pqxxSqlCursor(KexiDB::Connection* conn, const QString& statement, uint options): + Cursor(conn,statement, options) +{ +// KexiDBDrvDbg << "PQXXSQLCURSOR: constructor for query statement" << endl; + my_conn = static_cast<pqxxSqlConnection*>(conn)->d->pqxxsql; + m_options = Buffered; + m_res = 0; +// m_tran = 0; + m_implicityStarted = false; +} + +//================================================================================== +//Constructor base on query object +pqxxSqlCursor::pqxxSqlCursor(Connection* conn, QuerySchema& query, uint options ) + : Cursor( conn, query, options ) +{ +// KexiDBDrvDbg << "PQXXSQLCURSOR: constructor for query schema" << endl; + my_conn = static_cast<pqxxSqlConnection*>(conn)->d->pqxxsql; + m_options = Buffered; + m_res = 0; +// m_tran = 0; + m_implicityStarted = false; +} + +//================================================================================== +//Destructor +pqxxSqlCursor::~pqxxSqlCursor() +{ + close(); +} + +//================================================================================== +//Create a cursor result set +bool pqxxSqlCursor::drv_open() +{ +// KexiDBDrvDbg << "pqxxSqlCursor::drv_open:" << m_sql << endl; + + if (!my_conn->is_open()) + { +//! @todo this check should be moved to Connection! when drv_prepareQuery() arrive + //should never happen, but who knows + setError(ERR_NO_CONNECTION,i18n("No connection for cursor open operation specified")); + return false; + } + + QCString cur_name; + //Set up a transaction + try + { + //m_tran = new pqxx::work(*my_conn, "cursor_open"); + cur_name.sprintf("cursor_transaction%d", pqxxSqlCursor_trans_num++); + +// m_tran = new pqxx::nontransaction(*my_conn, (const char*)cur_name); + if (!((pqxxSqlConnection*)connection())->m_trans) { +// my_conn->drv_beginTransaction(); +// if (implicityStarted) + (void)new pqxxTransactionData((pqxxSqlConnection*)connection(), true); + m_implicityStarted = true; + } + + m_res = new pqxx::result(((pqxxSqlConnection*)connection())->m_trans->data->exec(std::string(m_sql.utf8()))); + ((pqxxSqlConnection*)connection()) + ->drv_commitTransaction(((pqxxSqlConnection*)connection())->m_trans); +// my_conn->m_trans->commit(); +// KexiDBDrvDbg << "pqxxSqlCursor::drv_open: trans. committed: " << cur_name <<endl; + + //We should now be placed before the first row, if any + m_fieldCount = m_res->columns() - (m_containsROWIDInfo ? 1 : 0); +//js m_opened=true; + m_afterLast=false; + m_records_in_buf = m_res->size(); + m_buffering_completed = true; + return true; + } + catch (const std::exception &e) + { + setError(ERR_DB_SPECIFIC, QString::fromUtf8( e.what()) ); + KexiDBDrvWarn << "pqxxSqlCursor::drv_open:exception - " << QString::fromUtf8( e.what() ) << endl; + } + catch(...) + { + setError(); + } +// delete m_tran; +// m_tran = 0; + if (m_implicityStarted) { + delete ((pqxxSqlConnection*)connection())->m_trans; + m_implicityStarted = false; + } +// KexiDBDrvDbg << "pqxxSqlCursor::drv_open: trans. rolled back! - " << cur_name <<endl; + return false; +} + +//================================================================================== +//Delete objects +bool pqxxSqlCursor::drv_close() +{ +//js m_opened=false; + + delete m_res; + m_res = 0; + +// if (m_implicityStarted) { +// delete m_tran; +// m_tran = 0; +// m_implicityStarted = false; +// } + + return true; +} + +//================================================================================== +//Gets the next record...does not need to do much, just return fetchend if at end of result set +void pqxxSqlCursor::drv_getNextRecord() +{ +// KexiDBDrvDbg << "pqxxSqlCursor::drv_getNextRecord, size is " <<m_res->size() << " Current Position is " << (long)at() << endl; + if(at() < m_res->size() && at() >=0) + { + m_result = FetchOK; + } + else if (at() >= m_res->size()) + { + m_result = FetchEnd; + } + else + { + m_result = FetchError; + } +} + +//================================================================================== +//Check the current position is within boundaries +void pqxxSqlCursor::drv_getPrevRecord() +{ +// KexiDBDrvDbg << "pqxxSqlCursor::drv_getPrevRecord" << endl; + + if(at() < m_res->size() && at() >=0) + { + m_result = FetchOK; + } + else if (at() >= m_res->size()) + { + m_result = FetchEnd; + } + else + { + m_result = FetchError; + } +} + +//================================================================================== +//Return the value for a given column for the current record +QVariant pqxxSqlCursor::value(uint pos) +{ + if (pos < m_fieldCount) + return pValue(pos); + else + return QVariant(); +} + +//================================================================================== +//Return the value for a given column for the current record - Private const version +QVariant pqxxSqlCursor::pValue(uint pos)const +{ + if (m_res->size() <= 0) + { + KexiDBDrvWarn << "pqxxSqlCursor::value - ERROR: result size not greater than 0" << endl; + return QVariant(); + } + + if (pos>=(m_fieldCount+(m_containsROWIDInfo ? 1 : 0))) + { +// KexiDBDrvWarn << "pqxxSqlCursor::value - ERROR: requested position is greater than the number of fields" << endl; + return QVariant(); + } + + KexiDB::Field *f = (m_fieldsExpanded && pos<QMIN(m_fieldsExpanded->count(), m_fieldCount)) + ? m_fieldsExpanded->at(pos)->field : 0; + +// KexiDBDrvDbg << "pqxxSqlCursor::value(" << pos << ")" << endl; + + //from most to least frequently used types: + if (f) //We probably have a schema type query so can use kexi to determin the row type + { + if ((f->isIntegerType()) || (/*ROWID*/!f && m_containsROWIDInfo && pos==m_fieldCount)) + { + return (*m_res)[at()][pos].as(int()); + } + else if (f->isTextType()) + { + return QString::fromUtf8((*m_res)[at()][pos].c_str()); //utf8? + } + else if (f->isFPNumericType()) + { + return (*m_res)[at()][pos].as(double()); + } + else if (f->typeGroup() == Field::BLOBGroup) + { +// pqxx::result::field r = (*m_res)[at()][pos]; +// kdDebug() << r.name() << ", " << r.c_str() << ", " << r.type() << ", " << r.size() << endl; + return ::pgsqlByteaToByteArray((*m_res)[at()][pos]); + } + } + else // We probably have a raw type query so use pqxx to determin the column type + { + return pgsqlCStrToVariant((*m_res)[at()][pos]); + } + + return QString::fromUtf8((*m_res)[at()][pos].c_str(), (*m_res)[at()][pos].size()); //utf8? +} + +//================================================================================== +//Return the current record as a char** +//who'd have thought we'd be using char** in this day and age :o) +const char** pqxxSqlCursor::rowData() const +{ +// KexiDBDrvDbg << "pqxxSqlCursor::recordData" << endl; + + const char** row; + + row = (const char**)malloc(m_res->columns()+1); + row[m_res->columns()] = NULL; + if (at() >= 0 && at() < m_res->size()) + { + for(int i = 0; i < (int)m_res->columns(); i++) + { + row[i] = (char*)malloc(strlen((*m_res)[at()][i].c_str())+1); + strcpy((char*)(*m_res)[at()][i].c_str(), row[i]); +// KexiDBDrvDbg << row[i] << endl; + } + } + else + { + KexiDBDrvWarn << "pqxxSqlCursor::recordData: m_at is invalid" << endl; + } + return row; +} + +//================================================================================== +//Store the current record in [data] +void pqxxSqlCursor::storeCurrentRow(RowData &data) const +{ +// KexiDBDrvDbg << "pqxxSqlCursor::storeCurrentRow: POSITION IS " << (long)m_at<< endl; + + if (m_res->size()<=0) + return; + + const uint realCount = m_fieldCount + (m_containsROWIDInfo ? 1 : 0); + data.resize(realCount); + + for( uint i=0; i<realCount; i++) + { + data[i] = pValue(i); + } +} + +//================================================================================== +// +void pqxxSqlCursor::drv_clearServerResult() +{ +//! @todo pqxxSqlCursor: stuff with server results +} + +//================================================================================== +//Add the current record to the internal buffer +//Implementation required but no need in this driver +//Result set is a buffer so do not need another +void pqxxSqlCursor::drv_appendCurrentRecordToBuffer() +{ + +} + +//================================================================================== +//Move internal pointer to internal buffer +1 +//Implementation required but no need in this driver +void pqxxSqlCursor::drv_bufferMovePointerNext() +{ + +} + +//================================================================================== +//Move internal pointer to internal buffer -1 +//Implementation required but no need in this driver +void pqxxSqlCursor::drv_bufferMovePointerPrev() +{ + +} + +//================================================================================== +//Move internal pointer to internal buffer to N +//Implementation required but no need in this driver +void pqxxSqlCursor::drv_bufferMovePointerTo(Q_LLONG to) +{ + Q_UNUSED(to); +} + diff --git a/kexi/kexidb/drivers/pqxx/pqxxcursor.h b/kexi/kexidb/drivers/pqxx/pqxxcursor.h new file mode 100644 index 00000000..d596acca --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxcursor.h @@ -0,0 +1,110 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Adam Pigg <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_CURSOR_PQXX_H +#define KEXIDB_CURSOR_PQXX_H + +#include <kexidb/cursor.h> +#include <kexidb/connection.h> +#include <kexidb/utils.h> + +#if 0 +#include <pqxx/all.h> +#else +#include <pqxx/pqxx> +#endif + +#include <pqxx/binarystring> +#include <migration/pqxx/pg_type.h> + +namespace KexiDB { + +class pqxxSqlCursor: public Cursor { +public: + virtual ~pqxxSqlCursor(); + + virtual QVariant value(uint i); + virtual const char** rowData() const; + virtual void storeCurrentRow(RowData &data) const; + +//TODO virtual const char *** bufferData() + +//TODO virtual int serverResult() const; + +//TODO virtual QString serverResultName() const; + +//TODO virtual QString serverErrorMsg() const; + +protected: + pqxxSqlCursor(Connection* conn, const QString& statement = QString::null, uint options = NoOptions ); + pqxxSqlCursor(Connection* conn, QuerySchema& query, uint options = NoOptions ); + virtual void drv_clearServerResult(); + virtual void drv_appendCurrentRecordToBuffer(); + virtual void drv_bufferMovePointerNext(); + virtual void drv_bufferMovePointerPrev(); + virtual void drv_bufferMovePointerTo(Q_LLONG to); + virtual bool drv_open(); + virtual bool drv_close(); + virtual void drv_getNextRecord(); + virtual void drv_getPrevRecord(); + +private: + pqxx::result* m_res; +// pqxx::nontransaction* m_tran; + pqxx::connection* my_conn; + QVariant pValue(uint pos)const; + bool m_implicityStarted : 1; + //QByteArray processBinaryData(pqxx::binarystring*) const; + friend class pqxxSqlConnection; +}; + +inline QVariant pgsqlCStrToVariant(const pqxx::result::field& r) +{ + switch(r.type()) + { + case BOOLOID: + return QString::fromLatin1(r.c_str(), r.size())=="true"; //TODO check formatting + case INT2OID: + case INT4OID: + case INT8OID: + return r.as(int()); + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + return r.as(double()); + case DATEOID: + return QString::fromUtf8(r.c_str(), r.size()); //TODO check formatting + case TIMEOID: + return QString::fromUtf8(r.c_str(), r.size()); //TODO check formatting + case TIMESTAMPOID: + return QString::fromUtf8(r.c_str(), r.size()); //TODO check formatting + case BYTEAOID: + return KexiDB::pgsqlByteaToByteArray(r.c_str(), r.size()); + case BPCHAROID: + case VARCHAROID: + case TEXTOID: + return QString::fromUtf8(r.c_str(), r.size()); //utf8? + default: + return QString::fromUtf8(r.c_str(), r.size()); //utf8? + } +} + +} + +#endif diff --git a/kexi/kexidb/drivers/pqxx/pqxxdriver.cpp b/kexi/kexidb/drivers/pqxx/pqxxdriver.cpp new file mode 100644 index 00000000..d8e6216d --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxdriver.cpp @@ -0,0 +1,181 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Adam Pigg <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/connection.h> +#include <kexidb/drivermanager.h> +#include <kexidb/driver_p.h> +#include <kexidb/utils.h> +#include "pqxxdriver.h" +#include "pqxxconnection.h" +#include <string> + +#include <kdebug.h> + +using namespace KexiDB; + +KEXIDB_DRIVER_INFO( pqxxSqlDriver, pqxxsql ) + +//================================================================================== +// +pqxxSqlDriver::pqxxSqlDriver( QObject *parent, const char *name, const QStringList &args ) + : Driver( parent, name, args ) +{ + d->isFileDriver = false; + d->features = SingleTransactions | CursorForward | CursorBackward; +//! @todo enable this when kexidb supports multiple: d->features = MultipleTransactions | CursorForward | CursorBackward; + + beh->UNSIGNED_TYPE_KEYWORD = ""; + beh->ROW_ID_FIELD_NAME = "oid"; + beh->SPECIAL_AUTO_INCREMENT_DEF = false; + beh->AUTO_INCREMENT_TYPE = "SERIAL"; + beh->AUTO_INCREMENT_FIELD_OPTION = ""; + beh->AUTO_INCREMENT_PK_FIELD_OPTION = "PRIMARY KEY"; + beh->ALWAYS_AVAILABLE_DATABASE_NAME = "template1"; + beh->QUOTATION_MARKS_FOR_IDENTIFIER = '"'; + beh->SQL_KEYWORDS = keywords; + initSQLKeywords(233); + + //predefined properties + d->properties["client_library_version"] = "";//TODO + d->properties["default_server_encoding"] = ""; //TODO + + d->typeNames[Field::Byte]="SMALLINT"; + d->typeNames[Field::ShortInteger]="SMALLINT"; + d->typeNames[Field::Integer]="INTEGER"; + d->typeNames[Field::BigInteger]="BIGINT"; + d->typeNames[Field::Boolean]="BOOLEAN"; + d->typeNames[Field::Date]="DATE"; + d->typeNames[Field::DateTime]="TIMESTAMP"; + d->typeNames[Field::Time]="TIME"; + d->typeNames[Field::Float]="REAL"; + d->typeNames[Field::Double]="DOUBLE PRECISION"; + d->typeNames[Field::Text]="CHARACTER VARYING"; + d->typeNames[Field::LongText]="TEXT"; + d->typeNames[Field::BLOB]="BYTEA"; +} + +//================================================================================== +//Override the default implementation to allow for NUMERIC type natively +QString pqxxSqlDriver::sqlTypeName(int id_t, int p) const +{ + if (id_t==Field::Null) + return "NULL"; + if (id_t==Field::Float || id_t==Field::Double) + { + if (p>0) + { + return "NUMERIC"; + } + else + { + return d->typeNames[id_t]; + } + } + else + { + return d->typeNames[id_t]; + } +} + +//================================================================================== +// +pqxxSqlDriver::~pqxxSqlDriver() +{ +// delete d; +} + +//================================================================================== +// +KexiDB::Connection* +pqxxSqlDriver::drv_createConnection( ConnectionData &conn_data ) +{ + return new pqxxSqlConnection( this, conn_data ); +} + +//================================================================================== +// +bool pqxxSqlDriver::isSystemObjectName( const QString& n ) const +{ + return Driver::isSystemObjectName(n); +} + +//================================================================================== +// +bool pqxxSqlDriver::drv_isSystemFieldName( const QString& ) const +{ + return false; +} + +//================================================================================== +// +bool pqxxSqlDriver::isSystemDatabaseName( const QString& n ) const +{ + return n.lower()=="template1" || n.lower()=="template0"; +} + +//================================================================================== +// +QString pqxxSqlDriver::escapeString( const QString& str) const +{ + return QString::fromLatin1("'") + + QString::fromAscii( pqxx::sqlesc(std::string(str.utf8())).c_str() ) + + QString::fromLatin1("'"); +} + +//================================================================================== +// +QCString pqxxSqlDriver::escapeString( const QCString& str) const +{ + return QCString("'") + + QCString( pqxx::sqlesc(QString(str).ascii()).c_str() ) + + QCString("'"); +} + +//================================================================================== +// +QString pqxxSqlDriver::drv_escapeIdentifier( const QString& str) const { + return QString(str).replace( '"', "\"\"" ); +} + +//================================================================================== +// +QCString pqxxSqlDriver::drv_escapeIdentifier( const QCString& str) const { + return QCString(str).replace( '"', "\"\"" ); +} + +//================================================================================== +// +QString pqxxSqlDriver::escapeBLOB(const QByteArray& array) const +{ + return KexiDB::escapeBLOB(array, KexiDB::BLOBEscapeOctal); +} + +QString pqxxSqlDriver::valueToSQL( uint ftype, const QVariant& v ) const +{ + if (ftype==Field::Boolean) { + // use SQL compliant TRUE or FALSE as described here + // http://www.postgresql.org/docs/8.0/interactive/datatype-boolean.html + // 1 or 0 does not work + return v.toInt()==0 ? QString::fromLatin1("FALSE") : QString::fromLatin1("TRUE"); + } + return Driver::valueToSQL(ftype, v); +} + + +#include "pqxxdriver.moc" diff --git a/kexi/kexidb/drivers/pqxx/pqxxdriver.h b/kexi/kexidb/drivers/pqxx/pqxxdriver.h new file mode 100644 index 00000000..bbfdddc3 --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxdriver.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Adam Pigg <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_DRIVER_PQXX_H +#define KEXIDB_DRIVER_PQXX_H + +#include <qstringlist.h> + +#include <kexidb/driver.h> + +namespace KexiDB +{ + +class Connection; +class DriverManager; + +//! PostgreSQL database driver. +class pqxxSqlDriver : public Driver +{ + Q_OBJECT + KEXIDB_DRIVER + + public: + pqxxSqlDriver( QObject *parent, const char *name, const QStringList &args = QStringList() ); + ~pqxxSqlDriver(); + + virtual bool isSystemObjectName( const QString& n )const; + virtual bool isSystemDatabaseName( const QString& n )const; + + //! Escape a string for use as a value + virtual QString escapeString( const QString& str) const; + virtual QCString escapeString( const QCString& str) const; + virtual QString sqlTypeName(int id_t, int p=0) const; + + //! Escape BLOB value \a array + virtual QString escapeBLOB(const QByteArray& array) const; + + /*! Escapes and converts value \a v (for type \a ftype) + to string representation required by SQL commands. + Reimplemented for boolean type only to use SQL compliant TRUE or FALSE */ + virtual QString valueToSQL( uint ftype, const QVariant& v ) const; + + protected: + virtual QString drv_escapeIdentifier( const QString& str) const; + virtual QCString drv_escapeIdentifier( const QCString& str) const; + virtual Connection *drv_createConnection( ConnectionData &conn_data ); + virtual bool drv_isSystemFieldName( const QString& n )const; + + private: + static const char *keywords[]; +}; + +}; + +#endif diff --git a/kexi/kexidb/drivers/pqxx/pqxxkeywords.cpp b/kexi/kexidb/drivers/pqxx/pqxxkeywords.cpp new file mode 100644 index 00000000..cc1a9f6e --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxkeywords.cpp @@ -0,0 +1,244 @@ + /* + * This file has been automatically generated from + * koffice/kexi/tools/sql_keywords/sql_keywords.sh and + * postgresql-7.4.6/src/backend/parser/keywords.c. + * + * Please edit the sql_keywords.sh, not this file! + */ +#include <pqxxdriver.h> + +namespace KexiDB { + const char* pqxxSqlDriver::keywords[] = { + "ABORT", + "ABSOLUTE", + "ACCESS", + "ACTION", + "ADD", + "AGGREGATE", + "ALTER", + "ANALYSE", + "ANALYZE", + "ANY", + "ARRAY", + "ASSERTION", + "ASSIGNMENT", + "AT", + "AUTHORIZATION", + "BACKWARD", + "BIGINT", + "BINARY", + "BIT", + "BOOLEAN", + "BOTH", + "CACHE", + "CALLED", + "CAST", + "CHAIN", + "CHAR", + "CHARACTER", + "CHARACTERISTICS", + "CHECKPOINT", + "CLASS", + "CLOSE", + "CLUSTER", + "COALESCE", + "COLUMN", + "COMMENT", + "COMMITTED", + "CONSTRAINTS", + "CONVERSION", + "CONVERT", + "COPY", + "CREATEDB", + "CREATEUSER", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "CURRENT_USER", + "CURSOR", + "CYCLE", + "DAY", + "DEALLOCATE", + "DEC", + "DECIMAL", + "DECLARE", + "DEFAULTS", + "DEFERRABLE", + "DEFERRED", + "DEFINER", + "DELIMITER", + "DELIMITERS", + "DO", + "DOMAIN", + "DOUBLE", + "EACH", + "ENCODING", + "ENCRYPTED", + "ESCAPE", + "EXCEPT", + "EXCLUDING", + "EXCLUSIVE", + "EXECUTE", + "EXISTS", + "EXTERNAL", + "EXTRACT", + "FALSE", + "FETCH", + "FIRST", + "FLOAT", + "FORCE", + "FORWARD", + "FREEZE", + "FUNCTION", + "GLOBAL", + "GRANT", + "HANDLER", + "HOLD", + "HOUR", + "ILIKE", + "IMMEDIATE", + "IMMUTABLE", + "IMPLICIT", + "INCLUDING", + "INCREMENT", + "INHERITS", + "INITIALLY", + "INOUT", + "INPUT", + "INSENSITIVE", + "INSTEAD", + "INT", + "INTERSECT", + "INTERVAL", + "INVOKER", + "ISNULL", + "ISOLATION", + "LANCOMPILER", + "LANGUAGE", + "LAST", + "LEADING", + "LEVEL", + "LISTEN", + "LOAD", + "LOCAL", + "LOCALTIME", + "LOCALTIMESTAMP", + "LOCATION", + "LOCK", + "MAXVALUE", + "MINUTE", + "MINVALUE", + "MODE", + "MONTH", + "MOVE", + "NAMES", + "NATIONAL", + "NCHAR", + "NEW", + "NEXT", + "NO", + "NOCREATEDB", + "NOCREATEUSER", + "NONE", + "NOTHING", + "NOTIFY", + "NOTNULL", + "NULLIF", + "NUMERIC", + "OF", + "OFF", + "OIDS", + "OLD", + "ONLY", + "OPERATOR", + "OPTION", + "OUT", + "OVERLAPS", + "OVERLAY", + "OWNER", + "PARTIAL", + "PASSWORD", + "PATH", + "PENDANT", + "PLACING", + "POSITION", + "PRECISION", + "PREPARE", + "PRESERVE", + "PRIOR", + "PRIVILEGES", + "PROCEDURAL", + "PROCEDURE", + "READ", + "REAL", + "RECHECK", + "REINDEX", + "RELATIVE", + "RENAME", + "RESET", + "RESTART", + "RETURNS", + "REVOKE", + "ROWS", + "RULE", + "SCHEMA", + "SCROLL", + "SECOND", + "SECURITY", + "SEQUENCE", + "SERIALIZABLE", + "SESSION", + "SESSION_USER", + "SETOF", + "SHARE", + "SHOW", + "SIMPLE", + "SMALLINT", + "SOME", + "STABLE", + "START", + "STATEMENT", + "STATISTICS", + "STDIN", + "STDOUT", + "STORAGE", + "STRICT", + "SUBSTRING", + "SYSID", + "TEMP", + "TEMPLATE", + "TIME", + "TIMESTAMP", + "TOAST", + "TRAILING", + "TREAT", + "TRIGGER", + "TRIM", + "TRUE", + "TRUNCATE", + "TRUSTED", + "TYPE", + "UNENCRYPTED", + "UNKNOWN", + "UNLISTEN", + "UNTIL", + "USAGE", + "USER", + "VACUUM", + "VALID", + "VALIDATOR", + "VARCHAR", + "VARYING", + "VERBOSE", + "VERSION", + "VIEW", + "VOLATILE", + "WITH", + "WITHOUT", + "WORK", + "WRITE", + "YEAR", + "ZONE", + 0 + }; +} diff --git a/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.cpp b/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.cpp new file mode 100644 index 00000000..5c87f78a --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.cpp @@ -0,0 +1,56 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Adam Pigg <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ +// +// C++ Implementation: pqxxpreparedstatement +// +// Description: +// +// +// Author: Adam Pigg <[email protected]>, (C) 2005 +// +// Copyright: See COPYING file that comes with this distribution +// +#include "pqxxpreparedstatement.h" +#include <kdebug.h> +using namespace KexiDB; + +pqxxPreparedStatement::pqxxPreparedStatement( + StatementType type, ConnectionInternal& conn, FieldList& fields) + : KexiDB::PreparedStatement(type, conn, fields) + , m_conn(conn.connection) +{ +// KexiDBDrvDbg << "pqxxPreparedStatement: Construction" << endl; +} + + +pqxxPreparedStatement::~pqxxPreparedStatement() +{ +} + +bool pqxxPreparedStatement::execute() +{ +// KexiDBDrvDbg << "pqxxPreparedStatement::execute()" << endl; + m_resetRequired = true; + if (m_conn->insertRecord(*m_fields, m_args)) { + return true; + } + return false; +} + + diff --git a/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.h b/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.h new file mode 100644 index 00000000..232454d3 --- /dev/null +++ b/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Adam Pigg <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ +// +// C++ Interface: pqxxpreparedstatement +// +// Description: +// +// +#ifndef PQXXPREPAREDSTATEMENT_H +#define PQXXPREPAREDSTATEMENT_H +#include <kexidb/preparedstatement.h> +#include <kexidb/connection_p.h> + +namespace KexiDB +{ +/** + @author Adam Pigg <[email protected]> +*/ +class pqxxPreparedStatement : public PreparedStatement +{ + public: + pqxxPreparedStatement(StatementType type, ConnectionInternal& conn, FieldList& fields); + + virtual ~pqxxPreparedStatement(); + + virtual bool execute(); + bool m_resetRequired : 1; + + private: + Connection* m_conn; +}; +} +#endif diff --git a/kexi/kexidb/drivers/sqlite/Makefile.am b/kexi/kexidb/drivers/sqlite/Makefile.am new file mode 100644 index 00000000..fc0ad677 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/Makefile.am @@ -0,0 +1,27 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexidb_sqlite3driver.la + +INCLUDES = -I$(top_srcdir)/kexi/3rdparty/kexisql3/src -I$(srcdir)/../.. \ + -I$(top_srcdir)/kexi $(all_includes) + +kexidb_sqlite3driver_la_METASOURCES = AUTO + +kexidb_sqlite3driver_la_SOURCES = sqliteconnection.cpp sqlitedriver.cpp sqlitecursor.cpp \ +sqlitekeywords.cpp sqlitepreparedstatement.cpp sqlitevacuum.cpp sqliteadmin.cpp \ +sqlitealter.cpp + +kexidb_sqlite3driver_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) \ + $(top_builddir)/kexi/3rdparty/kexisql3/src/libkexisql3.la \ + $(top_builddir)/kexi/kexidb/libkexidb.la \ + $(top_builddir)/kexi/kexidb/parser/libkexidbparser.la + +kexidb_sqlite3driver_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO) + + +kde_services_DATA = kexidb_sqlite3driver.desktop + + +KDE_CXXFLAGS += -DKEXIDB_SQLITE_DRIVER_EXPORT= -D__KEXIDB__= \ + -include $(top_srcdir)/kexi/kexidb/global.h + diff --git a/kexi/kexidb/drivers/sqlite/driver/sqlite.h b/kexi/kexidb/drivers/sqlite/driver/sqlite.h new file mode 100644 index 00000000..680f81e2 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/driver/sqlite.h @@ -0,0 +1,687 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the SQLite library +** presents to client programs. +** +** @(#) $Id: sqlite.h 614463 2006-12-17 21:08:15Z staniek $ +*/ +#ifndef _SQLITE_H_ +#define _SQLITE_H_ +#include <stdarg.h> /* Needed for the definition of va_list */ + +/* +** Make sure we can call this stuff from C++. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +/* +** The version of the SQLite library. +*/ +#define SQLITE_VERSION "2.8.2" + +/* +** The version string is also compiled into the library so that a program +** can check to make sure that the lib*.a file and the *.h file are from +** the same version. +*/ +extern const char sqlite_version[]; + +/* +** The SQLITE_UTF8 macro is defined if the library expects to see +** UTF-8 encoded data. The SQLITE_ISO8859 macro is defined if the +** iso8859 encoded should be used. +*/ +#define SQLITE_ISO8859 1 + +/* +** The following constant holds one of two strings, "UTF-8" or "iso8859", +** depending on which character encoding the SQLite library expects to +** see. The character encoding makes a difference for the LIKE and GLOB +** operators and for the LENGTH() and SUBSTR() functions. +*/ +extern const char sqlite_encoding[]; + +/* +** Each open sqlite database is represented by an instance of the +** following opaque structure. +*/ +typedef struct sqlite sqlite; + +/* +** A function to open a new sqlite database. +** +** If the database does not exist and mode indicates write +** permission, then a new database is created. If the database +** does not exist and mode does not indicate write permission, +** then the open fails, an error message generated (if errmsg!=0) +** and the function returns 0. +** +** If mode does not indicates user write permission, then the +** database is opened read-only. +** +** The Truth: As currently implemented, all databases are opened +** for writing all the time. Maybe someday we will provide the +** ability to open a database readonly. The mode parameters is +** provided in anticipation of that enhancement. +*/ +sqlite *sqlite_open(const char *filename, int mode, char **errmsg); + +/* +** A function to close the database. +** +** Call this function with a pointer to a structure that was previously +** returned from sqlite_open() and the corresponding database will by closed. +*/ +void sqlite_close(sqlite *); + +/* +** The type for a callback function. +*/ +typedef int (*sqlite_callback)(void*,int,char**, char**); + +/* +** A function to executes one or more statements of SQL. +** +** If one or more of the SQL statements are queries, then +** the callback function specified by the 3rd parameter is +** invoked once for each row of the query result. This callback +** should normally return 0. If the callback returns a non-zero +** value then the query is aborted, all subsequent SQL statements +** are skipped and the sqlite_exec() function returns the SQLITE_ABORT. +** +** The 4th parameter is an arbitrary pointer that is passed +** to the callback function as its first parameter. +** +** The 2nd parameter to the callback function is the number of +** columns in the query result. The 3rd parameter to the callback +** is an array of strings holding the values for each column. +** The 4th parameter to the callback is an array of strings holding +** the names of each column. +** +** The callback function may be NULL, even for queries. A NULL +** callback is not an error. It just means that no callback +** will be invoked. +** +** If an error occurs while parsing or evaluating the SQL (but +** not while executing the callback) then an appropriate error +** message is written into memory obtained from malloc() and +** *errmsg is made to point to that message. The calling function +** is responsible for freeing the memory that holds the error +** message. Use sqlite_freemem() for this. If errmsg==NULL, +** then no error message is ever written. +** +** The return value is is SQLITE_OK if there are no errors and +** some other return code if there is an error. The particular +** return value depends on the type of error. +** +** If the query could not be executed because a database file is +** locked or busy, then this function returns SQLITE_BUSY. (This +** behavior can be modified somewhat using the sqlite_busy_handler() +** and sqlite_busy_timeout() functions below.) +*/ +int sqlite_exec( + sqlite*, /* An open database */ + const char *sql, /* SQL to be executed */ + sqlite_callback, /* Callback function */ + void *, /* 1st argument to callback function */ + char **errmsg /* Error msg written here */ +); + +/* +** Return values for sqlite_exec() and sqlite_step() +*/ +#define SQLITE_OK 0 /* Successful result */ +#define SQLITE_ERROR 1 /* SQL error or missing database */ +#define SQLITE_INTERNAL 2 /* An internal logic error in SQLite */ +#define SQLITE_PERM 3 /* Access permission denied */ +#define SQLITE_ABORT 4 /* Callback routine requested an abort */ +#define SQLITE_BUSY 5 /* The database file is locked */ +#define SQLITE_LOCKED 6 /* A table in the database is locked */ +#define SQLITE_NOMEM 7 /* A malloc() failed */ +#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ +#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite_interrupt() */ +#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ +#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ +#define SQLITE_NOTFOUND 12 /* (Internal Only) Table or record not found */ +#define SQLITE_FULL 13 /* Insertion failed because database is full */ +#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ +#define SQLITE_PROTOCOL 15 /* Database lock protocol error */ +#define SQLITE_EMPTY 16 /* (Internal Only) Database table is empty */ +#define SQLITE_SCHEMA 17 /* The database schema changed */ +#define SQLITE_TOOBIG 18 /* Too much data for one row of a table */ +#define SQLITE_CONSTRAINT 19 /* Abort due to contraint violation */ +#define SQLITE_MISMATCH 20 /* Data type mismatch */ +#define SQLITE_MISUSE 21 /* Library used incorrectly */ +#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ +#define SQLITE_AUTH 23 /* Authorization denied */ +#define SQLITE_FORMAT 24 /* Auxiliary database format error */ +#define SQLITE_ROW 100 /* sqlite_step() has another row ready */ +#define SQLITE_DONE 101 /* sqlite_step() has finished executing */ + +/* +** Each entry in an SQLite table has a unique integer key. (The key is +** the value of the INTEGER PRIMARY KEY column if there is such a column, +** otherwise the key is generated at random. The unique key is always +** available as the ROWID, OID, or _ROWID_ column.) The following routine +** returns the integer key of the most recent insert in the database. +** +** This function is similar to the mysql_insert_id() function from MySQL. +*/ +int sqlite_last_insert_rowid(sqlite*); + +/* +** This function returns the number of database rows that were changed +** (or inserted or deleted) by the most recent called sqlite_exec(). +** +** All changes are counted, even if they were later undone by a +** ROLLBACK or ABORT. Except, changes associated with creating and +** dropping tables are not counted. +** +** If a callback invokes sqlite_exec() recursively, then the changes +** in the inner, recursive call are counted together with the changes +** in the outer call. +** +** SQLite implements the command "DELETE FROM table" without a WHERE clause +** by dropping and recreating the table. (This is much faster than going +** through and deleting individual elements form the table.) Because of +** this optimization, the change count for "DELETE FROM table" will be +** zero regardless of the number of elements that were originally in the +** table. To get an accurate count of the number of rows deleted, use +** "DELETE FROM table WHERE 1" instead. +*/ +int sqlite_changes(sqlite*); + +/* If the parameter to this routine is one of the return value constants +** defined above, then this routine returns a constant text string which +** descripts (in English) the meaning of the return value. +*/ +const char *sqlite_error_string(int); +#define sqliteErrStr sqlite_error_string /* Legacy. Do not use in new code. */ + +/* This function causes any pending database operation to abort and +** return at its earliest opportunity. This routine is typically +** called in response to a user action such as pressing "Cancel" +** or Ctrl-C where the user wants a long query operation to halt +** immediately. +*/ +void sqlite_interrupt(sqlite*); + + +/* This function returns true if the given input string comprises +** one or more complete SQL statements. +** +** The algorithm is simple. If the last token other than spaces +** and comments is a semicolon, then return true. otherwise return +** false. +*/ +int sqlite_complete(const char *sql); + +/* +** This routine identifies a callback function that is invoked +** whenever an attempt is made to open a database table that is +** currently locked by another process or thread. If the busy callback +** is NULL, then sqlite_exec() returns SQLITE_BUSY immediately if +** it finds a locked table. If the busy callback is not NULL, then +** sqlite_exec() invokes the callback with three arguments. The +** second argument is the name of the locked table and the third +** argument is the number of times the table has been busy. If the +** busy callback returns 0, then sqlite_exec() immediately returns +** SQLITE_BUSY. If the callback returns non-zero, then sqlite_exec() +** tries to open the table again and the cycle repeats. +** +** The default busy callback is NULL. +** +** Sqlite is re-entrant, so the busy handler may start a new query. +** (It is not clear why anyone would every want to do this, but it +** is allowed, in theory.) But the busy handler may not close the +** database. Closing the database from a busy handler will delete +** data structures out from under the executing query and will +** probably result in a coredump. +*/ +void sqlite_busy_handler(sqlite*, int(*)(void*,const char*,int), void*); + +/* +** This routine sets a busy handler that sleeps for a while when a +** table is locked. The handler will sleep multiple times until +** at least "ms" milleseconds of sleeping have been done. After +** "ms" milleseconds of sleeping, the handler returns 0 which +** causes sqlite_exec() to return SQLITE_BUSY. +** +** Calling this routine with an argument less than or equal to zero +** turns off all busy handlers. +*/ +void sqlite_busy_timeout(sqlite*, int ms); + +/* +** This next routine is really just a wrapper around sqlite_exec(). +** Instead of invoking a user-supplied callback for each row of the +** result, this routine remembers each row of the result in memory +** obtained from malloc(), then returns all of the result after the +** query has finished. +** +** As an example, suppose the query result where this table: +** +** Name | Age +** ----------------------- +** Alice | 43 +** Bob | 28 +** Cindy | 21 +** +** If the 3rd argument were &azResult then after the function returns +** azResult will contain the following data: +** +** azResult[0] = "Name"; +** azResult[1] = "Age"; +** azResult[2] = "Alice"; +** azResult[3] = "43"; +** azResult[4] = "Bob"; +** azResult[5] = "28"; +** azResult[6] = "Cindy"; +** azResult[7] = "21"; +** +** Notice that there is an extra row of data containing the column +** headers. But the *nrow return value is still 3. *ncolumn is +** set to 2. In general, the number of values inserted into azResult +** will be ((*nrow) + 1)*(*ncolumn). +** +** After the calling function has finished using the result, it should +** pass the result data pointer to sqlite_free_table() in order to +** release the memory that was malloc-ed. Because of the way the +** malloc() happens, the calling function must not try to call +** malloc() directly. Only sqlite_free_table() is able to release +** the memory properly and safely. +** +** The return value of this routine is the same as from sqlite_exec(). +*/ +int sqlite_get_table( + sqlite*, /* An open database */ + const char *sql, /* SQL to be executed */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg /* Error msg written here */ +); + +/* +** Call this routine to free the memory that sqlite_get_table() allocated. +*/ +void sqlite_free_table(char **result); + +/* +** The following routines are wrappers around sqlite_exec() and +** sqlite_get_table(). The only difference between the routines that +** follow and the originals is that the second argument to the +** routines that follow is really a printf()-style format +** string describing the SQL to be executed. Arguments to the format +** string appear at the end of the argument list. +** +** All of the usual printf formatting options apply. In addition, there +** is a "%q" option. %q works like %s in that it substitutes a null-terminated +** string from the argument list. But %q also doubles every '\'' character. +** %q is designed for use inside a string literal. By doubling each '\'' +** character it escapes that character and allows it to be inserted into +** the string. +** +** For example, so some string variable contains text as follows: +** +** char *zText = "It's a happy day!"; +** +** We can use this text in an SQL statement as follows: +** +** sqlite_exec_printf(db, "INSERT INTO table VALUES('%q')", +** callback1, 0, 0, zText); +** +** Because the %q format string is used, the '\'' character in zText +** is escaped and the SQL generated is as follows: +** +** INSERT INTO table1 VALUES('It''s a happy day!') +** +** This is correct. Had we used %s instead of %q, the generated SQL +** would have looked like this: +** +** INSERT INTO table1 VALUES('It's a happy day!'); +** +** This second example is an SQL syntax error. As a general rule you +** should always use %q instead of %s when inserting text into a string +** literal. +*/ +int sqlite_exec_printf( + sqlite*, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + sqlite_callback, /* Callback function */ + void *, /* 1st argument to callback function */ + char **errmsg, /* Error msg written here */ + ... /* Arguments to the format string. */ +); +int sqlite_exec_vprintf( + sqlite*, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + sqlite_callback, /* Callback function */ + void *, /* 1st argument to callback function */ + char **errmsg, /* Error msg written here */ + va_list ap /* Arguments to the format string. */ +); +int sqlite_get_table_printf( + sqlite*, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg, /* Error msg written here */ + ... /* Arguments to the format string */ +); +int sqlite_get_table_vprintf( + sqlite*, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg, /* Error msg written here */ + va_list ap /* Arguments to the format string */ +); +char *sqlite_mprintf(const char*,...); + +/* +** Windows systems should call this routine to free memory that +** is returned in the in the errmsg parameter of sqlite_open() when +** SQLite is a DLL. For some reason, it does not work to call free() +** directly. +*/ +void sqlite_freemem(void *p); + +/* +** Windows systems need functions to call to return the sqlite_version +** and sqlite_encoding strings. +*/ +const char *sqlite_libversion(void); +const char *sqlite_libencoding(void); + +/* +** A pointer to the following structure is used to communicate with +** the implementations of user-defined functions. +*/ +typedef struct sqlite_func sqlite_func; + +/* +** Use the following routines to create new user-defined functions. See +** the documentation for details. +*/ +int sqlite_create_function( + sqlite*, /* Database where the new function is registered */ + const char *zName, /* Name of the new function */ + int nArg, /* Number of arguments. -1 means any number */ + void (*xFunc)(sqlite_func*,int,const char**), /* C code to implement */ + void *pUserData /* Available via the sqlite_user_data() call */ +); +int sqlite_create_aggregate( + sqlite*, /* Database where the new function is registered */ + const char *zName, /* Name of the function */ + int nArg, /* Number of arguments */ + void (*xStep)(sqlite_func*,int,const char**), /* Called for each row */ + void (*xFinalize)(sqlite_func*), /* Called once to get final result */ + void *pUserData /* Available via the sqlite_user_data() call */ +); + +/* +** Use the following routine to define the datatype returned by a +** user-defined function. The second argument can be one of the +** constants SQLITE_NUMERIC, SQLITE_TEXT, or SQLITE_ARGS or it +** can be an integer greater than or equal to zero. The datatype +** will be numeric or text (the only two types supported) if the +** argument is SQLITE_NUMERIC or SQLITE_TEXT. If the argument is +** SQLITE_ARGS, then the datatype is numeric if any argument to the +** function is numeric and is text otherwise. If the second argument +** is an integer, then the datatype of the result is the same as the +** parameter to the function that corresponds to that integer. +*/ +int sqlite_function_type( + sqlite *db, /* The database there the function is registered */ + const char *zName, /* Name of the function */ + int datatype /* The datatype for this function */ +); +#define SQLITE_NUMERIC (-1) +#define SQLITE_TEXT (-2) +#define SQLITE_ARGS (-3) + +/* +** The user function implementations call one of the following four routines +** in order to return their results. The first parameter to each of these +** routines is a copy of the first argument to xFunc() or xFinialize(). +** The second parameter to these routines is the result to be returned. +** A NULL can be passed as the second parameter to sqlite_set_result_string() +** in order to return a NULL result. +** +** The 3rd argument to _string and _error is the number of characters to +** take from the string. If this argument is negative, then all characters +** up to and including the first '\000' are used. +** +** The sqlite_set_result_string() function allocates a buffer to hold the +** result and returns a pointer to this buffer. The calling routine +** (that is, the implementation of a user function) can alter the content +** of this buffer if desired. +*/ +char *sqlite_set_result_string(sqlite_func*,const char*,int); +void sqlite_set_result_int(sqlite_func*,int); +void sqlite_set_result_double(sqlite_func*,double); +void sqlite_set_result_error(sqlite_func*,const char*,int); + +/* +** The pUserData parameter to the sqlite_create_function() and +** sqlite_create_aggregate() routines used to register user functions +** is available to the implementation of the function using this +** call. +*/ +void *sqlite_user_data(sqlite_func*); + +/* +** Aggregate functions use the following routine to allocate +** a structure for storing their state. The first time this routine +** is called for a particular aggregate, a new structure of size nBytes +** is allocated, zeroed, and returned. On subsequent calls (for the +** same aggregate instance) the same buffer is returned. The implementation +** of the aggregate can use the returned buffer to accumulate data. +** +** The buffer allocated is freed automatically be SQLite. +*/ +void *sqlite_aggregate_context(sqlite_func*, int nBytes); + +/* +** The next routine returns the number of calls to xStep for a particular +** aggregate function instance. The current call to xStep counts so this +** routine always returns at least 1. +*/ +int sqlite_aggregate_count(sqlite_func*); + +/* +** This routine registers a callback with the SQLite library. The +** callback is invoked (at compile-time, not at run-time) for each +** attempt to access a column of a table in the database. The callback +** returns SQLITE_OK if access is allowed, SQLITE_DENY if the entire +** SQL statement should be aborted with an error and SQLITE_IGNORE +** if the column should be treated as a NULL value. +*/ +int sqlite_set_authorizer( + sqlite*, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pUserData +); + +/* +** The second parameter to the access authorization function above will +** be one of the values below. These values signify what kind of operation +** is to be authorized. The 3rd and 4th parameters to the authorization +** function will be parameters or NULL depending on which of the following +** codes is used as the second parameter. The 5th parameter is the name +** of the database ("main", "temp", etc.) if applicable. The 6th parameter +** is the name of the inner-most trigger or view that is responsible for +** the access attempt or NULL if this access attempt is directly from +** input SQL code. +** +** Arg-3 Arg-4 +*/ +#define SQLITE_COPY 0 /* Table Name File Name */ +#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */ +#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */ +#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */ +#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */ +#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */ +#define SQLITE_CREATE_VIEW 8 /* View Name NULL */ +#define SQLITE_DELETE 9 /* Table Name NULL */ +#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */ +#define SQLITE_DROP_TABLE 11 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */ +#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */ +#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */ +#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */ +#define SQLITE_DROP_VIEW 17 /* View Name NULL */ +#define SQLITE_INSERT 18 /* Table Name NULL */ +#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */ +#define SQLITE_READ 20 /* Table Name Column Name */ +#define SQLITE_SELECT 21 /* NULL NULL */ +#define SQLITE_TRANSACTION 22 /* NULL NULL */ +#define SQLITE_UPDATE 23 /* Table Name Column Name */ + +/* +** The return value of the authorization function should be one of the +** following constants: +*/ +/* #define SQLITE_OK 0 // Allow access (This is actually defined above) */ +#define SQLITE_DENY 1 /* Abort the SQL statement with an error */ +#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ + +/* +** Register a function that is called at every invocation of sqlite_exec() +** or sqlite_compile(). This function can be used (for example) to generate +** a log file of all SQL executed against a database. +*/ +void *sqlite_trace(sqlite*, void(*xTrace)(void*,const char*), void*); + +/*** The Callback-Free API +** +** The following routines implement a new way to access SQLite that does not +** involve the use of callbacks. +** +** An sqlite_vm is an opaque object that represents a single SQL statement +** that is ready to be executed. +*/ +typedef struct sqlite_vm sqlite_vm; + +/* +** To execute an SQLite query without the use of callbacks, you first have +** to compile the SQL using this routine. The 1st parameter "db" is a pointer +** to an sqlite object obtained from sqlite_open(). The 2nd parameter +** "zSql" is the text of the SQL to be compiled. The remaining parameters +** are all outputs. +** +** *pzTail is made to point to the first character past the end of the first +** SQL statement in zSql. This routine only compiles the first statement +** in zSql, so *pzTail is left pointing to what remains uncompiled. +** +** *ppVm is left pointing to a "virtual machine" that can be used to execute +** the compiled statement. Or if there is an error, *ppVm may be set to NULL. +** If the input text contained no SQL (if the input is and empty string or +** a comment) then *ppVm is set to NULL. +** +** If any errors are detected during compilation, an error message is written +** into space obtained from malloc() and *pzErrMsg is made to point to that +** error message. The calling routine is responsible for freeing the text +** of this message when it has finished with it. Use sqlite_freemem() to +** free the message. pzErrMsg may be NULL in which case no error message +** will be generated. +** +** On success, SQLITE_OK is returned. Otherwise and error code is returned. +*/ +int sqlite_compile( + sqlite *db, /* The open database */ + const char *zSql, /* SQL statement to be compiled */ + const char **pzTail, /* OUT: uncompiled tail of zSql */ + sqlite_vm **ppVm, /* OUT: the virtual machine to execute zSql */ + char **pzErrmsg /* OUT: Error message. */ +); + +/* +** After an SQL statement has been compiled, it is handed to this routine +** to be executed. This routine executes the statement as far as it can +** go then returns. The return value will be one of SQLITE_DONE, +** SQLITE_ERROR, SQLITE_BUSY, SQLITE_ROW, or SQLITE_MISUSE. +** +** SQLITE_DONE means that the execute of the SQL statement is complete +** an no errors have occurred. sqlite_step() should not be called again +** for the same virtual machine. *pN is set to the number of columns in +** the result set and *pazColName is set to an array of strings that +** describe the column names and datatypes. The name of the i-th column +** is (*pazColName)[i] and the datatype of the i-th column is +** (*pazColName)[i+*pN]. *pazValue is set to NULL. +** +** SQLITE_ERROR means that the virtual machine encountered a run-time +** error. sqlite_step() should not be called again for the same +** virtual machine. *pN is set to 0 and *pazColName and *pazValue are set +** to NULL. Use sqlite_finalize() to obtain the specific error code +** and the error message text for the error. +** +** SQLITE_BUSY means that an attempt to open the database failed because +** another thread or process is holding a lock. The calling routine +** can try again to open the database by calling sqlite_step() again. +** The return code will only be SQLITE_BUSY if no busy handler is registered +** using the sqlite_busy_handler() or sqlite_busy_timeout() routines. If +** a busy handler callback has been registered but returns 0, then this +** routine will return SQLITE_ERROR and sqltie_finalize() will return +** SQLITE_BUSY when it is called. +** +** SQLITE_ROW means that a single row of the result is now available. +** The data is contained in *pazValue. The value of the i-th column is +** (*azValue)[i]. *pN and *pazColName are set as described in SQLITE_DONE. +** Invoke sqlite_step() again to advance to the next row. +** +** SQLITE_MISUSE is returned if sqlite_step() is called incorrectly. +** For example, if you call sqlite_step() after the virtual machine +** has halted (after a prior call to sqlite_step() has returned SQLITE_DONE) +** or if you call sqlite_step() with an incorrectly initialized virtual +** machine or a virtual machine that has been deleted or that is associated +** with an sqlite structure that has been closed. +*/ +int sqlite_step( + sqlite_vm *pVm, /* The virtual machine to execute */ + int *pN, /* OUT: Number of columns in result */ + const char ***pazValue, /* OUT: Column data */ + const char ***pazColName /* OUT: Column names and datatypes */ +); + +/* +** This routine is called to delete a virtual machine after it has finished +** executing. The return value is the result code. SQLITE_OK is returned +** if the statement executed successfully and some other value is returned if +** there was any kind of error. If an error occurred and pzErrMsg is not +** NULL, then an error message is written into memory obtained from malloc() +** and *pzErrMsg is made to point to that error message. The calling routine +** should use sqlite_freemem() to delete this message when it has finished +** with it. +** +** This routine can be called at any point during the execution of the +** virtual machine. If the virtual machine has not completed execution +** when this routine is called, that is like encountering an error or +** an interrupt. (See sqlite_interrupt().) Incomplete updates may be +** rolled back and transactions cancelled, depending on the circumstances, +** and the result code returned will be SQLITE_ABORT. +*/ +int sqlite_finalize(sqlite_vm*, char **pzErrMsg); + +#ifdef __cplusplus +} /* End of the 'extern "C"' block */ +#endif + +#endif /* _SQLITE_H_ */ diff --git a/kexi/kexidb/drivers/sqlite/kexidb_sqlite3driver.desktop b/kexi/kexidb/drivers/sqlite/kexidb_sqlite3driver.desktop new file mode 100644 index 00000000..92e59931 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/kexidb_sqlite3driver.desktop @@ -0,0 +1,56 @@ +[Desktop Entry] +Name=SQLite3 +Name[sv]=Sqlite 3 +Comment=SQLite is default Kexi embedded SQL engine +Comment[bg]=SQLite е СУБД по подразбиране в Kexi , с вградено SQL ядро +Comment[ca]=SQLite és el motor SQL encastat i per defecte de Kexi +Comment[cy]=Y peiriant SQL mewnadeiledig rhagosod Kexi yw SQLite +Comment[da]=SQLite er en standard Kexi-indlejret SQL-motor +Comment[de]=SQLite ist der standardmäßig in Kexi eingebettete SQL-Treiber +Comment[el]=Η SQLite είναι η προκαθορισμένη ενσωματωμένη μηχανή SQL του Kexi +Comment[es]=SQLite es el motor SQL incrustado en Kexi de forma predefinida +Comment[et]=SQLite on Kexi vaikimisi kasutatav SQL mootor +Comment[eu]=SQLite Kexi-ren kapsultatutako SQL motore lehenetsia da +Comment[fa]=SQLite، پیشفرض موتور SQL نهفتۀ Kexi است +Comment[fi]=SQLite on Kexin käyttämä sisäänrakennetti SQL-moottori. +Comment[fr]=SQLite est le moteur SQL par défaut intégré à Kexi +Comment[fy]=SQLite is de standert SQL-databank foar Kexi +Comment[gl]=SQLite é o motor embebido de SQL de Kexi +Comment[he]=SQLite הוא מנוע ה־SQL המוטבע של Kexi המשמש כברירת מחדל +Comment[hi]=केएक्साई एम्बेडेड एसक्यूएल इंजिन के लिए एसक्यूएललाइट डिफ़ॉल्ट है +Comment[hr]=SQLite je zadani Kexi ugrađeni pogon SQL pogona +Comment[hu]=Az SQLite a Kexi alapértelmezett, beépített SQL-motorja +Comment[is]=SQLite er sjálfgefna Kexi SQL vélin +Comment[it]=SQLite è il motore predefinito integrato in Kexi +Comment[ja]=SQLite は Kexi の標準埋め込み SQL エンジンです。 +Comment[km]=SQLite គឺជាម៉ាស៊ីន SQL ដែលបានបង្កប់ក្នុង Kexi តាមលំនាំដើម +Comment[lv]=SQLite ir Kexi noklusējuma SQL dzinējs +Comment[ms]=SQLite adalah KeXi piawai yang dipasang dalam enjin SQL +Comment[nb]=SQLite er den innebygde SQL-motoren i Kexi +Comment[nds]=SQLite is de standardinbett SQL-Driever för Kexi +Comment[ne]=SQLite पूर्वनिर्धारित केक्सी सम्मिलित SQL इन्जिन हो +Comment[nl]=SQLite is de standaard SQL-database voor Kexi +Comment[nn]=SQLite er den innebygde SQL-motoren i Kexi +Comment[pl]=SQLite jest domyślnym wbudowanym silnikiem SQL dla Kexi +Comment[pt]=SQLite é o motor embebido de SQL do Kexi +Comment[pt_BR]=SQLite é o mecanismo SQL embutido padrão do Kexi +Comment[ru]=SQLite -- движок встроенного SQL по умолчанию в Kexi +Comment[se]=SQLite lea Kexi:a sisahuksejuvvon SQL-mohtor +Comment[sk]=SQLite je štandardný Kexi embedded SQL systém +Comment[sl]=SQLite je privzet vključen pogon SQL za Kexi +Comment[sr]=SQLite је подразумевани Kexi-јев уграђен SQL мотор +Comment[sr@Latn]=SQLite je podrazumevani Kexi-jev ugrađen SQL motor +Comment[sv]=Sqlite är inbyggd SQL-standarddatabas i Kexi +Comment[tg]=SQLite -- движоки SQL, ки дар дар дохили Kexi бо пешфарз сохта шудааст +Comment[tr]=SQlite Kexi'ye gömülü varsayılan SQL motorudur +Comment[uk]=SQLite - це типовий рушій SQL вбудований в Kexi +Comment[zh_CN]=SQLite 是嵌入 Kexi 的默认 SQL 引擎 +Comment[zh_TW]=SQLite 是 Kexi 預設內建的 SQL 引擎 +X-KDE-Library=kexidb_sqlite3driver +ServiceTypes=Kexi/DBDriver +Type=Service +InitialPreference=8 +X-Kexi-DriverName=SQLite3 +X-Kexi-DriverType=File +X-Kexi-FileDBDriverMimeList=application/x-sqlite3,application/x-kexiproject-sqlite3,application/x-hk_classes-sqlite3 +X-Kexi-KexiDBVersion=1.8 diff --git a/kexi/kexidb/drivers/sqlite/sqlite.pro b/kexi/kexidb/drivers/sqlite/sqlite.pro new file mode 100644 index 00000000..7fde2926 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlite.pro @@ -0,0 +1,10 @@ +include( sqlite_common.pro ) + +win32:LIBS += $$KDELIBDESTDIR/kexisql3$$KDELIBDEBUGLIB + +INCLUDEPATH += $(KEXI)/3rdparty/kexisql3/src + +TARGET = kexidb_sqlite3driver$$KDELIBDEBUG + +SOURCES += sqlitekeywords.cpp \ +sqlitevacuum.cpp diff --git a/kexi/kexidb/drivers/sqlite/sqlite_common.pro b/kexi/kexidb/drivers/sqlite/sqlite_common.pro new file mode 100644 index 00000000..81fb85b3 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlite_common.pro @@ -0,0 +1,16 @@ +include( ../common.pro ) + +DEFINES += MAKE_KEXIDB_SQLITE_DRIVER_LIB + +system( bash kmoc ) + +SOURCES = \ +sqliteconnection.cpp \ +sqlitedriver.cpp \ +sqliteadmin.cpp \ +sqlitecursor.cpp \ +sqlitepreparedstatement.cpp \ +sqlitealter.cpp + +HEADERS = + diff --git a/kexi/kexidb/drivers/sqlite/sqliteadmin.cpp b/kexi/kexidb/drivers/sqlite/sqliteadmin.cpp new file mode 100644 index 00000000..8bd8085a --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqliteadmin.cpp @@ -0,0 +1,64 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <qdir.h> + +#include "sqliteadmin.h" +#include <kexidb/drivermanager.h> +#include <kexidb/driver_p.h> + +#ifndef SQLITE2 +# include "sqlitevacuum.h" +#endif + +SQLiteAdminTools::SQLiteAdminTools() + : KexiDB::AdminTools() +{ +} + +SQLiteAdminTools::~SQLiteAdminTools() +{ +} + +bool SQLiteAdminTools::vacuum(const KexiDB::ConnectionData& data, const QString& databaseName) +{ + clearError(); +#ifdef SQLITE2 + Q_UNUSED(data); + Q_UNUSED(databaseName); + return false; +#else + KexiDB::DriverManager manager; + KexiDB::Driver *drv = manager.driver(data.driverName); + QString title( i18n("Could not compact database \"%1\".").arg(QDir::convertSeparators(databaseName)) ); + if (!drv) { + setError(&manager, title); + return false; + } + SQLiteVacuum vacuum(data.dbPath()+QDir::separator()+databaseName); + tristate result = vacuum.run(); + if (!result) { + setError(title); + return false; + } + else //success or cancelled + return true; +#endif +} + diff --git a/kexi/kexidb/drivers/sqlite/sqliteadmin.h b/kexi/kexidb/drivers/sqlite/sqliteadmin.h new file mode 100644 index 00000000..d9f8a6b6 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqliteadmin.h @@ -0,0 +1,36 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_SQLITEADMIN_H +#define KEXIDB_SQLITEADMIN_H + +#include <kexidb/admin.h> + +//! @short An interface containing a set of tools for SQLite database administration. +class SQLiteAdminTools : public KexiDB::AdminTools +{ + public: + SQLiteAdminTools(); + virtual ~SQLiteAdminTools(); + + /*! Performs vacuum (compacting) for connection \a conn. */ + virtual bool vacuum(const KexiDB::ConnectionData& data, const QString& databaseName); +}; + +#endif diff --git a/kexi/kexidb/drivers/sqlite/sqlitealter.cpp b/kexi/kexidb/drivers/sqlite/sqlitealter.cpp new file mode 100644 index 00000000..cc72e48a --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlitealter.cpp @@ -0,0 +1,114 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +// ** bits of SQLiteConnection related to table altering ** + +#include "sqliteconnection.h" +#include <kexidb/utils.h> + +#include <kstaticdeleter.h> + +#include <qmap.h> + +using namespace KexiDB; + +enum SQLiteTypeAffinity { //as defined here: 2.1 Determination Of Column Affinity (http://sqlite.org/datatype3.html) + NoAffinity = 0, IntAffinity = 1, TextAffinity = 2, BLOBAffinity = 3 +}; + +//! helper for affinityForType() +static KStaticDeleter< QMap<int,int> > KexiDB_SQLite_affinityForType_deleter; +QMap<int,int> *KexiDB_SQLite_affinityForType = 0; + +//! \return SQLite type affinity for \a type +//! See doc/dev/alter_table_type_conversions.ods, page 2 for more info +static SQLiteTypeAffinity affinityForType(Field::Type type) +{ + if (!KexiDB_SQLite_affinityForType) { + KexiDB_SQLite_affinityForType_deleter.setObject( KexiDB_SQLite_affinityForType, new QMap<int,int>() ); + KexiDB_SQLite_affinityForType->insert(Field::Byte, IntAffinity); + KexiDB_SQLite_affinityForType->insert(Field::ShortInteger, IntAffinity); + KexiDB_SQLite_affinityForType->insert(Field::Integer, IntAffinity); + KexiDB_SQLite_affinityForType->insert(Field::BigInteger, IntAffinity); + KexiDB_SQLite_affinityForType->insert(Field::Boolean, IntAffinity); + KexiDB_SQLite_affinityForType->insert(Field::Date, TextAffinity); + KexiDB_SQLite_affinityForType->insert(Field::DateTime, TextAffinity); + KexiDB_SQLite_affinityForType->insert(Field::Time, TextAffinity); + KexiDB_SQLite_affinityForType->insert(Field::Float, IntAffinity); + KexiDB_SQLite_affinityForType->insert(Field::Double, IntAffinity); + KexiDB_SQLite_affinityForType->insert(Field::Text, TextAffinity); + KexiDB_SQLite_affinityForType->insert(Field::LongText, TextAffinity); + KexiDB_SQLite_affinityForType->insert(Field::BLOB, BLOBAffinity); + } + return static_cast<SQLiteTypeAffinity>((*KexiDB_SQLite_affinityForType)[(int)type]); +} + +tristate SQLiteConnection::drv_changeFieldProperty(TableSchema &table, Field& field, + const QString& propertyName, const QVariant& value) +{ +/* if (propertyName=="name") { + + }*/ + if (propertyName=="type") { + bool ok; + Field::Type type = KexiDB::intToFieldType( value.toUInt(&ok) ); + if (!ok || Field::InvalidType == type) { + //! @todo msg + return false; + } + return changeFieldType(table, field, type); + } + // not found + return cancelled; +} + +/*! + From http://sqlite.org/datatype3.html : + Version 3 enhances provides the ability to store integer and real numbers in a more compact + format and the capability to store BLOB data. + + Each value stored in an SQLite database (or manipulated by the database engine) has one + of the following storage classes: + * NULL. The value is a NULL value. + * INTEGER. The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending + on the magnitude of the value. + * REAL. The value is a floating point value, stored as an 8-byte IEEE floating point number. + * TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16-LE). + * BLOB. The value is a blob of data, stored exactly as it was input. + + Column Affinity + In SQLite version 3, the type of a value is associated with the value itself, + not with the column or variable in which the value is stored. +.The type affinity of a column is the recommended type for data stored in that column. + + See alter_table_type_conversions.ods for details. +*/ +tristate SQLiteConnection::changeFieldType(TableSchema &table, Field& field, + Field::Type type) +{ + Q_UNUSED(table); + const Field::Type oldType = field.type(); + const SQLiteTypeAffinity oldAffinity = affinityForType(oldType); + const SQLiteTypeAffinity newAffinity = affinityForType(type); + if (oldAffinity!=newAffinity) { + //type affinity will be changed + } + + return cancelled; +} diff --git a/kexi/kexidb/drivers/sqlite/sqliteconnection.cpp b/kexi/kexidb/drivers/sqlite/sqliteconnection.cpp new file mode 100644 index 00000000..6de41c59 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqliteconnection.cpp @@ -0,0 +1,414 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "sqliteconnection.h" +#include "sqliteconnection_p.h" +#include "sqlitecursor.h" +#include "sqlitepreparedstatement.h" + +#include "sqlite.h" + +#ifndef SQLITE2 +# include "kexisql.h" //for isReadOnly() +#endif + +#include <kexidb/driver.h> +#include <kexidb/cursor.h> +#include <kexidb/error.h> +#include <kexiutils/utils.h> + +#include <qfile.h> +#include <qdir.h> +#include <qregexp.h> + +#include <kgenericfactory.h> +#include <kdebug.h> + +//remove debug +#undef KexiDBDrvDbg +#define KexiDBDrvDbg if (0) kdDebug() + +using namespace KexiDB; + +SQLiteConnectionInternal::SQLiteConnectionInternal(Connection *connection) + : ConnectionInternal(connection) + , data(0) + , data_owned(true) + , errmsg_p(0) + , res(SQLITE_OK) + , temp_st(0x10000) +#ifdef SQLITE3 + , result_name(0) +#endif +{ +} + +SQLiteConnectionInternal::~SQLiteConnectionInternal() +{ + if (data_owned && data) { + free( data ); + data = 0; + } +//sqlite_freemem does this if (errmsg) { +// free( errmsg ); +// errmsg = 0; +// } +} + +void SQLiteConnectionInternal::storeResult() +{ + if (errmsg_p) { + errmsg = errmsg_p; + sqlite_free(errmsg_p); + errmsg_p = 0; + } +#ifdef SQLITE3 + errmsg = (data && res!=SQLITE_OK) ? sqlite3_errmsg(data) : 0; +#endif +} + +/*! Used by driver */ +SQLiteConnection::SQLiteConnection( Driver *driver, ConnectionData &conn_data ) + : Connection( driver, conn_data ) + ,d(new SQLiteConnectionInternal(this)) +{ +} + +SQLiteConnection::~SQLiteConnection() +{ + KexiDBDrvDbg << "SQLiteConnection::~SQLiteConnection()" << endl; + //disconnect if was connected +// disconnect(); + destroy(); + delete d; + KexiDBDrvDbg << "SQLiteConnection::~SQLiteConnection() ok" << endl; +} + +bool SQLiteConnection::drv_connect(KexiDB::ServerVersionInfo& version) +{ + KexiDBDrvDbg << "SQLiteConnection::connect()" << endl; + version.string = QString(SQLITE_VERSION); //defined in sqlite3.h + QRegExp re("(\\d+)\\.(\\d+)\\.(\\d+)"); + if (re.exactMatch(version.string)) { + version.major = re.cap(1).toUInt(); + version.minor = re.cap(2).toUInt(); + version.release = re.cap(3).toUInt(); + } + return true; +} + +bool SQLiteConnection::drv_disconnect() +{ + KexiDBDrvDbg << "SQLiteConnection::disconnect()" << endl; + return true; +} + +bool SQLiteConnection::drv_getDatabasesList( QStringList &list ) +{ + //this is one-db-per-file database + list.append( data()->fileName() ); //more consistent than dbFileName() ? + return true; +} + +bool SQLiteConnection::drv_containsTable( const QString &tableName ) +{ + bool success; + return resultExists(QString("select name from sqlite_master where type='table' and name LIKE %1") + .arg(driver()->escapeString(tableName)), success) && success; +} + +bool SQLiteConnection::drv_getTablesList( QStringList &list ) +{ + KexiDB::Cursor *cursor; + m_sql = "select lower(name) from sqlite_master where type='table'"; + if (!(cursor = executeQuery( m_sql ))) { + KexiDBWarn << "Connection::drv_getTablesList(): !executeQuery()" << endl; + return false; + } + list.clear(); + cursor->moveFirst(); + while (!cursor->eof() && !cursor->error()) { + list += cursor->value(0).toString(); + cursor->moveNext(); + } + if (cursor->error()) { + deleteCursor(cursor); + return false; + } + return deleteCursor(cursor); +} + +bool SQLiteConnection::drv_createDatabase( const QString &dbName ) +{ + // SQLite creates a new db is it does not exist + return drv_useDatabase(dbName); +#if 0 + d->data = sqlite_open( QFile::encodeName( data()->fileName() ), 0/*mode: unused*/, + &d->errmsg_p ); + d->storeResult(); + return d->data != 0; +#endif +} + +bool SQLiteConnection::drv_useDatabase( const QString &dbName, bool *cancelled, + MessageHandler* msgHandler ) +{ + Q_UNUSED(dbName); +// KexiDBDrvDbg << "drv_useDatabase(): " << data()->fileName() << endl; +#ifdef SQLITE2 + Q_UNUSED(cancelled); + Q_UNUSED(msgHandler); + d->data = sqlite_open( QFile::encodeName( data()->fileName() ), 0/*mode: unused*/, + &d->errmsg_p ); + d->storeResult(); + return d->data != 0; +#else //SQLITE3 + //TODO: perhaps allow to use sqlite3_open16() as well for SQLite ~ 3.3 ? +//! @todo add option (command line or in kexirc?) + int exclusiveFlag = Connection::isReadOnly() ? SQLITE_OPEN_READONLY : SQLITE_OPEN_WRITE_LOCKED; // <-- shared read + (if !r/o): exclusive write +//! @todo add option + int allowReadonly = 1; + const bool wasReadOnly = Connection::isReadOnly(); + + d->res = sqlite3_open( + //QFile::encodeName( data()->fileName() ), + data()->fileName().utf8(), /* unicode expected since SQLite 3.1 */ + &d->data, + exclusiveFlag, + allowReadonly /* If 1 and locking fails, try opening in read-only mode */ + ); + d->storeResult(); + + if (d->res == SQLITE_OK && cancelled && !wasReadOnly && allowReadonly && isReadOnly()) { + //opened as read only, ask + if (KMessageBox::Continue != + askQuestion( + i18n("Do you want to open file \"%1\" as read-only?") + .arg(QDir::convertSeparators(data()->fileName())) + + "\n\n" + + i18n("The file is probably already open on this or another computer.") + " " + + i18n("Could not gain exclusive access for writing the file."), + KMessageBox::WarningContinueCancel, KMessageBox::Continue, + KGuiItem(i18n("Open As Read-Only"), "fileopen"), KStdGuiItem::cancel(), + "askBeforeOpeningFileReadOnly", KMessageBox::Notify, msgHandler )) + { + clearError(); + if (!drv_closeDatabase()) + return false; + *cancelled = true; + return false; + } + } + + if (d->res == SQLITE_CANTOPEN_WITH_LOCKED_READWRITE) { + setError(ERR_ACCESS_RIGHTS, + i18n("The file is probably already open on this or another computer.")+"\n\n" + + i18n("Could not gain exclusive access for reading and writing the file.") + " " + + i18n("Check the file's permissions and whether it is already opened and locked by another application.")); + } + else if (d->res == SQLITE_CANTOPEN_WITH_LOCKED_WRITE) { + setError(ERR_ACCESS_RIGHTS, + i18n("The file is probably already open on this or another computer.")+"\n\n" + + i18n("Could not gain exclusive access for writing the file.") + " " + + i18n("Check the file's permissions and whether it is already opened and locked by another application.")); + } + return d->res == SQLITE_OK; +#endif +} + +bool SQLiteConnection::drv_closeDatabase() +{ + if (!d->data) + return false; + +#ifdef SQLITE2 + sqlite_close(d->data); + d->data = 0; + return true; +#else + const int res = sqlite_close(d->data); + if (SQLITE_OK == res) { + d->data = 0; + return true; + } + if (SQLITE_BUSY==res) { +#if 0 //this is ANNOYING, needs fixing (by closing cursors or waiting) + setError(ERR_CLOSE_FAILED, i18n("Could not close busy database.")); +#else + return true; +#endif + } + return false; +#endif +} + +bool SQLiteConnection::drv_dropDatabase( const QString &dbName ) +{ + Q_UNUSED(dbName); // Each database is one single SQLite file. + const QString filename = data()->fileName(); + if (QFile(filename).exists() && !QDir().remove(filename)) { + setError(ERR_ACCESS_RIGHTS, i18n("Could not remove file \"%1\".") + .arg(QDir::convertSeparators(filename)) + " " + + i18n("Check the file's permissions and whether it is already opened and locked by another application.")); + return false; + } + return true; +} + +//CursorData* SQLiteConnection::drv_createCursor( const QString& statement ) +Cursor* SQLiteConnection::prepareQuery( const QString& statement, uint cursor_options ) +{ + return new SQLiteCursor( this, statement, cursor_options ); +} + +Cursor* SQLiteConnection::prepareQuery( QuerySchema& query, uint cursor_options ) +{ + return new SQLiteCursor( this, query, cursor_options ); +} + +bool SQLiteConnection::drv_executeSQL( const QString& statement ) +{ +// KexiDBDrvDbg << "SQLiteConnection::drv_executeSQL(" << statement << ")" <<endl; +// QCString st(statement.length()*2); +// st = escapeString( statement.local8Bit() ); //? +#ifdef SQLITE_UTF8 + d->temp_st = statement.utf8(); +#else + d->temp_st = statement.local8Bit(); //latin1 only +#endif + +#ifdef KEXI_DEBUG_GUI + KexiUtils::addKexiDBDebug(QString("ExecuteSQL (SQLite): ")+statement); +#endif + + d->res = sqlite_exec( + d->data, + (const char*)d->temp_st, + 0/*callback*/, + 0, + &d->errmsg_p ); + d->storeResult(); +#ifdef KEXI_DEBUG_GUI + KexiUtils::addKexiDBDebug(d->res==SQLITE_OK ? " Success" : " Failure"); +#endif + return d->res==SQLITE_OK; +} + +Q_ULLONG SQLiteConnection::drv_lastInsertRowID() +{ + return (Q_ULLONG)sqlite_last_insert_rowid(d->data); +} + +int SQLiteConnection::serverResult() +{ + return d->res==0 ? Connection::serverResult() : d->res; +} + +QString SQLiteConnection::serverResultName() +{ + QString r = +#ifdef SQLITE2 + QString::fromLatin1( sqlite_error_string(d->res) ); +#else //SQLITE3 + QString::null; //fromLatin1( d->result_name ); +#endif + return r.isEmpty() ? Connection::serverResultName() : r; +} + +void SQLiteConnection::drv_clearServerResult() +{ + if (!d) + return; + d->res = SQLITE_OK; +#ifdef SQLITE2 + d->errmsg_p = 0; +#else +// d->result_name = 0; +#endif +} + +QString SQLiteConnection::serverErrorMsg() +{ + return d->errmsg.isEmpty() ? Connection::serverErrorMsg() : d->errmsg; +} + +PreparedStatement::Ptr SQLiteConnection::prepareStatement(PreparedStatement::StatementType type, + FieldList& fields) +{ +//#ifndef SQLITE2 //TEMP IFDEF! + return new SQLitePreparedStatement(type, *d, fields); +//#endif +} + +bool SQLiteConnection::isReadOnly() const +{ +#ifdef SQLITE2 + return Connection::isReadOnly(); +#else + return d->data ? sqlite3_is_readonly(d->data) : false; +#endif +} + +#ifdef SQLITE2 +bool SQLiteConnection::drv_alterTableName(TableSchema& tableSchema, const QString& newName, bool replace) +{ + const QString oldTableName = tableSchema.name(); + const bool destTableExists = this->tableSchema( newName ) != 0; + + //1. drop the table + if (destTableExists) { + if (!replace) + return false; + if (!drv_dropTable( newName )) + return false; + } + + //2. create a copy of the table +//TODO: move this code to drv_copyTable() + tableSchema.setName(newName); + +//helper: +#define drv_alterTableName_ERR \ + tableSchema.setName(oldTableName) //restore old name + + if (!drv_createTable( tableSchema )) { + drv_alterTableName_ERR; + return false; + } + +//TODO indices, etc.??? + + // 3. copy all rows to the new table + if (!executeSQL(QString::fromLatin1("INSERT INTO %1 SELECT * FROM %2") + .arg(escapeIdentifier(tableSchema.name())).arg(escapeIdentifier(oldTableName)))) + { + drv_alterTableName_ERR; + return false; + } + + // 4. drop old table. + if (!drv_dropTable( oldTableName )) { + drv_alterTableName_ERR; + return false; + } + return true; +} +#endif + +#include "sqliteconnection.moc" diff --git a/kexi/kexidb/drivers/sqlite/sqliteconnection.h b/kexi/kexidb/drivers/sqlite/sqliteconnection.h new file mode 100644 index 00000000..ba0d3b5a --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqliteconnection.h @@ -0,0 +1,125 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_CONN_SQLITE_H +#define KEXIDB_CONN_SQLITE_H + +#include <qstringlist.h> + +#include <kexidb/connection.h> + +/*! + */ + +namespace KexiDB +{ + +class SQLiteConnectionInternal; +class Driver; + +//! sqlite-specific connection +class SQLiteConnection : public Connection +{ + Q_OBJECT + + public: + virtual ~SQLiteConnection(); + + virtual Cursor* prepareQuery( const QString& statement, uint cursor_options = 0 ); + virtual Cursor* prepareQuery( QuerySchema& query, uint cursor_options = 0 ); + +//#ifndef SQLITE2 //TEMP IFDEF! + virtual PreparedStatement::Ptr prepareStatement(PreparedStatement::StatementType type, + FieldList& fields); +//#endif + /*! Reimplemented to provide real read-only flag of the connection */ + virtual bool isReadOnly() const; + + protected: + /*! Used by driver */ + SQLiteConnection( Driver *driver, ConnectionData &conn_data ); + + virtual bool drv_connect(KexiDB::ServerVersionInfo& version); + virtual bool drv_disconnect(); + virtual bool drv_getDatabasesList( QStringList &list ); + +//TODO: move this somewhere to low level class (MIGRATION?) + virtual bool drv_getTablesList( QStringList &list ); + +//TODO: move this somewhere to low level class (MIGRATION?) + virtual bool drv_containsTable( const QString &tableName ); + + /*! Creates new database using connection. Note: Do not pass \a dbName + arg because for file-based engine (that has one database per connection) + it is defined during connection. */ + virtual bool drv_createDatabase( const QString &dbName = QString::null ); + + /*! Opens existing database using connection. Do not pass \a dbName + arg because for file-based engine (that has one database per connection) + it is defined during connection. If you pass it, + database file name will be changed. */ + virtual bool drv_useDatabase( const QString &dbName = QString::null, bool *cancelled = 0, + MessageHandler* msgHandler = 0 ); + + virtual bool drv_closeDatabase(); + + /*! Drops database from the server using connection. + After drop, database shouldn't be accessible + anymore, so database file is just removed. See note from drv_useDatabase(). */ + virtual bool drv_dropDatabase( const QString &dbName = QString::null ); + + //virtual bool drv_createTable( const KexiDB::Table& table ); + + virtual bool drv_executeSQL( const QString& statement ); +// virtual bool drv_executeQuery( const QString& statement ); + + virtual Q_ULLONG drv_lastInsertRowID(); + + virtual int serverResult(); + virtual QString serverResultName(); + virtual QString serverErrorMsg(); + virtual void drv_clearServerResult(); + virtual tristate drv_changeFieldProperty(TableSchema &table, Field& field, + const QString& propertyName, const QVariant& value); + +#ifdef SQLITE2 + /*! Alters table's described \a tableSchema name to \a newName. + This implementation is ineffective but works. + - creates a copy of the table + - copies all rows + - drops old table. + All the above should be performed within single transaction. + \return true on success. + More advanced server backends implement this using "ALTER TABLE .. RENAME TO". + */ + virtual bool drv_alterTableName(TableSchema& tableSchema, const QString& newName, bool replace = false); +#endif + + //! for drv_changeFieldProperty() + tristate changeFieldType(TableSchema &table, Field& field, Field::Type type); + + SQLiteConnectionInternal* d; + + friend class SQLiteDriver; + friend class SQLiteCursor; +}; + +} + +#endif diff --git a/kexi/kexidb/drivers/sqlite/sqliteconnection_p.h b/kexi/kexidb/drivers/sqlite/sqliteconnection_p.h new file mode 100644 index 00000000..f295573d --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqliteconnection_p.h @@ -0,0 +1,73 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_SQLITECONN_P_H +#define KEXIDB_SQLITECONN_P_H + +#include <kexidb/connection_p.h> + +#include "sqlite.h" + +//for compatibility +#ifdef _SQLITE3_H_ +# define SQLITE3 + typedef sqlite3 sqlite_struct; +# define sqlite_free sqlite3_free +# define sqlite_close sqlite3_close +# define sqlite_exec sqlite3_exec +# define sqlite_last_insert_rowid sqlite3_last_insert_rowid +# define sqlite_error_string sqlite3_last_insert_row_id +# define sqlite_libversion sqlite3_libversion +# define sqlite_libencoding sqlite3_libencoding +#else +# ifndef SQLITE2 +# define SQLITE2 +# endif + typedef struct sqlite sqlite_struct; +# define sqlite_free sqlite_freemem +#endif + +namespace KexiDB +{ + +/*! Internal SQLite connection data. Also used inside SQLiteCursor. */ +class SQLiteConnectionInternal : public ConnectionInternal +{ + public: + SQLiteConnectionInternal(Connection* connection); + virtual ~SQLiteConnectionInternal(); + + //! stores last result's message + virtual void storeResult(); + + sqlite_struct *data; + bool data_owned; //!< true if data pointer should be freed on destruction + QString errmsg; //<! server-specific message of last operation + char *errmsg_p; //<! temporary: server-specific message of last operation + int res; //<! result code of last operation on server + + QCString temp_st; +#ifdef SQLITE3 + const char *result_name; +#endif +}; + +} + +#endif diff --git a/kexi/kexidb/drivers/sqlite/sqlitecursor.cpp b/kexi/kexidb/drivers/sqlite/sqlitecursor.cpp new file mode 100644 index 00000000..4b18b437 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlitecursor.cpp @@ -0,0 +1,567 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "sqlitecursor.h" + +#include "sqliteconnection.h" +#include "sqliteconnection_p.h" + +#include <kexidb/error.h> +#include <kexidb/driver.h> +#include <kexiutils/utils.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <qptrvector.h> +#include <qdatetime.h> + +using namespace KexiDB; + +//! safer interpretations of boolean values for SQLite +static bool sqliteStringToBool(const QString& s) +{ + return s.lower()=="yes" || (s.lower()!="no" && s!="0"); +} + +//---------------------------------------------------- + +class KexiDB::SQLiteCursorData : public SQLiteConnectionInternal +{ + public: + SQLiteCursorData(Connection* conn) + : + SQLiteConnectionInternal(conn) +// : curr_cols(0) +// errmsg_p(0) +// , res(SQLITE_OK) + , curr_coldata(0) + , curr_colname(0) + , cols_pointers_mem_size(0) +// , rec_stored(false) +/* MOVED TO Cursor: + , cols_pointers_mem_size(0) + , records_in_buf(0) + , buffering_completed(false) + , at_buffer(false)*/ +//#ifdef SQLITE3 +// , rowDataReadyToFetch(false) +//#endif + { + data_owned = false; + } + +/*#ifdef SQLITE3 + void fetchRowDataIfNeeded() + { + if (!rowDataReadyToFetch) + return true; + rowDataReadyToFetch = false; + m_fieldCount = sqlite3_data_count(data); + for (int i=0; i<m_fieldCount; i++) { + + } + } +#endif*/ + + QCString st; + //for sqlite: +// sqlite_struct *data; //! taken from SQLiteConnection +#ifdef SQLITE2 + sqlite_vm *prepared_st_handle; //vm +#else //SQLITE3 + sqlite3_stmt *prepared_st_handle; +#endif + + char *utail; + +// QString errmsg; //<! server-specific message of last operation +// char *errmsg_p; //<! temporary: server-specific message of last operation +// int res; //<! result code of last operation on server + +// int curr_cols; + const char **curr_coldata; + const char **curr_colname; + + int next_cols; +// const char **next_coldata; +// const char **next_colname; +// bool rec_stored : 1; //! true, current record is stored in next_coldata + +/* MOVED TO Cursor: + uint cols_pointers_mem_size; //! size of record's array of pointers to values + int records_in_buf; //! number of records currently stored in the buffer + bool buffering_completed; //! true if we have already all records stored in the buffer + QPtrVector<const char*> records; //buffer data + bool at_buffer; //! true if we already point to the buffer with curr_coldata +*/ + +/* int prev_cols; + const char **prev_coldata; + const char **prev_colname;*/ + + uint cols_pointers_mem_size; //! size of record's array of pointers to values + QPtrVector<const char*> records;//! buffer data +//#ifdef SQLITE3 +// bool rowDataReadyToFetch : 1; +//#endif + +#ifdef SQLITE3 + inline QVariant getValue(Field *f, int i) + { + int type = sqlite3_column_type(prepared_st_handle, i); + if (type==SQLITE_NULL) { + return QVariant(); + } + else if (!f || type==SQLITE_TEXT) { +//TODO: support for UTF-16 +#define GET_sqlite3_column_text QString::fromUtf8( (const char*)sqlite3_column_text(prepared_st_handle, i) ) + if (!f || f->isTextType()) + return GET_sqlite3_column_text; + else { + switch (f->type()) { + case Field::Date: + return QDate::fromString( GET_sqlite3_column_text, Qt::ISODate ); + case Field::Time: + //QDateTime - a hack needed because QVariant(QTime) has broken isNull() + return KexiUtils::stringToHackedQTime(GET_sqlite3_column_text); + case Field::DateTime: { + QString tmp( GET_sqlite3_column_text ); + tmp[10] = 'T'; //for ISODate compatibility + return QDateTime::fromString( tmp, Qt::ISODate ); + } + case Field::Boolean: + return QVariant(sqliteStringToBool(GET_sqlite3_column_text), 1); + default: + return QVariant(); //TODO + } + } + } + else if (type==SQLITE_INTEGER) { + switch (f->type()) { + case Field::Byte: + case Field::ShortInteger: + case Field::Integer: + return QVariant( sqlite3_column_int(prepared_st_handle, i) ); + case Field::BigInteger: + return QVariant( (Q_LLONG)sqlite3_column_int64(prepared_st_handle, i) ); + case Field::Boolean: + return QVariant( sqlite3_column_int(prepared_st_handle, i)!=0, 1 ); + default:; + } + if (f->isFPNumericType()) //WEIRD, YEAH? + return QVariant( (double)sqlite3_column_int(prepared_st_handle, i) ); + else + return QVariant(); //TODO + } + else if (type==SQLITE_FLOAT) { + if (f && f->isFPNumericType()) + return QVariant( sqlite3_column_double(prepared_st_handle, i) ); + else if (!f || f->isIntegerType()) + return QVariant( (double)sqlite3_column_double(prepared_st_handle, i) ); + else + return QVariant(); //TODO + } + else if (type==SQLITE_BLOB) { + if (f && f->type()==Field::BLOB) { + QByteArray ba; +//! @todo efficient enough? + ba.duplicate((const char*)sqlite3_column_blob(prepared_st_handle, i), + sqlite3_column_bytes(prepared_st_handle, i)); + return ba; + } else + return QVariant(); //TODO + } + return QVariant(); + } +#endif //SQLITE3 +}; + +SQLiteCursor::SQLiteCursor(Connection* conn, const QString& statement, uint options) + : Cursor( conn, statement, options ) + , d( new SQLiteCursorData(conn) ) +{ + d->data = static_cast<SQLiteConnection*>(conn)->d->data; +} + +SQLiteCursor::SQLiteCursor(Connection* conn, QuerySchema& query, uint options ) + : Cursor( conn, query, options ) + , d( new SQLiteCursorData(conn) ) +{ + d->data = static_cast<SQLiteConnection*>(conn)->d->data; +} + +SQLiteCursor::~SQLiteCursor() +{ + close(); + delete d; +} + +bool SQLiteCursor::drv_open() +{ +// d->st.resize(statement.length()*2); + //TODO: decode +// d->st = statement.local8Bit(); +// d->st = m_conn->driver()->escapeString( statement.local8Bit() ); + + if(! d->data) { + // this may as example be the case if SQLiteConnection::drv_useDatabase() + // wasn't called before. Normaly sqlite_compile/sqlite3_prepare + // should handle it, but it crashes in in sqlite3SafetyOn at util.c:786 + kdWarning() << "SQLiteCursor::drv_open(): Database handle undefined." << endl; + return false; + } + +#ifdef SQLITE2 + d->st = m_sql.local8Bit(); + d->res = sqlite_compile( + d->data, + d->st.data(), + (const char**)&d->utail, + &d->prepared_st_handle, + &d->errmsg_p ); +#else //SQLITE3 + d->st = m_sql.utf8(); + d->res = sqlite3_prepare( + d->data, /* Database handle */ + d->st.data(), /* SQL statement, UTF-8 encoded */ + d->st.length(), /* Length of zSql in bytes. */ + &d->prepared_st_handle, /* OUT: Statement handle */ + 0/*const char **pzTail*/ /* OUT: Pointer to unused portion of zSql */ + ); +#endif + if (d->res!=SQLITE_OK) { + d->storeResult(); + return false; + } +//cursor is automatically @ first record +// m_beforeFirst = true; + + if (isBuffered()) { + d->records.resize(128); //TODO: manage size dynamically + } + + return true; +} + +/*bool SQLiteCursor::drv_getFirstRecord() +{ + bool ok = drv_getNextRecord();*/ +/* if ((m_options & Buffered) && ok) { //1st record is there: + //compute parameters for cursor's buffer: + //-size of record's array of pointer to values + d->cols_pointers_mem_size = d->curr_cols * sizeof(char*); + d->records_in_buf = 1; + }*/ + /*return ok; +}*/ + +bool SQLiteCursor::drv_close() +{ +#ifdef SQLITE2 + d->res = sqlite_finalize( d->prepared_st_handle, &d->errmsg_p ); +#else //SQLITE3 + d->res = sqlite3_finalize( d->prepared_st_handle ); +#endif + if (d->res!=SQLITE_OK) { + d->storeResult(); + return false; + } + return true; +} + +void SQLiteCursor::drv_getNextRecord() +{ +#ifdef SQLITE2 + static int _fieldCount; + d->res = sqlite_step( + d->prepared_st_handle, + &_fieldCount, + &d->curr_coldata, + &d->curr_colname); +#else //SQLITE3 + d->res = sqlite3_step( d->prepared_st_handle ); +#endif + if (d->res == SQLITE_ROW) { + m_result = FetchOK; +#ifdef SQLITE2 + m_fieldCount = (uint)_fieldCount; +#else + m_fieldCount = sqlite3_data_count(d->prepared_st_handle); +//#else //for SQLITE3 data fetching is delayed. Now we even do not take field count information +// // -- just set a flag that we've a data not fetched but available +// d->rowDataReadyToFetch = true; +#endif + //(m_logicalFieldCount introduced) m_fieldCount -= (m_containsROWIDInfo ? 1 : 0); + } else { +//#ifdef SQLITE3 +// d->rowDataReadyToFetch = false; +//#endif + if (d->res==SQLITE_DONE) + m_result = FetchEnd; + else + m_result = FetchError; + } + + //debug +/* + if (m_result == FetchOK && d->curr_coldata) { + for (uint i=0;i<m_fieldCount;i++) { + KexiDBDrvDbg<<"col."<< i<<": "<< d->curr_colname[i]<<" "<< d->curr_colname[m_fieldCount+i] + << " = " << (d->curr_coldata[i] ? QString::fromLocal8Bit(d->curr_coldata[i]) : "(NULL)") <<endl; + } +// KexiDBDrvDbg << "SQLiteCursor::drv_getNextRecord(): "<<m_fieldCount<<" col(s) fetched"<<endl; + }*/ +} + +void SQLiteCursor::drv_appendCurrentRecordToBuffer() +{ +// KexiDBDrvDbg << "SQLiteCursor::drv_appendCurrentRecordToBuffer():" <<endl; + if (!d->cols_pointers_mem_size) + d->cols_pointers_mem_size = m_fieldCount * sizeof(char*); + const char **record = (const char**)malloc(d->cols_pointers_mem_size); + const char **src_col = d->curr_coldata; + const char **dest_col = record; + for (uint i=0; i<m_fieldCount; i++,src_col++,dest_col++) { +// KexiDBDrvDbg << i <<": '" << *src_col << "'" <<endl; +// KexiDBDrvDbg << "src_col: " << src_col << endl; + *dest_col = *src_col ? strdup(*src_col) : 0; + } + d->records.insert(m_records_in_buf,record); +// KexiDBDrvDbg << "SQLiteCursor::drv_appendCurrentRecordToBuffer() ok." <<endl; +} + +void SQLiteCursor::drv_bufferMovePointerNext() +{ + d->curr_coldata++; //move to next record in the buffer +} + +void SQLiteCursor::drv_bufferMovePointerPrev() +{ + d->curr_coldata--; //move to prev record in the buffer +} + +//compute a place in the buffer that contain next record's data +//and move internal buffer pointer to that place +void SQLiteCursor::drv_bufferMovePointerTo(Q_LLONG at) +{ + d->curr_coldata = d->records.at(at); +} + +void SQLiteCursor::drv_clearBuffer() +{ + if (d->cols_pointers_mem_size>0) { + const uint records_in_buf = m_records_in_buf; + const char ***r_ptr = d->records.data(); + for (uint i=0; i<records_in_buf; i++, r_ptr++) { + // const char **record = m_records.at(i); + const char **field_data = *r_ptr; + // for (int col=0; col<d->curr_cols; col++, field_data++) { + for (uint col=0; col<m_fieldCount; col++, field_data++) { + free((void*)*field_data); //free field memory + } + free(*r_ptr); //free pointers to fields array + } + } +// d->curr_cols=0; +// m_fieldCount=0; + m_records_in_buf=0; + d->cols_pointers_mem_size=0; +// m_at_buffer=false; + d->records.clear(); +} + +/* +void SQLiteCursor::drv_storeCurrentRecord() +{ +#if 0 + assert(!m_data->rec_stored); + m_data->rec_stored = true; + m_data->next_cols = m_data->curr_cols; + for (int i=0;i<m_data->curr_cols;i++) { + KexiDBDrvDbg<<"[COPY] "<<i<<": "<< m_data->curr_coldata[i]<<endl; + if (m_data->curr_coldata[i]) + m_data->next_coldata[i] = strdup( m_data->curr_coldata[i] ); + else + m_data->next_coldata[i] = 0; + } +#endif +} +*/ + +/*TODO +const char *** SQLiteCursor::bufferData() +{ + if (!isBuffered()) + return 0; + return m_records.data(); +}*/ + +const char ** SQLiteCursor::rowData() const +{ + return d->curr_coldata; +} + +void SQLiteCursor::storeCurrentRow(RowData &data) const +{ +#ifdef SQLITE2 + const char **col = d->curr_coldata; +#endif + //const uint realCount = m_fieldCount + (m_containsROWIDInfo ? 1 : 0); + data.resize(m_fieldCount); + if (!m_fieldsExpanded) {//simple version: without types + for( uint i=0; i<m_fieldCount; i++ ) { +#ifdef SQLITE2 + data[i] = QVariant( *col ); + col++; +#else //SQLITE3 + data[i] = QString::fromUtf8( (const char*)sqlite3_column_text(d->prepared_st_handle, i) ); +#endif + } + return; + } + + //const uint fieldsExpandedCount = m_fieldsExpanded->count(); + const uint maxCount = QMIN(m_fieldCount, m_fieldsExpanded->count()); + // i - visible field's index, j - physical index + for( uint i=0, j=0; i<m_fieldCount; i++, j++ ) { +// while (j < m_detailedVisibility.count() && !m_detailedVisibility[j]) //!m_query->isColumnVisible(j)) +// j++; + while (j < maxCount && !m_fieldsExpanded->at(j)->visible) + j++; + if (j >= (maxCount /*+(m_containsROWIDInfo ? 1 : 0)*/)) { + //ERR! + break; + } + //(m_logicalFieldCount introduced) Field *f = (m_containsROWIDInfo && i>=m_fieldCount) ? 0 : m_fieldsExpanded->at(j)->field; + Field *f = (i>=m_fieldCount) ? 0 : m_fieldsExpanded->at(j)->field; +// KexiDBDrvDbg << "SQLiteCursor::storeCurrentRow(): col=" << (col ? *col : 0) << endl; + +#ifdef SQLITE2 + if (!*col) + data[i] = QVariant(); + else if (f && f->isTextType()) +# ifdef SQLITE_UTF8 + data[i] = QString::fromUtf8( *col ); +# else + data[i] = QVariant( *col ); //only latin1 +# endif + else if (f && f->isFPNumericType()) + data[i] = QVariant( QCString(*col).toDouble() ); + else { + switch (f ? f->type() : Field::Integer/*ROWINFO*/) { +//todo: use short, etc. + case Field::Byte: + case Field::ShortInteger: + case Field::Integer: + data[i] = QVariant( QCString(*col).toInt() ); + case Field::BigInteger: + data[i] = QVariant( QString::fromLatin1(*col).toLongLong() ); + case Field::Boolean: + data[i] = QVariant( sqliteStringToBool(QString::fromLatin1(*col)), 1 ); + break; + case Field::Date: + data[i] = QDate::fromString( QString::fromLatin1(*col), Qt::ISODate ); + break; + case Field::Time: + //QDateTime - a hack needed because QVariant(QTime) has broken isNull() + data[i] = KexiUtils::stringToHackedQTime(QString::fromLatin1(*col)); + break; + case Field::DateTime: { + QString tmp( QString::fromLatin1(*col) ); + tmp[10] = 'T'; + data[i] = QDateTime::fromString( tmp, Qt::ISODate ); + break; + } + default: + data[i] = QVariant( *col ); + } + } + + col++; +#else //SQLITE3 + data[i] = d->getValue(f, i); //, !f /*!f means ROWID*/); +#endif + } +} + +QVariant SQLiteCursor::value(uint i) +{ +// if (i > (m_fieldCount-1+(m_containsROWIDInfo?1:0))) //range checking + if (i > (m_fieldCount-1)) //range checking + return QVariant(); +//TODO: allow disable range checking! - performance reasons +// const KexiDB::Field *f = m_query ? m_query->field(i) : 0; + KexiDB::Field *f = (m_fieldsExpanded && i<m_fieldsExpanded->count()) + ? m_fieldsExpanded->at(i)->field : 0; +#ifdef SQLITE2 + //from most to least frequently used types: +//(m_logicalFieldCount introduced) if (i==m_fieldCount || f && f->isIntegerType()) + if (!f || f->isIntegerType()) + return QVariant( QCString(d->curr_coldata[i]).toInt() ); + else if (!f || f->isTextType()) + return QVariant( d->curr_coldata[i] ); + else if (f->isFPNumericType()) + return QVariant( QCString(d->curr_coldata[i]).toDouble() ); + + return QVariant( d->curr_coldata[i] ); //default +#else + return d->getValue(f, i); //, i==m_logicalFieldCount/*ROWID*/); +#endif +} + +/*! Stores string value taken from field number \a i to \a str. + \return false when range checking failed. +bool SQLiteCursor::storeStringValue(uint i, QString &str) +{ + if (i > (m_fieldCount-1)) //range checking + return false; + str = d->curr_coldata[i]; + return true; +}*/ + +int SQLiteCursor::serverResult() +{ + return d->res; +} + +QString SQLiteCursor::serverResultName() +{ +#ifdef SQLITE2 + return QString::fromLatin1( sqlite_error_string(d->res) ); +#else //SQLITE3 + return QString::fromLatin1( d->result_name ); +#endif +} + +QString SQLiteCursor::serverErrorMsg() +{ + return d->errmsg; +} + +void SQLiteCursor::drv_clearServerResult() +{ + d->res = SQLITE_OK; + d->errmsg_p = 0; +} + diff --git a/kexi/kexidb/drivers/sqlite/sqlitecursor.h b/kexi/kexidb/drivers/sqlite/sqlitecursor.h new file mode 100644 index 00000000..b7170f67 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlitecursor.h @@ -0,0 +1,92 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_SQLITECURSOR_H +#define KEXIDB_SQLITECURSOR_H + +#include <qstring.h> + +#include <kexidb/cursor.h> +#include "connection.h" + +namespace KexiDB { + +class SQLiteCursorData; + +/*! + +*/ +class SQLiteCursor : public Cursor +{ + public: + virtual ~SQLiteCursor(); + virtual QVariant value(uint i); + + /*! [PROTOTYPE] \return internal buffer data. */ +//TODO virtual const char *** bufferData() + /*! [PROTOTYPE] \return current record data or NULL if there is no current records. */ + virtual const char ** rowData() const; + + virtual void storeCurrentRow(RowData &data) const; + +// virtual bool save(RowData& data, RowEditBuffer& buf); + + virtual int serverResult(); + virtual QString serverResultName(); + virtual QString serverErrorMsg(); + + protected: + /*! Cursor will operate on \a conn, raw \a statement will be used to execute query. */ + SQLiteCursor(Connection* conn, const QString& statement, uint options = NoOptions ); + + /*! Cursor will operate on \a conn, \a query schema will be used to execute query. */ + SQLiteCursor(Connection* conn, QuerySchema& query, + uint options = NoOptions ); + + virtual bool drv_open(); + + virtual bool drv_close(); +// virtual bool drv_moveFirst(); + virtual void drv_getNextRecord(); +//unused virtual bool drv_getPrevRecord(); + + virtual void drv_appendCurrentRecordToBuffer(); + virtual void drv_bufferMovePointerNext(); + virtual void drv_bufferMovePointerPrev(); + virtual void drv_bufferMovePointerTo(Q_LLONG at); + +//TODO virtual void drv_storeCurrentRecord(); + + //PROTOTYPE: + /*! Method called when cursor's buffer need to be cleared + (only for buffered cursor type), eg. in close(). */ + virtual void drv_clearBuffer(); + + virtual void drv_clearServerResult(); + + SQLiteCursorData *d; + + friend class SQLiteConnection; +}; + +} //namespace KexiDB + +#endif + + diff --git a/kexi/kexidb/drivers/sqlite/sqlitedriver.cpp b/kexi/kexidb/drivers/sqlite/sqlitedriver.cpp new file mode 100644 index 00000000..e2abc246 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlitedriver.cpp @@ -0,0 +1,159 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/connection.h> +#include <kexidb/drivermanager.h> +#include <kexidb/driver_p.h> +#include <kexidb/utils.h> + +#include "sqlite.h" +#include "sqlitedriver.h" +#include "sqliteconnection.h" +#include "sqliteconnection_p.h" +#include "sqliteadmin.h" + +#include <kdebug.h> + +using namespace KexiDB; + +#ifdef SQLITE2 +KEXIDB_DRIVER_INFO( SQLiteDriver, sqlite2 ) +#else +KEXIDB_DRIVER_INFO( SQLiteDriver, sqlite3 ) +#endif + +//! driver specific private data +//! @internal +class KexiDB::SQLiteDriverPrivate +{ + public: + SQLiteDriverPrivate() + { + } +}; + +//PgSqlDB::PgSqlDB(QObject *parent, const char *name, const QStringList &) +SQLiteDriver::SQLiteDriver( QObject *parent, const char *name, const QStringList &args ) + : Driver( parent, name, args ) + ,dp( new SQLiteDriverPrivate() ) +{ + d->isFileDriver = true; + d->isDBOpenedAfterCreate = true; + d->features = SingleTransactions | CursorForward +#ifndef SQLITE2 + | CompactingDatabaseSupported; +#endif + ; + + //special method for autoincrement definition + beh->SPECIAL_AUTO_INCREMENT_DEF = true; + beh->AUTO_INCREMENT_FIELD_OPTION = ""; //not available + beh->AUTO_INCREMENT_TYPE = "INTEGER"; + beh->AUTO_INCREMENT_PK_FIELD_OPTION = "PRIMARY KEY"; + beh->AUTO_INCREMENT_REQUIRES_PK = true; + beh->ROW_ID_FIELD_NAME = "OID"; + beh->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY=true; + beh->QUOTATION_MARKS_FOR_IDENTIFIER='"'; + beh->SELECT_1_SUBQUERY_SUPPORTED = true; + beh->SQL_KEYWORDS = keywords; + initSQLKeywords(29); + + //predefined properties + d->properties["client_library_version"] = sqlite_libversion(); + d->properties["default_server_encoding"] = +#ifdef SQLITE2 + sqlite_libencoding(); +#else //SQLITE3 + "UTF8"; //OK? +#endif + + d->typeNames[Field::Byte]="Byte"; + d->typeNames[Field::ShortInteger]="ShortInteger"; + d->typeNames[Field::Integer]="Integer"; + d->typeNames[Field::BigInteger]="BigInteger"; + d->typeNames[Field::Boolean]="Boolean"; + d->typeNames[Field::Date]="Date"; // In fact date/time types could be declared as datetext etc. + d->typeNames[Field::DateTime]="DateTime"; // to force text affinity..., see http://sqlite.org/datatype3.html + d->typeNames[Field::Time]="Time"; // + d->typeNames[Field::Float]="Float"; + d->typeNames[Field::Double]="Double"; + d->typeNames[Field::Text]="Text"; + d->typeNames[Field::LongText]="CLOB"; + d->typeNames[Field::BLOB]="BLOB"; +} + +SQLiteDriver::~SQLiteDriver() +{ + delete dp; +} + + +KexiDB::Connection* +SQLiteDriver::drv_createConnection( ConnectionData &conn_data ) +{ + return new SQLiteConnection( this, conn_data ); +} + +bool SQLiteDriver::isSystemObjectName( const QString& n ) const +{ + return Driver::isSystemObjectName(n) || n.lower().startsWith("sqlite_"); +} + +bool SQLiteDriver::drv_isSystemFieldName( const QString& n ) const +{ + return n.lower()=="_rowid_" + || n.lower()=="rowid" + || n.lower()=="oid"; +} + +QString SQLiteDriver::escapeString(const QString& str) const +{ + return QString("'")+QString(str).replace( '\'', "''" ) + "'"; +} + +QCString SQLiteDriver::escapeString(const QCString& str) const +{ + return QCString("'")+QCString(str).replace( '\'', "''" )+"'"; +} + +QString SQLiteDriver::escapeBLOB(const QByteArray& array) const +{ + return KexiDB::escapeBLOB(array, KexiDB::BLOBEscapeXHex); +} + +QString SQLiteDriver::drv_escapeIdentifier( const QString& str) const +{ + return QString(str).replace( '"', "\"\"" ); +} + +QCString SQLiteDriver::drv_escapeIdentifier( const QCString& str) const +{ + return QCString(str).replace( '"', "\"\"" ); +} + +AdminTools* SQLiteDriver::drv_createAdminTools() const +{ +#ifdef SQLITE2 + return new AdminTools(); //empty impl. +#else + return new SQLiteAdminTools(); +#endif +} + +#include "sqlitedriver.moc" diff --git a/kexi/kexidb/drivers/sqlite/sqlitedriver.h b/kexi/kexidb/drivers/sqlite/sqlitedriver.h new file mode 100644 index 00000000..66d4d4d2 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlitedriver.h @@ -0,0 +1,82 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_DRIVER_SQLITE_H +#define KEXIDB_DRIVER_SQLITE_H + +#include <qstringlist.h> + +#include <kexidb/driver.h> + +namespace KexiDB +{ + +class Connection; +class DriverManager; +class SQLiteDriverPrivate; + +//! SQLite database driver. +class SQLiteDriver : public Driver +{ + Q_OBJECT + KEXIDB_DRIVER + + public: + SQLiteDriver( QObject *parent, const char *name, const QStringList &args = QStringList() ); + virtual ~SQLiteDriver(); + + /*! \return true if \a n is a system object name; + for this driver any object with name prefixed with "sqlite_" + is considered as system object. + */ + virtual bool isSystemObjectName( const QString& n ) const; + + /*! \return false for this driver. */ + virtual bool isSystemDatabaseName( const QString& ) const { return false; } + + //! Escape a string for use as a value + virtual QString escapeString(const QString& str) const; + virtual QCString escapeString(const QCString& str) const; + + //! Escape BLOB value \a array + virtual QString escapeBLOB(const QByteArray& array) const; + + protected: + virtual QString drv_escapeIdentifier( const QString& str) const; + virtual QCString drv_escapeIdentifier( const QCString& str) const; + virtual Connection *drv_createConnection( ConnectionData &conn_data ); + virtual AdminTools* drv_createAdminTools() const; + + /*! \return true if \a n is a system field name; + for this driver fields with name equal "_ROWID_" + is considered as system field. + */ + virtual bool drv_isSystemFieldName( const QString& n ) const; + + SQLiteDriverPrivate *dp; + + private: + static const char *keywords[]; + +}; + +} + +#endif + diff --git a/kexi/kexidb/drivers/sqlite/sqlitekeywords.cpp b/kexi/kexidb/drivers/sqlite/sqlitekeywords.cpp new file mode 100644 index 00000000..a3d6095b --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlitekeywords.cpp @@ -0,0 +1,39 @@ + /* + * This file has been automatically generated from + * koffice/kexi/tools/sql_keywords/sql_keywords.sh and + * ../../3rdparty/kexisql3/src/tokenize.c. + * + * Please edit the sql_keywords.sh, not this file! + */ +#include <sqlitedriver.h> + +namespace KexiDB { + const char* SQLiteDriver::keywords[] = { + "ABORT", + "ATTACH", + "CLUSTER", + "CONFLICT", + "DEFERRED", + "DEFERRABLE", + "DETACH", + "EACH", + "EXCEPT", + "FAIL", + "GLOB", + "IMMEDIATE", + "INITIALLY", + "INSTEAD", + "INTERSECT", + "ISNULL", + "NOTNULL", + "OF", + "PRAGMA", + "RAISE", + "STATEMENT", + "TEMP", + "TRIGGER", + "VACUUM", + "VIEW", + 0 + }; +} diff --git a/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.cpp b/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.cpp new file mode 100644 index 00000000..9103b131 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.cpp @@ -0,0 +1,242 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "sqlitepreparedstatement.h" + +#include <kdebug.h> +#include <assert.h> + +using namespace KexiDB; + +SQLitePreparedStatement::SQLitePreparedStatement(StatementType type, ConnectionInternal& conn, + FieldList& fields) + : KexiDB::PreparedStatement(type, conn, fields) + , SQLiteConnectionInternal(conn.connection) + , prepared_st_handle(0) + , m_resetRequired(false) +{ + data_owned = false; + data = dynamic_cast<KexiDB::SQLiteConnectionInternal&>(conn).data; //copy + + temp_st = generateStatementString(); +#ifdef SQLITE2 + //! @todo +#else + if (!temp_st.isEmpty()) { + res = sqlite3_prepare( + data, /* Database handle */ + temp_st, //const char *zSql, /* SQL statement, UTF-8 encoded */ + temp_st.length(), //int nBytes, /* Length of zSql in bytes. */ + &prepared_st_handle, //sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + 0 //const char **pzTail /* OUT: Pointer to unused portion of zSql */ + ); + if (SQLITE_OK != res) { +//! @todo copy error msg + } + } +#endif +} + +SQLitePreparedStatement::~SQLitePreparedStatement() +{ +#ifdef SQLITE2 +//! @todo +#else + sqlite3_finalize(prepared_st_handle); + prepared_st_handle = 0; +#endif +} + +bool SQLitePreparedStatement::execute() +{ +#ifdef SQLITE2 +//! @todo +#else + if (!prepared_st_handle) + return false; + if (m_resetRequired) { + res = sqlite3_reset(prepared_st_handle); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + m_resetRequired = false; + } + + int arg=1; //arg index counted from 1 + KexiDB::Field *field; + + Field::List _dummy; + Field::ListIterator itFields(_dummy); + //for INSERT, we're iterating over inserting values + //for SELECT, we're iterating over WHERE conditions + if (m_type == SelectStatement) + itFields = *m_whereFields; + else if (m_type == InsertStatement) + itFields = m_fields->fieldsIterator(); + else + assert(0); //impl. error + + for (QValueListConstIterator<QVariant> it = m_args.constBegin(); + (field = itFields.current()); ++it, ++itFields, arg++) + { + if (it==m_args.constEnd() || (*it).isNull()) {//no value to bind or the value is null: bind NULL + res = sqlite3_bind_null(prepared_st_handle, arg); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + continue; + } + if (field->isTextType()) { + //! @todo optimize: make a static copy so SQLITE_STATIC can be used + QCString utf8String((*it).toString().utf8()); + res = sqlite3_bind_text(prepared_st_handle, arg, + (const char*)utf8String, utf8String.length(), SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + } + else switch (field->type()) { + case KexiDB::Field::Byte: + case KexiDB::Field::ShortInteger: + case KexiDB::Field::Integer: + { +//! @todo what about unsigned > INT_MAX ? + bool ok; + const int value = (*it).toInt(&ok); + if (ok) { + res = sqlite3_bind_int(prepared_st_handle, arg, value); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + } + else { + res = sqlite3_bind_null(prepared_st_handle, arg); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + } + break; + } + case KexiDB::Field::Float: + case KexiDB::Field::Double: + res = sqlite3_bind_double(prepared_st_handle, arg, (*it).toDouble()); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + case KexiDB::Field::BigInteger: + { +//! @todo what about unsigned > LLONG_MAX ? + bool ok; + Q_LLONG value = (*it).toLongLong(&ok); + if (ok) { + res = sqlite3_bind_int64(prepared_st_handle, arg, value); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + } + else { + res = sqlite3_bind_null(prepared_st_handle, arg); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + } + break; + } + case KexiDB::Field::Boolean: + res = sqlite3_bind_text(prepared_st_handle, arg, + QString::number((*it).toBool() ? 1 : 0).latin1(), + 1, SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + case KexiDB::Field::Time: + res = sqlite3_bind_text(prepared_st_handle, arg, + (*it).toTime().toString(Qt::ISODate).latin1(), + sizeof("HH:MM:SS"), SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + case KexiDB::Field::Date: + res = sqlite3_bind_text(prepared_st_handle, arg, + (*it).toDate().toString(Qt::ISODate).latin1(), + sizeof("YYYY-MM-DD"), SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + case KexiDB::Field::DateTime: + res = sqlite3_bind_text(prepared_st_handle, arg, + (*it).toDateTime().toString(Qt::ISODate).latin1(), + sizeof("YYYY-MM-DDTHH:MM:SS"), SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + case KexiDB::Field::BLOB: + { + const QByteArray byteArray((*it).toByteArray()); + res = sqlite3_bind_blob(prepared_st_handle, arg, + (const char*)byteArray, byteArray.size(), SQLITE_TRANSIENT /*??*/); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + break; + } + default: + KexiDBWarn << "PreparedStatement::execute(): unsupported field type: " + << field->type() << " - NULL value bound to column #" << arg << endl; + res = sqlite3_bind_null(prepared_st_handle, arg); + if (SQLITE_OK != res) { + //! @todo msg? + return false; + } + } //switch + } + + //real execution + res = sqlite3_step(prepared_st_handle); + m_resetRequired = true; + if (m_type == InsertStatement && res == SQLITE_DONE) { + return true; + } + if (m_type == SelectStatement) { + //fetch result + + //todo + } +#endif + return false; +} + diff --git a/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.h b/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.h new file mode 100644 index 00000000..b8b9c376 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.h @@ -0,0 +1,50 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_SQLITEPREPAREDSTATEMENT_H +//&& !defined SQLITE2 +#define KEXIDB_SQLITEPREPAREDSTATEMENT_H + +#include <kexidb/preparedstatement.h> +#include "sqliteconnection_p.h" + +namespace KexiDB { + +/*! Implementation of prepared statements for SQLite driver. */ +class SQLitePreparedStatement : public PreparedStatement, SQLiteConnectionInternal +{ + public: + SQLitePreparedStatement(StatementType type, ConnectionInternal& conn, + FieldList& fields); + + virtual ~SQLitePreparedStatement(); + + virtual bool execute(); + +#ifdef SQLITE2 + sqlite_vm *prepared_st_handle; +#else //SQLITE3 + sqlite3_stmt *prepared_st_handle; +#endif + bool m_resetRequired : 1; +}; + +} + +#endif diff --git a/kexi/kexidb/drivers/sqlite/sqlitevacuum.cpp b/kexi/kexidb/drivers/sqlite/sqlitevacuum.cpp new file mode 100644 index 00000000..adf8709a --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlitevacuum.cpp @@ -0,0 +1,150 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/global.h> +#include "sqlitevacuum.h" + +#include <kstandarddirs.h> +#include <kprogress.h> +#include <kdebug.h> +#include <klocale.h> +#include <ktempfile.h> +#include <kmessagebox.h> +#include <kio/global.h> + +#include <qfileinfo.h> +#include <qdir.h> +#include <qapplication.h> +#include <qprocess.h> +#include <qcursor.h> + +#include <unistd.h> + +SQLiteVacuum::SQLiteVacuum(const QString& filePath) +: m_filePath(filePath) +{ + m_process = 0; + m_percent = 0; + m_dlg = 0; + m_result = true; +} + +SQLiteVacuum::~SQLiteVacuum() +{ + delete m_process; + if (m_dlg) + m_dlg->close(); + delete m_dlg; +} + +tristate SQLiteVacuum::run() +{ + const QString ksqlite_app = KStandardDirs::findExe( "ksqlite" ); + if (ksqlite_app.isEmpty()) { + m_result = false; + return m_result; + } + QFileInfo fi(m_filePath); + if (!fi.isReadable()) { + KexiDBDrvWarn << "SQLiteVacuum::run(): No such file" << m_filePath << endl; + return false; + } + const uint origSize = fi.size(); + + QStringList args; + args << ksqlite_app << "-verbose-vacuum" << m_filePath << "vacuum"; + m_process = new QProcess(args, this, "process"); + m_process->setWorkingDirectory( QFileInfo(m_filePath).dir(true) ); + connect( m_process, SIGNAL(readyReadStdout()), this, SLOT(readFromStdout()) ); + connect( m_process, SIGNAL(processExited()), this, SLOT(processExited()) ); + if (!m_process->start()) { + m_result = false; + return m_result; + } + m_dlg = new KProgressDialog(0, 0, i18n("Compacting database"), + "<qt>"+i18n("Compacting database \"%1\"...") + .arg("<nobr>"+QDir::convertSeparators(QFileInfo(m_filePath).fileName())+"</nobr>") + ); + m_dlg->adjustSize(); + m_dlg->resize(300, m_dlg->height()); + connect(m_dlg, SIGNAL(cancelClicked()), this, SLOT(cancelClicked())); + m_dlg->setMinimumDuration(1000); + m_dlg->setAutoClose(true); + m_dlg->progressBar()->setTotalSteps(100); + m_dlg->exec(); + while (m_process->isRunning()) { + readFromStdout(); + usleep(50000); + } + delete m_process; + m_process = 0; + if (m_result == true) { + const uint newSize = QFileInfo(m_filePath).size(); + const uint decrease = 100-100*newSize/origSize; + KMessageBox::information(0, i18n("The database has been compacted. Current size decreased by %1% to %2.") + .arg(decrease).arg(KIO::convertSize(newSize))); + } + return m_result; +} + +void SQLiteVacuum::readFromStdout() +{ + while (true) { + QString s( m_process->readLineStdout() ); //readStdout(); + if (s.isEmpty()) + break; + m_dlg->progressBar()->setProgress(m_percent); +// KexiDBDrvDbg << m_percent << " " << s << endl; + if (s.startsWith("VACUUM: ")) { + //set previously known progress + m_dlg->progressBar()->setProgress(m_percent); + //update progress info + if (s.mid(8,4)=="100%") { + m_percent = 100; + m_dlg->setAllowCancel(false); + m_dlg->setCursor(QCursor(Qt::WaitCursor)); + } + else if (s.mid(9,1)=="%") { + m_percent = s.mid(8,1).toInt(); + } + else if (s.mid(10,1)=="%") { + m_percent = s.mid(8,2).toInt(); + } + m_process->writeToStdin(" "); + } + } +} + +void SQLiteVacuum::processExited() +{ +// KexiDBDrvDbg << sender()->name() << " EXIT" << endl; + m_dlg->close(); + delete m_dlg; + m_dlg = 0; +} + +void SQLiteVacuum::cancelClicked() +{ + if (!m_process->normalExit()) { + m_process->writeToStdin("q"); //quit + m_result = cancelled; + } +} + +#include "sqlitevacuum.moc" diff --git a/kexi/kexidb/drivers/sqlite/sqlitevacuum.h b/kexi/kexidb/drivers/sqlite/sqlitevacuum.h new file mode 100644 index 00000000..4424b7fc --- /dev/null +++ b/kexi/kexidb/drivers/sqlite/sqlitevacuum.h @@ -0,0 +1,70 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef SQLITE_VACUUM_H +#define SQLITE_VACUUM_H + +#include <qobject.h> +#include <qstring.h> + +#include <kexiutils/tristate.h> + +class QProcess; +class KTempFile; +class KProgressDialog; + +//! @short Helper class performing interactive compacting (VACUUM) of the SQLite database +/*! Proved SQLite database filename in the constructor. + Then execute run() should be executed. + + KProgressDialog will be displayed. Its progress bar will be updated whenever another + table's data compacting is performed. User can click "Cancel" button in any time + (except the final committing) to cancel the operation. In this case, + it's guaranteed that the original file remains unchanged. + + This is possible because we rely on SQLite's VACUUM SQL command, which itself temporarily + creates a copy of the original database file, and replaces the orginal with the new only + on success. +*/ +class SQLiteVacuum : public QObject +{ + Q_OBJECT + public: + SQLiteVacuum(const QString& filePath); + ~SQLiteVacuum(); + + /*! Performs compacting procedure. + \return true on success, false on failure and cancelled if user + clicked "Cancel" button in the progress dialog. */ + tristate run(); + + public slots: + void readFromStdout(); + void processExited(); + void cancelClicked(); + + protected: + QString m_filePath; + QProcess *m_process; + KProgressDialog* m_dlg; + int m_percent; + tristate m_result; +}; + +#endif diff --git a/kexi/kexidb/drivers/sqlite2/Makefile.am b/kexi/kexidb/drivers/sqlite2/Makefile.am new file mode 100644 index 00000000..891a071a --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/Makefile.am @@ -0,0 +1,31 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexidb_sqlite2driver.la + +INCLUDES = -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/kexidb \ + -I$(top_srcdir)/kexi/3rdparty/kexisql/src \ + -I$(srcdir)/../sqlite \ + -I$(srcdir)/../sqlite/moc \ + -I../sqlite \ + $(all_includes) + +kexidb_sqlite2driver_la_METASOURCES = AUTO + +kexidb_sqlite2driver_la_SOURCES = sqliteconnection.cpp sqlitedriver.cpp sqlitecursor.cpp \ + sqlitepreparedstatement.cpp sqliteadmin.cpp sqlitealter.cpp + +kexidb_sqlite2driver_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) \ + $(top_builddir)/kexi/3rdparty/kexisql/src/libkexisql2.la \ + $(top_builddir)/kexi/kexidb/libkexidb.la \ + $(top_builddir)/kexi/kexidb/parser/libkexidbparser.la + +kexidb_sqlite2driver_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO) + + +kde_services_DATA = kexidb_sqlite2driver.desktop + + +KDE_CXXFLAGS += -DKEXIDB_SQLITE_DRIVER_EXPORT= -D__KEXIDB__= \ + -DSQLITE2= -include $(top_srcdir)/kexi/kexidb/global.h + diff --git a/kexi/kexidb/drivers/sqlite2/kexidb_sqlite2driver.desktop b/kexi/kexidb/drivers/sqlite2/kexidb_sqlite2driver.desktop new file mode 100644 index 00000000..5e0959df --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/kexidb_sqlite2driver.desktop @@ -0,0 +1,57 @@ +[Desktop Entry] +Name=SQLite2 +Name[sv]=Sqlite 2 +Comment=SQLite is default Kexi embedded SQL engine +Comment[bg]=SQLite е СУБД по подразбиране в Kexi , с вградено SQL ядро +Comment[ca]=SQLite és el motor SQL encastat i per defecte de Kexi +Comment[cy]=Y peiriant SQL mewnadeiledig rhagosod Kexi yw SQLite +Comment[da]=SQLite er en standard Kexi-indlejret SQL-motor +Comment[de]=SQLite ist der standardmäßig in Kexi eingebettete SQL-Treiber +Comment[el]=Η SQLite είναι η προκαθορισμένη ενσωματωμένη μηχανή SQL του Kexi +Comment[es]=SQLite es el motor SQL incrustado en Kexi de forma predefinida +Comment[et]=SQLite on Kexi vaikimisi kasutatav SQL mootor +Comment[eu]=SQLite Kexi-ren kapsultatutako SQL motore lehenetsia da +Comment[fa]=SQLite، پیشفرض موتور SQL نهفتۀ Kexi است +Comment[fi]=SQLite on Kexin käyttämä sisäänrakennetti SQL-moottori. +Comment[fr]=SQLite est le moteur SQL par défaut intégré à Kexi +Comment[fy]=SQLite is de standert SQL-databank foar Kexi +Comment[gl]=SQLite é o motor embebido de SQL de Kexi +Comment[he]=SQLite הוא מנוע ה־SQL המוטבע של Kexi המשמש כברירת מחדל +Comment[hi]=केएक्साई एम्बेडेड एसक्यूएल इंजिन के लिए एसक्यूएललाइट डिफ़ॉल्ट है +Comment[hr]=SQLite je zadani Kexi ugrađeni pogon SQL pogona +Comment[hu]=Az SQLite a Kexi alapértelmezett, beépített SQL-motorja +Comment[is]=SQLite er sjálfgefna Kexi SQL vélin +Comment[it]=SQLite è il motore predefinito integrato in Kexi +Comment[ja]=SQLite は Kexi の標準埋め込み SQL エンジンです。 +Comment[km]=SQLite គឺជាម៉ាស៊ីន SQL ដែលបានបង្កប់ក្នុង Kexi តាមលំនាំដើម +Comment[lv]=SQLite ir Kexi noklusējuma SQL dzinējs +Comment[ms]=SQLite adalah KeXi piawai yang dipasang dalam enjin SQL +Comment[nb]=SQLite er den innebygde SQL-motoren i Kexi +Comment[nds]=SQLite is de standardinbett SQL-Driever för Kexi +Comment[ne]=SQLite पूर्वनिर्धारित केक्सी सम्मिलित SQL इन्जिन हो +Comment[nl]=SQLite is de standaard SQL-database voor Kexi +Comment[nn]=SQLite er den innebygde SQL-motoren i Kexi +Comment[pl]=SQLite jest domyślnym wbudowanym silnikiem SQL dla Kexi +Comment[pt]=SQLite é o motor embebido de SQL do Kexi +Comment[pt_BR]=SQLite é o mecanismo SQL embutido padrão do Kexi +Comment[ru]=SQLite -- движок встроенного SQL по умолчанию в Kexi +Comment[se]=SQLite lea Kexi:a sisahuksejuvvon SQL-mohtor +Comment[sk]=SQLite je štandardný Kexi embedded SQL systém +Comment[sl]=SQLite je privzet vključen pogon SQL za Kexi +Comment[sr]=SQLite је подразумевани Kexi-јев уграђен SQL мотор +Comment[sr@Latn]=SQLite je podrazumevani Kexi-jev ugrađen SQL motor +Comment[sv]=Sqlite är inbyggd SQL-standarddatabas i Kexi +Comment[tg]=SQLite -- движоки SQL, ки дар дар дохили Kexi бо пешфарз сохта шудааст +Comment[tr]=SQlite Kexi'ye gömülü varsayılan SQL motorudur +Comment[uk]=SQLite - це типовий рушій SQL вбудований в Kexi +Comment[zh_CN]=SQLite 是嵌入 Kexi 的默认 SQL 引擎 +Comment[zh_TW]=SQLite 是 Kexi 預設內建的 SQL 引擎 +X-KDE-Library=kexidb_sqlite2driver +ServiceTypes=Kexi/DBDriver +Type=Service +InitialPreference=8 +X-Kexi-DriverName=SQLite2 +X-Kexi-DriverType=File +X-Kexi-FileDBDriverMimeList=application/x-sqlite2,application/x-kexiproject-sqlite2,application/x-kexiproject-sqlite,application/x-hk_classes-sqlite2 +X-Kexi-KexiDBVersion=1.8 +X-Kexi-DoNotAllowProjectImportingTo=true diff --git a/kexi/kexidb/drivers/sqlite2/sqlite2.pro b/kexi/kexidb/drivers/sqlite2/sqlite2.pro new file mode 100644 index 00000000..e9597ebd --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqlite2.pro @@ -0,0 +1,12 @@ +include( ../sqlite/sqlite_common.pro ) + +LIBS += $$KDELIBDESTDIR/kexisql2$$KDELIBDEBUGLIB + +INCLUDEPATH += $(KEXI)/3rdparty/kexisql/src ../sqlite/moc + +system( bash kmoc ../sqlite ) + +DEFINES += SQLITE2 + +TARGET = kexidb_sqlite2driver$$KDELIBDEBUG + diff --git a/kexi/kexidb/drivers/sqlite2/sqliteadmin.cpp b/kexi/kexidb/drivers/sqlite2/sqliteadmin.cpp new file mode 100644 index 00000000..8b0ca7ea --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqliteadmin.cpp @@ -0,0 +1 @@ +#include "../sqlite/sqliteadmin.cpp" diff --git a/kexi/kexidb/drivers/sqlite2/sqliteadmin.h b/kexi/kexidb/drivers/sqlite2/sqliteadmin.h new file mode 100644 index 00000000..bd55e6c6 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqliteadmin.h @@ -0,0 +1 @@ +#include "../sqlite/sqliteadmin.h" diff --git a/kexi/kexidb/drivers/sqlite2/sqlitealter.cpp b/kexi/kexidb/drivers/sqlite2/sqlitealter.cpp new file mode 100644 index 00000000..1942fee7 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqlitealter.cpp @@ -0,0 +1 @@ +#include "../sqlite/sqlitealter.cpp" diff --git a/kexi/kexidb/drivers/sqlite2/sqliteconnection.cpp b/kexi/kexidb/drivers/sqlite2/sqliteconnection.cpp new file mode 100644 index 00000000..51bda0d8 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqliteconnection.cpp @@ -0,0 +1 @@ +#include "../sqlite/sqliteconnection.cpp" diff --git a/kexi/kexidb/drivers/sqlite2/sqliteconnection.h b/kexi/kexidb/drivers/sqlite2/sqliteconnection.h new file mode 100644 index 00000000..34e22a13 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqliteconnection.h @@ -0,0 +1,2 @@ +#include "../sqlite/sqliteconnection.h" + diff --git a/kexi/kexidb/drivers/sqlite2/sqliteconnection_p.h b/kexi/kexidb/drivers/sqlite2/sqliteconnection_p.h new file mode 100644 index 00000000..53bc38e9 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqliteconnection_p.h @@ -0,0 +1,2 @@ +#include "../sqlite/sqliteconnection_p.h" + diff --git a/kexi/kexidb/drivers/sqlite2/sqlitecursor.cpp b/kexi/kexidb/drivers/sqlite2/sqlitecursor.cpp new file mode 100644 index 00000000..764fc4db --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqlitecursor.cpp @@ -0,0 +1,2 @@ +#include "../sqlite/sqlitecursor.cpp" + diff --git a/kexi/kexidb/drivers/sqlite2/sqlitecursor.h b/kexi/kexidb/drivers/sqlite2/sqlitecursor.h new file mode 100644 index 00000000..8d4f87fe --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqlitecursor.h @@ -0,0 +1,2 @@ +#include "../sqlite/sqlitecursor.h" + diff --git a/kexi/kexidb/drivers/sqlite2/sqlitedriver.cpp b/kexi/kexidb/drivers/sqlite2/sqlitedriver.cpp new file mode 100644 index 00000000..de8ce2d8 --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqlitedriver.cpp @@ -0,0 +1,2 @@ +#include "../sqlite/sqlitedriver.cpp" +#include "../sqlite/sqlitekeywords.cpp" diff --git a/kexi/kexidb/drivers/sqlite2/sqlitedriver.h b/kexi/kexidb/drivers/sqlite2/sqlitedriver.h new file mode 100644 index 00000000..a0af4b9f --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqlitedriver.h @@ -0,0 +1,2 @@ +#include "../sqlite/sqlitedriver.h" + diff --git a/kexi/kexidb/drivers/sqlite2/sqlitepreparedstatement.cpp b/kexi/kexidb/drivers/sqlite2/sqlitepreparedstatement.cpp new file mode 100644 index 00000000..2e45bb0b --- /dev/null +++ b/kexi/kexidb/drivers/sqlite2/sqlitepreparedstatement.cpp @@ -0,0 +1 @@ +#include "../sqlite/sqlitepreparedstatement.cpp" diff --git a/kexi/kexidb/error.h b/kexi/kexidb/error.h new file mode 100644 index 00000000..01ab8052 --- /dev/null +++ b/kexi/kexidb/error.h @@ -0,0 +1,138 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + Copyright (C) 2003 Joseph Wenninger <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef _KEXI_ERROR_H_ +#define _KEXI_ERROR_H_ + +#include <qstring.h> + +#include "kexidb/kexidb_export.h" + +/*! Fine-grained KexiDB error codes */ + +#define ERR_NONE 0 +#define ERR_NO_NAME_SPECIFIED 9 //! used when name (e.g. for database) was not specified +#define ERR_DRIVERMANAGER 10 +#define ERR_INVALID_IDENTIFIER 11 //! used when name (e.g. for database) was not specified +#define ERR_MISSING_DB_LOCATION 20 +#define ERR_ALREADY_CONNECTED 30 +#define ERR_NO_CONNECTION 40 //!< when opened connection was expected using KexiDB::Connection +#define ERR_CONNECTION_FAILED 41 //!< when connection has failed +#define ERR_CLOSE_FAILED 42 //!< when closing has failed +#define ERR_NO_DB_USED 43 //!< when used database was expected in KexiDB::Connection +#define ERR_OBJECT_EXISTS 50 +#define ERR_OBJECT_THE_SAME 51 +#define ERR_OBJECT_NOT_FOUND 60 +#define ERR_ACCESS_RIGHTS 70 +#define ERR_TRANSACTION_ACTIVE 80 +#define ERR_NO_TRANSACTION_ACTIVE 81 +#define ERR_NO_DB_PROPERTY 90 //! database property not found, see DatabaseProperties class +#define ERR_DB_SPECIFIC 100 +#define ERR_CURSOR_NOT_OPEN 110 +#define ERR_SINGLE_DB_NAME_MISMATCH 120 +#define ERR_CURSOR_RECORD_FETCHING 130 //!< eg. for Cursor::drv_getNextRecord() +#define ERR_UNSUPPORTED_DRV_FEATURE 140 //!< given driver's feature is unsupported (eg. transactins) +#define ERR_ROLLBACK_OR_COMMIT_TRANSACTION 150 //!< error during transaction rollback or commit +#define ERR_SYSTEM_NAME_RESERVED 160 //!< system name is reserved and cannot be used + //!< (e.g. for table, db, or field name) +#define ERR_CANNOT_CREATE_EMPTY_OBJECT 170 //!< empty object cannot be created + //!< (e.g. table without fields) +#define ERR_INVALID_DRIVER_IMPL 180 //! driver's implementation is invalid +#define ERR_INCOMPAT_DRIVER_VERSION 181 //!< driver's version is incompatible +#define ERR_INCOMPAT_DATABASE_VERSION 182 //!< db's version is incompatible with currently + //!< used Kexi version +#define ERR_INVALID_DATABASE_CONTENTS 183 //!< db's contents are invalid + //!< (e.g. no enough information to open db) + +//! errors related to data updating on the server +#define ERR_UPDATE_NULL_PKEY_FIELD 190 //!< null pkey field on updating +#define ERR_UPDATE_SERVER_ERROR 191 //!< error @ the server side during data updating +#define ERR_UPDATE_NO_MASTER_TABLE 192 //!< data could not be edited because there + //!< is no master table defined +#define ERR_UPDATE_NO_MASTER_TABLES_PKEY 193 //!< data could not be edited + //!< because it's master table has + //!< no primary key defined +#define ERR_UPDATE_NO_ENTIRE_MASTER_TABLES_PKEY 194 //!< data could not be edited + //!< because it does not contain entire + //!< master table's primary key + +//! errors related to data inserting on the server +#define ERR_INSERT_NULL_PKEY_FIELD 220 //!< null pkey field on updating +#define ERR_INSERT_SERVER_ERROR 221 //!< error @ the server side during data inserting +#define ERR_INSERT_NO_MASTER_TABLE 222 //!< data could not be inserted because there + //!< is no master table defined +#define ERR_INSERT_NO_MASTER_TABLES_PKEY 223 //!< data could not be inserted because master + //!< table has no primary key defined +#define ERR_INSERT_NO_ENTIRE_MASTER_TABLES_PKEY 224 //!< data could not be inserted + //!< because it does not contain entire + //!< master table's primary key + +//! errors related to data deleting on the server +#define ERR_DELETE_NULL_PKEY_FIELD 250 //!< null pkey field on updating +#define ERR_DELETE_SERVER_ERROR 251 //!< error @ the server side during data deleting +#define ERR_DELETE_NO_MASTER_TABLE 252 //!< data could not be deleted because there + //!< is no master table defined +#define ERR_DELETE_NO_MASTER_TABLES_PKEY 253 //!< data could not be deleted because master + //!< table has no primary key defined +#define ERR_DELETE_NO_ENTIRE_MASTER_TABLES_PKEY 254 //!< data could not be deleted + //!< because it does not contain entire + //!< master table's primary key + +//! errors related to queries +#define ERR_SQL_EXECUTION_ERROR 260 //!< general server error for sql statement execution + //!< usually returned by Connection::executeSQL() +#define ERR_SQL_PARSE_ERROR 270 //!< Parse error coming from arser::parse() + +#define ERR_OTHER 0xffff //!< use this if you have not (yet?) the name for given error + + +namespace KexiDB { + +/*! This class contains a result information + for various data manipulation operations, like cell/row updating/inserting. */ +class KEXI_DB_EXPORT ResultInfo +{ + public: + ResultInfo() + { + success = true; + allowToDiscardChanges = false; + column = -1; + } + /*! Sets information to default values. */ + void clear() { + success = true; + allowToDiscardChanges = false; + column = -1; + msg = QString::null; + desc = QString::null; + } + bool success : 1; //!< result of the operation, true by default + bool allowToDiscardChanges : 1; //!< True if changes can be discarded, false by default + //!< If true, additional "Discard changes" messagebox + //!< button can be displayed. + QString msg, desc; //!< error message and detailed description, both empty by default + int column; //!< faulty column, -1 (the default) means: there is no faulty column +}; + +}//namespace + +#endif + diff --git a/kexi/kexidb/expression.cpp b/kexi/kexidb/expression.cpp new file mode 100644 index 00000000..49bb231a --- /dev/null +++ b/kexi/kexidb/expression.cpp @@ -0,0 +1,914 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2007 Jaroslaw Staniek <[email protected]> + + Based on nexp.cpp : Parser module of Python-like language + (C) 2001 Jaroslaw Staniek, MIMUW (www.mimuw.edu.pl) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "expression.h" +#include "utils.h" +#include "parser/sqlparser.h" +#include "parser/parser_p.h" + +#include <ctype.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <qdatetime.h> + +KEXI_DB_EXPORT QString KexiDB::exprClassName(int c) +{ + if (c==KexiDBExpr_Unary) + return "Unary"; + else if (c==KexiDBExpr_Arithm) + return "Arithm"; + else if (c==KexiDBExpr_Logical) + return "Logical"; + else if (c==KexiDBExpr_Relational) + return "Relational"; + else if (c==KexiDBExpr_SpecialBinary) + return "SpecialBinary"; + else if (c==KexiDBExpr_Const) + return "Const"; + else if (c==KexiDBExpr_Variable) + return "Variable"; + else if (c==KexiDBExpr_Function) + return "Function"; + else if (c==KexiDBExpr_Aggregation) + return "Aggregation"; + else if (c==KexiDBExpr_TableList) + return "TableList"; + else if (c==KexiDBExpr_QueryParameter) + return "QueryParameter"; + + return "Unknown"; +} + +using namespace KexiDB; + +//========================================= + +BaseExpr::BaseExpr(int token) + : m_cl(KexiDBExpr_Unknown) + , m_par(0) + , m_token(token) +{ +} + +BaseExpr::~BaseExpr() +{ +} + +Field::Type BaseExpr::type() +{ + return Field::InvalidType; +} + +QString BaseExpr::debugString() +{ + return QString("BaseExpr(%1,type=%1)").arg(m_token).arg(Driver::defaultSQLTypeName(type())); +} + +bool BaseExpr::validate(ParseInfo& /*parseInfo*/) +{ + return true; +} + +extern const char * const tname(int offset); +#define safe_tname(token) ((token>=255 && token<=__LAST_TOKEN) ? tname(token-255) : "") + +QString BaseExpr::tokenToDebugString(int token) +{ + if (token < 254) { + if (isprint(token)) + return QString(QChar(uchar(token))); + else + return QString::number(token); + } + return QString(safe_tname(token)); +} + +QString BaseExpr::tokenToString() +{ + if (m_token < 255 && isprint(m_token)) + return tokenToDebugString(); + return QString::null; +} + +NArgExpr* BaseExpr::toNArg() { return dynamic_cast<NArgExpr*>(this); } +UnaryExpr* BaseExpr::toUnary() { return dynamic_cast<UnaryExpr*>(this); } +BinaryExpr* BaseExpr::toBinary() { return dynamic_cast<BinaryExpr*>(this); } +ConstExpr* BaseExpr::toConst() { return dynamic_cast<ConstExpr*>(this); } +VariableExpr* BaseExpr::toVariable() { return dynamic_cast<VariableExpr*>(this); } +FunctionExpr* BaseExpr::toFunction() { return dynamic_cast<FunctionExpr*>(this); } +QueryParameterExpr* BaseExpr::toQueryParameter() { return dynamic_cast<QueryParameterExpr*>(this); } + +//========================================= + +NArgExpr::NArgExpr(int aClass, int token) + : BaseExpr(token) +{ + m_cl = aClass; + list.setAutoDelete(true); +} + +NArgExpr::NArgExpr(const NArgExpr& expr) + : BaseExpr(expr) +{ + foreach_list (BaseExpr::ListIterator, it, expr.list) + add( it.current()->copy() ); +} + +NArgExpr::~NArgExpr() +{ +} + +NArgExpr* NArgExpr::copy() const +{ + return new NArgExpr(*this); +} + +QString NArgExpr::debugString() +{ + QString s = QString("NArgExpr(") + + "class=" + exprClassName(m_cl); + for ( BaseExpr::ListIterator it(list); it.current(); ++it ) { + s+=", "; + s+=it.current()->debugString(); + } + s+=")"; + return s; +} + +QString NArgExpr::toString( QuerySchemaParameterValueListIterator* params ) +{ + QString s; + s.reserve(256); + foreach_list( BaseExpr::ListIterator, it, list) { + if (!s.isEmpty()) + s+=", "; + s+=it.current()->toString(params); + } + return s; +} + +void NArgExpr::getQueryParameters(QuerySchemaParameterList& params) +{ + foreach_list( BaseExpr::ListIterator, it, list) + it.current()->getQueryParameters(params); +} + +BaseExpr* NArgExpr::arg(int nr) +{ + return list.at(nr); +} + +void NArgExpr::add(BaseExpr *expr) +{ + list.append(expr); + expr->setParent(this); +} + +void NArgExpr::prepend(BaseExpr *expr) +{ + list.prepend(expr); + expr->setParent(this); +} + +int NArgExpr::args() +{ + return list.count(); +} + +bool NArgExpr::validate(ParseInfo& parseInfo) +{ + if (!BaseExpr::validate(parseInfo)) + return false; + + foreach_list(BaseExpr::ListIterator, it, list) { + if (!it.current()->validate(parseInfo)) + return false; + } + return true; +} + +//========================================= +UnaryExpr::UnaryExpr(int token, BaseExpr *arg) + : BaseExpr(token) + , m_arg(arg) +{ + m_cl = KexiDBExpr_Unary; + if (m_arg) + m_arg->setParent(this); +} + +UnaryExpr::UnaryExpr(const UnaryExpr& expr) + : BaseExpr(expr) + , m_arg( expr.m_arg ? expr.m_arg->copy() : 0 ) +{ + if (m_arg) + m_arg->setParent(this); +} + +UnaryExpr::~UnaryExpr() +{ + delete m_arg; +} + +UnaryExpr* UnaryExpr::copy() const +{ + return new UnaryExpr(*this); +} + +QString UnaryExpr::debugString() +{ + return "UnaryExpr('" + + tokenToDebugString() + "', " + + (m_arg ? m_arg->debugString() : QString("<NONE>")) + + QString(",type=%1)").arg(Driver::defaultSQLTypeName(type())); +} + +QString UnaryExpr::toString(QuerySchemaParameterValueListIterator* params) +{ + if (m_token=='(') //parentheses (special case) + return "(" + (m_arg ? m_arg->toString(params) : "<NULL>") + ")"; + if (m_token < 255 && isprint(m_token)) + return tokenToDebugString() + (m_arg ? m_arg->toString(params) : "<NULL>"); + if (m_token==NOT) + return "NOT " + (m_arg ? m_arg->toString(params) : "<NULL>"); + if (m_token==SQL_IS_NULL) + return (m_arg ? m_arg->toString(params) : "<NULL>") + " IS NULL"; + if (m_token==SQL_IS_NOT_NULL) + return (m_arg ? m_arg->toString(params) : "<NULL>") + " IS NOT NULL"; + return QString("{INVALID_OPERATOR#%1} ").arg(m_token) + (m_arg ? m_arg->toString(params) : "<NULL>"); +} + +void UnaryExpr::getQueryParameters(QuerySchemaParameterList& params) +{ + if (m_arg) + m_arg->getQueryParameters(params); +} + +Field::Type UnaryExpr::type() +{ + //NULL IS NOT NULL : BOOLEAN + //NULL IS NULL : BOOLEAN + switch (m_token) { + case SQL_IS_NULL: + case SQL_IS_NOT_NULL: + return Field::Boolean; + } + const Field::Type t = m_arg->type(); + if (t==Field::Null) + return Field::Null; + if (m_token==NOT) + return Field::Boolean; + + return t; +} + +bool UnaryExpr::validate(ParseInfo& parseInfo) +{ + if (!BaseExpr::validate(parseInfo)) + return false; + + if (!m_arg->validate(parseInfo)) + return false; + +//! @todo compare types... e.g. NOT applied to Text makes no sense... + + // update type + if (m_arg->toQueryParameter()) { + m_arg->toQueryParameter()->setType(type()); + } + + return true; +#if 0 + BaseExpr *n = l.at(0); + + n->check(); +/*typ wyniku: + const bool dla "NOT <bool>" (negacja) + int dla "# <str>" (dlugosc stringu) + int dla "+/- <int>" + */ + if (is(NOT) && n->nodeTypeIs(TYP_BOOL)) { + node_type=new NConstType(TYP_BOOL); + } + else if (is('#') && n->nodeTypeIs(TYP_STR)) { + node_type=new NConstType(TYP_INT); + } + else if ((is('+') || is('-')) && n->nodeTypeIs(TYP_INT)) { + node_type=new NConstType(TYP_INT); + } + else { + ERR("Niepoprawny argument typu '%s' dla operatora '%s'", + n->nodeTypeName(),is(NOT)?QString("not"):QChar(typ())); + } +#endif +} + +//========================================= +BinaryExpr::BinaryExpr(int aClass, BaseExpr *left_expr, int token, BaseExpr *right_expr) + : BaseExpr(token) + , m_larg(left_expr) + , m_rarg(right_expr) +{ + m_cl = aClass; + if (m_larg) + m_larg->setParent(this); + if (m_rarg) + m_rarg->setParent(this); +} + +BinaryExpr::BinaryExpr(const BinaryExpr& expr) + : BaseExpr(expr) + , m_larg( expr.m_larg ? expr.m_larg->copy() : 0 ) + , m_rarg( expr.m_rarg ? expr.m_rarg->copy() : 0 ) +{ +} + +BinaryExpr::~BinaryExpr() +{ + delete m_larg; + delete m_rarg; +} + +BinaryExpr* BinaryExpr::copy() const +{ + return new BinaryExpr(*this); +} + +bool BinaryExpr::validate(ParseInfo& parseInfo) +{ + if (!BaseExpr::validate(parseInfo)) + return false; + + if (!m_larg->validate(parseInfo)) + return false; + if (!m_rarg->validate(parseInfo)) + return false; + +//! @todo compare types..., BITWISE_SHIFT_RIGHT requires integers, etc... + + //update type for query parameters + QueryParameterExpr * queryParameter = m_larg->toQueryParameter(); + if (queryParameter) + queryParameter->setType(m_rarg->type()); + queryParameter = m_rarg->toQueryParameter(); + if (queryParameter) + queryParameter->setType(m_larg->type()); + + return true; +} + +Field::Type BinaryExpr::type() +{ + const Field::Type lt = m_larg->type(), rt = m_rarg->type(); + if (lt==Field::InvalidType || rt == Field::InvalidType) + return Field::InvalidType; + if (lt==Field::Null || rt == Field::Null) { + if (m_token!=OR) //note that NULL OR something != NULL + return Field::Null; + } + + switch (m_token) { + case BITWISE_SHIFT_RIGHT: + case BITWISE_SHIFT_LEFT: + case CONCATENATION: + return lt; + } + + const bool ltInt = Field::isIntegerType(lt); + const bool rtInt = Field::isIntegerType(rt); + if (ltInt && rtInt) + return KexiDB::maximumForIntegerTypes(lt, rt); + + if (Field::isFPNumericType(lt) && rtInt) + return lt; + if (Field::isFPNumericType(rt) && ltInt) + return rt; + if ((lt==Field::Double || lt==Field::Float) && rtInt) + return lt; + if ((rt==Field::Double || rt==Field::Float) && ltInt) + return rt; + + return Field::Boolean; +} + +QString BinaryExpr::debugString() +{ + return QString("BinaryExpr(") + + "class=" + exprClassName(m_cl) + + "," + (m_larg ? m_larg->debugString() : QString("<NONE>")) + + ",'" + tokenToDebugString() + "'," + + (m_rarg ? m_rarg->debugString() : QString("<NONE>")) + + QString(",type=%1)").arg(Driver::defaultSQLTypeName(type())); +} + +QString BinaryExpr::tokenToString() +{ + if (m_token < 255 && isprint(m_token)) + return tokenToDebugString(); + // other arithmetic operations: << >> + switch (m_token) { + case BITWISE_SHIFT_RIGHT: return ">>"; + case BITWISE_SHIFT_LEFT: return "<<"; + // other relational operations: <= >= <> (or !=) LIKE IN + case NOT_EQUAL: return "<>"; + case NOT_EQUAL2: return "!="; + case LESS_OR_EQUAL: return "<="; + case GREATER_OR_EQUAL: return ">="; + case LIKE: return "LIKE"; + case SQL_IN: return "IN"; + // other logical operations: OR (or ||) AND (or &&) XOR + case SIMILAR_TO: return "SIMILAR TO"; + case NOT_SIMILAR_TO: return "NOT SIMILAR TO"; + case OR: return "OR"; + case AND: return "AND"; + case XOR: return "XOR"; + // other string operations: || (as CONCATENATION) + case CONCATENATION: return "||"; + // SpecialBinary "pseudo operators": + /* not handled here */ + default:; + } + return QString("{INVALID_BINARY_OPERATOR#%1} ").arg(m_token); +} + +QString BinaryExpr::toString(QuerySchemaParameterValueListIterator* params) +{ +#define INFIX(a) \ + (m_larg ? m_larg->toString(params) : "<NULL>") + " " + a + " " + (m_rarg ? m_rarg->toString(params) : "<NULL>") + return INFIX(tokenToString()); +} + +void BinaryExpr::getQueryParameters(QuerySchemaParameterList& params) +{ + if (m_larg) + m_larg->getQueryParameters(params); + if (m_rarg) + m_rarg->getQueryParameters(params); +} + +//========================================= +ConstExpr::ConstExpr( int token, const QVariant& val) +: BaseExpr( token ) +, value(val) +{ + m_cl = KexiDBExpr_Const; +} + +ConstExpr::ConstExpr(const ConstExpr& expr) + : BaseExpr(expr) + , value(expr.value) +{ +} + +ConstExpr::~ConstExpr() +{ +} + +ConstExpr* ConstExpr::copy() const +{ + return new ConstExpr(*this); +} + +Field::Type ConstExpr::type() +{ + if (m_token==SQL_NULL) + return Field::Null; + else if (m_token==INTEGER_CONST) { +//TODO ok? +//TODO: add sign info? + if (value.type() == QVariant::Int || value.type() == QVariant::UInt) { + Q_LLONG v = value.toInt(); + if (v <= 0xff && v > -0x80) + return Field::Byte; + if (v <= 0xffff && v > -0x8000) + return Field::ShortInteger; + return Field::Integer; + } + return Field::BigInteger; + } + else if (m_token==CHARACTER_STRING_LITERAL) { +//TODO: Field::defaultTextLength() is hardcoded now! + if (value.toString().length() > Field::defaultTextLength()) + return Field::LongText; + else + return Field::Text; + } + else if (m_token==REAL_CONST) + return Field::Double; + else if (m_token==DATE_CONST) + return Field::Date; + else if (m_token==DATETIME_CONST) + return Field::DateTime; + else if (m_token==TIME_CONST) + return Field::Time; + + return Field::InvalidType; +} + +QString ConstExpr::debugString() +{ + return QString("ConstExpr('") + tokenToDebugString() +"'," + toString() + + QString(",type=%1)").arg(Driver::defaultSQLTypeName(type())); +} + +QString ConstExpr::toString(QuerySchemaParameterValueListIterator* params) +{ + Q_UNUSED(params); + if (m_token==SQL_NULL) + return "NULL"; + else if (m_token==CHARACTER_STRING_LITERAL) +//TODO: better escaping! + return "'" + value.toString() + "'"; + else if (m_token==REAL_CONST) + return QString::number(value.toPoint().x())+"."+QString::number(value.toPoint().y()); + else if (m_token==DATE_CONST) + return "'" + value.toDate().toString(Qt::ISODate) + "'"; + else if (m_token==DATETIME_CONST) + return "'" + value.toDateTime().date().toString(Qt::ISODate) + + " " + value.toDateTime().time().toString(Qt::ISODate) + "'"; + else if (m_token==TIME_CONST) + return "'" + value.toTime().toString(Qt::ISODate) + "'"; + + return value.toString(); +} + +void ConstExpr::getQueryParameters(QuerySchemaParameterList& params) +{ + Q_UNUSED(params); +} + +bool ConstExpr::validate(ParseInfo& parseInfo) +{ + if (!BaseExpr::validate(parseInfo)) + return false; + + return type()!=Field::InvalidType; +} + +//========================================= +QueryParameterExpr::QueryParameterExpr(const QString& message) +: ConstExpr( QUERY_PARAMETER, message ) +, m_type(Field::Text) +{ + m_cl = KexiDBExpr_QueryParameter; +} + +QueryParameterExpr::QueryParameterExpr(const QueryParameterExpr& expr) + : ConstExpr(expr) + , m_type(expr.m_type) +{ +} + +QueryParameterExpr::~QueryParameterExpr() +{ +} + +QueryParameterExpr* QueryParameterExpr::copy() const +{ + return new QueryParameterExpr(*this); +} + +Field::Type QueryParameterExpr::type() +{ + return m_type; +} + +void QueryParameterExpr::setType(Field::Type type) +{ + m_type = type; +} + +QString QueryParameterExpr::debugString() +{ + return QString("QueryParameterExpr('") + QString::fromLatin1("[%2]").arg(value.toString()) + + QString("',type=%1)").arg(Driver::defaultSQLTypeName(type())); +} + +QString QueryParameterExpr::toString(QuerySchemaParameterValueListIterator* params) +{ + return params ? params->getPreviousValueAsString(type()) : QString::fromLatin1("[%2]").arg(value.toString()); +} + +void QueryParameterExpr::getQueryParameters(QuerySchemaParameterList& params) +{ + QuerySchemaParameter param; + param.message = value.toString(); + param.type = type(); + params.append( param ); +} + +bool QueryParameterExpr::validate(ParseInfo& parseInfo) +{ + Q_UNUSED(parseInfo); + return type()!=Field::InvalidType; +} + +//========================================= +VariableExpr::VariableExpr(const QString& _name) +: BaseExpr( 0/*undefined*/ ) +, name(_name) +, field(0) +, tablePositionForField(-1) +, tableForQueryAsterisk(0) +{ + m_cl = KexiDBExpr_Variable; +} + +VariableExpr::VariableExpr(const VariableExpr& expr) + : BaseExpr(expr) + , name(expr.name) + , field(expr.field) + , tablePositionForField(expr.tablePositionForField) + , tableForQueryAsterisk(expr.tableForQueryAsterisk) +{ +} + +VariableExpr::~VariableExpr() +{ +} + +VariableExpr* VariableExpr::copy() const +{ + return new VariableExpr(*this); +} + +QString VariableExpr::debugString() +{ + return QString("VariableExpr(") + name + + QString(",type=%1)").arg(field ? Driver::defaultSQLTypeName(type()) : QString("FIELD NOT DEFINED YET")); +} + +QString VariableExpr::toString(QuerySchemaParameterValueListIterator* params) +{ + Q_UNUSED(params); + return name; +} + +void VariableExpr::getQueryParameters(QuerySchemaParameterList& params) +{ + Q_UNUSED(params); +} + +//! We're assuming it's called after VariableExpr::validate() +Field::Type VariableExpr::type() +{ + if (field) + return field->type(); + + //BTW, asterisks are not stored in VariableExpr outside of parser, so ok. + return Field::InvalidType; +} + +#define IMPL_ERROR(errmsg) parseInfo.errMsg = "Implementation error"; parseInfo.errDescr = errmsg + +bool VariableExpr::validate(ParseInfo& parseInfo) +{ + if (!BaseExpr::validate(parseInfo)) + return false; + field = 0; + tablePositionForField = -1; + tableForQueryAsterisk = 0; + +/* taken from parser's addColumn(): */ + KexiDBDbg << "checking variable name: " << name << endl; + int dotPos = name.find('.'); + QString tableName, fieldName; +//TODO: shall we also support db name? + if (dotPos>0) { + tableName = name.left(dotPos); + fieldName = name.mid(dotPos+1); + } + if (tableName.isEmpty()) {//fieldname only + fieldName = name; + if (fieldName=="*") { +// querySchema->addAsterisk( new QueryAsterisk(querySchema) ); + return true; + } + + //find first table that has this field + Field *firstField = 0; + foreach_list(TableSchema::ListIterator, it, *parseInfo.querySchema->tables()) { + Field *f = it.current()->field(fieldName); + if (f) { + if (!firstField) { + firstField = f; + } + else if (f->table()!=firstField->table()) { + //ambiguous field name + parseInfo.errMsg = i18n("Ambiguous field name"); + parseInfo.errDescr = i18n("Both table \"%1\" and \"%2\" have defined \"%3\" field. " + "Use \"<tableName>.%4\" notation to specify table name.") + .arg(firstField->table()->name()).arg(f->table()->name()) + .arg(fieldName).arg(fieldName); + return false; + } + } + } + if (!firstField) { + parseInfo.errMsg = i18n("Field not found"); + parseInfo.errDescr = i18n("Table containing \"%1\" field not found").arg(fieldName); + return false; + } + //ok + field = firstField; //store +// querySchema->addField(firstField); + return true; + } + + //table.fieldname or tableAlias.fieldname + tableName = tableName.lower(); + TableSchema *ts = parseInfo.querySchema->table( tableName ); + if (ts) {//table.fieldname + //check if "table" is covered by an alias + const QValueList<int> tPositions = parseInfo.querySchema->tablePositions(tableName); + QValueList<int>::ConstIterator it = tPositions.constBegin(); + QCString tableAlias; + bool covered = true; + for (; it!=tPositions.constEnd() && covered; ++it) { + tableAlias = parseInfo.querySchema->tableAlias(*it); + if (tableAlias.isEmpty() || tableAlias.lower()==tableName.latin1()) + covered = false; //uncovered + KexiDBDbg << " --" << "covered by " << tableAlias << " alias" << endl; + } + if (covered) { + parseInfo.errMsg = i18n("Could not access the table directly using its name"); + parseInfo.errDescr = i18n("Table \"%1\" is covered by aliases. Instead of \"%2\", " + "you can write \"%3\"").arg(tableName) + .arg(tableName+"."+fieldName).arg(tableAlias+"."+fieldName.latin1()); + return false; + } + } + + int tablePosition = -1; + if (!ts) {//try to find tableAlias + tablePosition = parseInfo.querySchema->tablePositionForAlias( tableName.latin1() ); + if (tablePosition>=0) { + ts = parseInfo.querySchema->tables()->at(tablePosition); + if (ts) { +// KexiDBDbg << " --it's a tableAlias.name" << endl; + } + } + } + + if (!ts) { + parseInfo.errMsg = i18n("Table not found"); + parseInfo.errDescr = i18n("Unknown table \"%1\"").arg(tableName); + return false; + } + + QValueList<int> *positionsList = parseInfo.repeatedTablesAndAliases[ tableName ]; + if (!positionsList) { //for sanity + IMPL_ERROR(tableName + "." + fieldName + ", !positionsList "); + return false; + } + + //it's a table.* + if (fieldName=="*") { + if (positionsList->count()>1) { + parseInfo.errMsg = i18n("Ambiguous \"%1.*\" expression").arg(tableName); + parseInfo.errDescr = i18n("More than one \"%1\" table or alias defined").arg(tableName); + return false; + } + tableForQueryAsterisk = ts; +// querySchema->addAsterisk( new QueryAsterisk(querySchema, ts) ); + return true; + } + +// KexiDBDbg << " --it's a table.name" << endl; + Field *realField = ts->field(fieldName); + if (!realField) { + parseInfo.errMsg = i18n("Field not found"); + parseInfo.errDescr = i18n("Table \"%1\" has no \"%2\" field") + .arg(tableName).arg(fieldName); + return false; + } + + // check if table or alias is used twice and both have the same column + // (so the column is ambiguous) + int numberOfTheSameFields = 0; + for (QValueList<int>::iterator it = positionsList->begin(); + it!=positionsList->end();++it) + { + TableSchema *otherTS = parseInfo.querySchema->tables()->at(*it); + if (otherTS->field(fieldName)) + numberOfTheSameFields++; + if (numberOfTheSameFields>1) { + parseInfo.errMsg = i18n("Ambiguous \"%1.%2\" expression") + .arg(tableName).arg(fieldName); + parseInfo.errDescr = i18n("More than one \"%1\" table or alias defined containing \"%2\" field") + .arg(tableName).arg(fieldName); + return false; + } + } + field = realField; //store + tablePositionForField = tablePosition; +// querySchema->addField(realField, tablePosition); + + return true; +} + +//========================================= +static QValueList<QCString> FunctionExpr_builtIns; +static const char* FunctionExpr_builtIns_[] = +{"SUM", "MIN", "MAX", "AVG", "COUNT", "STD", "STDDEV", "VARIANCE", 0 }; + +QValueList<QCString> FunctionExpr::builtInAggregates() +{ + if (FunctionExpr_builtIns.isEmpty()) { + for (const char **p = FunctionExpr_builtIns_; *p; p++) + FunctionExpr_builtIns << *p; + } + return FunctionExpr_builtIns; +} + +FunctionExpr::FunctionExpr( const QString& _name, NArgExpr* args_ ) + : BaseExpr( 0/*undefined*/ ) + , name(_name) + , args(args_) +{ + if (isBuiltInAggregate(name.latin1())) + m_cl = KexiDBExpr_Aggregation; + else + m_cl = KexiDBExpr_Function; + if (args) + args->setParent( this ); +} + +FunctionExpr::FunctionExpr( const FunctionExpr& expr ) + : BaseExpr( 0/*undefined*/ ) + , name(expr.name) + , args(expr.args ? args->copy() : 0) +{ + if (args) + args->setParent( this ); +} + +FunctionExpr::~FunctionExpr() +{ + delete args; +} + +FunctionExpr* FunctionExpr::copy() const +{ + return new FunctionExpr(*this); +} + +QString FunctionExpr::debugString() +{ + QString res; + res.append( QString("FunctionExpr(") + name ); + if (args) + res.append(QString(",") + args->debugString()); + res.append(QString(",type=%1)").arg(Driver::defaultSQLTypeName(type()))); + return res; +} + +QString FunctionExpr::toString(QuerySchemaParameterValueListIterator* params) +{ + return name + "(" + (args ? args->toString(params) : QString::null) + ")"; +} + +void FunctionExpr::getQueryParameters(QuerySchemaParameterList& params) +{ + args->getQueryParameters(params); +} + +Field::Type FunctionExpr::type() +{ + //TODO + return Field::InvalidType; +} + +bool FunctionExpr::validate(ParseInfo& parseInfo) +{ + if (!BaseExpr::validate(parseInfo)) + return false; + + return args ? args->validate(parseInfo) : true; +} + +bool FunctionExpr::isBuiltInAggregate(const QCString& fname) +{ + return builtInAggregates().find(fname.upper())!=FunctionExpr_builtIns.end(); +} diff --git a/kexi/kexidb/expression.h b/kexi/kexidb/expression.h new file mode 100644 index 00000000..6ee98f32 --- /dev/null +++ b/kexi/kexidb/expression.h @@ -0,0 +1,311 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2007 Jaroslaw Staniek <[email protected]> + + Design based on nexp.h : Parser module of Python-like language + (C) 2001 Jaroslaw Staniek, MIMUW (www.mimuw.edu.pl) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_EXPRESSION_H +#define KEXIDB_EXPRESSION_H + +#include "field.h" +#include "queryschema.h" + +#include <kdebug.h> +#include "global.h" + +namespace KexiDB { + +//! classes +#define KexiDBExpr_Unknown 0 +#define KexiDBExpr_Unary 1 +#define KexiDBExpr_Arithm 2 +#define KexiDBExpr_Logical 3 +#define KexiDBExpr_Relational 4 +#define KexiDBExpr_SpecialBinary 5 +#define KexiDBExpr_Const 6 +#define KexiDBExpr_Variable 7 +#define KexiDBExpr_Function 8 +#define KexiDBExpr_Aggregation 9 +#define KexiDBExpr_TableList 10 +#define KexiDBExpr_QueryParameter 11 + +//! Custom tokens are not used in parser but used as extension in expression classes. +//#define KEXIDB_CUSTOM_TOKEN 0x1000 + +//! \return class name of class \a c +KEXI_DB_EXPORT QString exprClassName(int c); + +class ParseInfo; +class NArgExpr; +class UnaryExpr; +class BinaryExpr; +class ConstExpr; +class VariableExpr; +class FunctionExpr; +class QueryParameterExpr; +class QuerySchemaParameterValueListIterator; +//class QuerySchemaParameterList; + +//! A base class for all expressions +class KEXI_DB_EXPORT BaseExpr +{ +public: + typedef QPtrList<BaseExpr> List; + typedef QPtrListIterator<BaseExpr> ListIterator; + + BaseExpr(int token); + virtual ~BaseExpr(); + + //! \return a deep copy of this object. +//! @todo a nonpointer will be returned here when we move to implicit data sharing + virtual BaseExpr* copy() const = 0; + + int token() const { return m_token; } + + virtual Field::Type type(); + + BaseExpr* parent() const { return m_par; } + + virtual void setParent(BaseExpr *p) { m_par = p; } + + virtual bool validate(ParseInfo& parseInfo); + + /*! \return string as a representation of this expression element by running recursive calls. + \a param, if not 0, points to a list item containing value of a query parameter + (used in QueryParameterExpr). */ + virtual QString toString(QuerySchemaParameterValueListIterator* params = 0) = 0; + + /*! Collects query parameters (messages and types) reculsively and saves them to params. + The leaf nodes are objects of QueryParameterExpr class. */ + virtual void getQueryParameters(QuerySchemaParameterList& params) = 0; + + inline void debug() { KexiDBDbg << debugString() << endl; } + + virtual QString debugString(); + + /*! \return single character if the token is < 256 + or token name, e.g. LESS_OR_EQUAL (for debugging). */ + inline QString tokenToDebugString() { return tokenToDebugString(m_token); } + + static QString tokenToDebugString(int token); + + /*! \return string for token, like "<=" or ">" */ + virtual QString tokenToString(); + + int exprClass() const { return m_cl; } + + /*! Convenience type casts. */ + NArgExpr* toNArg(); + UnaryExpr* toUnary(); + BinaryExpr* toBinary(); + ConstExpr* toConst(); + VariableExpr* toVariable(); + FunctionExpr* toFunction(); + QueryParameterExpr* toQueryParameter(); + +protected: + int m_cl; //!< class + BaseExpr *m_par; //!< parent expression + int m_token; +}; + +//! A base class N-argument operation +class KEXI_DB_EXPORT NArgExpr : public BaseExpr +{ +public: + NArgExpr(int aClass, int token); + NArgExpr(const NArgExpr& expr); + virtual ~NArgExpr(); + //! \return a deep copy of this object. + virtual NArgExpr* copy() const; + void add(BaseExpr *expr); + void prepend(BaseExpr *expr); + BaseExpr *arg(int n); + int args(); + virtual QString debugString(); + virtual QString toString(QuerySchemaParameterValueListIterator* params = 0); + virtual void getQueryParameters(QuerySchemaParameterList& params); + virtual bool validate(ParseInfo& parseInfo); + BaseExpr::List list; +}; + +//! An unary argument operation: + - NOT (or !) ~ "IS NULL" "IS NOT NULL" +class KEXI_DB_EXPORT UnaryExpr : public BaseExpr +{ +public: + UnaryExpr(int token, BaseExpr *arg); + UnaryExpr(const UnaryExpr& expr); + virtual ~UnaryExpr(); + //! \return a deep copy of this object. + virtual UnaryExpr* copy() const; + virtual Field::Type type(); + virtual QString debugString(); + virtual QString toString(QuerySchemaParameterValueListIterator* params = 0); + virtual void getQueryParameters(QuerySchemaParameterList& params); + BaseExpr *arg() const { return m_arg; } + virtual bool validate(ParseInfo& parseInfo); + + BaseExpr *m_arg; +}; + +/*! A base class for binary operation + - arithmetic operations: + - / * % << >> & | || + - relational operations: = (or ==) < > <= >= <> (or !=) LIKE IN 'SIMILAR TO' 'NOT SIMILAR TO' + - logical operations: OR (or ||) AND (or &&) XOR + - SpecialBinary "pseudo operators": + * e.g. "f1 f2" : token == 0 + * e.g. "f1 AS f2" : token == AS +*/ +class KEXI_DB_EXPORT BinaryExpr : public BaseExpr +{ +public: + BinaryExpr(int aClass, BaseExpr *left_expr, int token, BaseExpr *right_expr); + BinaryExpr(const BinaryExpr& expr); + virtual ~BinaryExpr(); + //! \return a deep copy of this object. + virtual BinaryExpr* copy() const; + virtual Field::Type type(); + virtual QString debugString(); + virtual QString toString(QuerySchemaParameterValueListIterator* params = 0); + virtual void getQueryParameters(QuerySchemaParameterList& params); + BaseExpr *left() const { return m_larg; } + BaseExpr *right() const { return m_rarg; } + virtual bool validate(ParseInfo& parseInfo); + virtual QString tokenToString(); + + BaseExpr *m_larg; + BaseExpr *m_rarg; +}; + +/*! String, integer, float constants also includes NULL value. + token can be: IDENTIFIER, SQL_NULL, CHARACTER_STRING_LITERAL, + INTEGER_CONST, REAL_CONST */ +class KEXI_DB_EXPORT ConstExpr : public BaseExpr +{ +public: + ConstExpr(int token, const QVariant& val); + ConstExpr(const ConstExpr& expr); + virtual ~ConstExpr(); + //! \return a deep copy of this object. + virtual ConstExpr* copy() const; + virtual Field::Type type(); + virtual QString debugString(); + virtual QString toString(QuerySchemaParameterValueListIterator* params = 0); + virtual void getQueryParameters(QuerySchemaParameterList& params); + virtual bool validate(ParseInfo& parseInfo); + QVariant value; +}; + +//! Query parameter used to getting user input of constant values. +//! It contains a message that is displayed to the user. +class KEXI_DB_EXPORT QueryParameterExpr : public ConstExpr +{ +public: + QueryParameterExpr(const QString& message); + QueryParameterExpr(const QueryParameterExpr& expr); + virtual ~QueryParameterExpr(); + //! \return a deep copy of this object. + virtual QueryParameterExpr* copy() const; + virtual Field::Type type(); + /*! Sets expected type of the parameter. The default is String. + This method is called from parent's expression validate(). + This depends on the type of the related expression. + For instance: query "SELECT * FROM cars WHERE name=[enter name]", + "[enter name]" has parameter of the same type as "name" field. + "=" binary expression's validate() will be called for the left side + of the expression and then the right side will have type set to String. + */ + void setType(Field::Type type); + virtual QString debugString(); + virtual QString toString(QuerySchemaParameterValueListIterator* params = 0); + virtual void getQueryParameters(QuerySchemaParameterList& params); + virtual bool validate(ParseInfo& parseInfo); +protected: + Field::Type m_type; +}; + +//! Variables like <i>fieldname</i> or <i>tablename</i>.<i>fieldname</i> +class KEXI_DB_EXPORT VariableExpr : public BaseExpr +{ +public: + VariableExpr(const QString& _name); + VariableExpr(const VariableExpr& expr); + virtual ~VariableExpr(); + //! \return a deep copy of this object. + virtual VariableExpr* copy() const; + virtual Field::Type type(); + virtual QString debugString(); + virtual QString toString(QuerySchemaParameterValueListIterator* params = 0); + virtual void getQueryParameters(QuerySchemaParameterList& params); + + /*! Validation. Sets field, tablePositionForField + and tableForQueryAsterisk members. + See addColumn() in parse.y to see how it's used on column adding. */ + virtual bool validate(ParseInfo& parseInfo); + + /*! Verbatim name as returned by scanner. */ + QString name; + + /* NULL by default. After successful validate() it will point to a field, + if the variable is of a form "tablename.fieldname" or "fieldname", + otherwise (eg. for asterisks) -still NULL. + Only meaningful for column expressions within a query. */ + Field *field; + + /* -1 by default. After successful validate() it will contain a position of a table + within query that needs to be bound to the field. + This value can be either be -1 if no binding is needed. + This value is used in the Parser to call + QuerySchema::addField(Field* field, int bindToTable); + Only meaningful for column expressions within a query. */ + int tablePositionForField; + + /*! NULL by default. After successful validate() it will point to a table + that is referenced by asterisk, i.e. "*.tablename". + This is set to NULL if this variable is not an asterisk of that form. */ + TableSchema *tableForQueryAsterisk; +}; + +//! - aggregation functions like SUM, COUNT, MAX, ... +//! - builtin functions like CURRENT_TIME() +//! - user defined functions +class KEXI_DB_EXPORT FunctionExpr : public BaseExpr +{ +public: + FunctionExpr(const QString& _name, NArgExpr* args_ = 0); + FunctionExpr(const FunctionExpr& expr); + virtual ~FunctionExpr(); + //! \return a deep copy of this object. + virtual FunctionExpr* copy() const; + virtual Field::Type type(); + virtual QString debugString(); + virtual QString toString(QuerySchemaParameterValueListIterator* params = 0); + virtual void getQueryParameters(QuerySchemaParameterList& params); + virtual bool validate(ParseInfo& parseInfo); + + static QValueList<QCString> builtInAggregates(); + static bool isBuiltInAggregate(const QCString& fname); + + QString name; + NArgExpr* args; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/field.cpp b/kexi/kexidb/field.cpp new file mode 100644 index 00000000..88233272 --- /dev/null +++ b/kexi/kexidb/field.cpp @@ -0,0 +1,726 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <[email protected]> + Copyright (C) 2002 Joseph Wenninger <[email protected]> + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "field.h" +#include "connection.h" +#include "driver.h" +#include "expression.h" +#include "utils.h" + +// we use here i18n() but this depends on kde libs: TODO: add #ifdefs +#include <kdebug.h> +#include <klocale.h> + +#include <qdatetime.h> + +#include <assert.h> + +using namespace KexiDB; + +Field::FieldTypeNames Field::m_typeNames; +Field::FieldTypeGroupNames Field::m_typeGroupNames; + +Field::Field() +{ + init(); + setConstraints(NoConstraints); +} + + +Field::Field(TableSchema *tableSchema) +{ + init(); + m_parent = tableSchema; + m_order = tableSchema->fieldCount(); + setConstraints(NoConstraints); +} + +Field::Field(QuerySchema *querySchema, BaseExpr* expr) +{ + init(); + m_parent = querySchema; + m_order = querySchema->fieldCount(); + setConstraints(NoConstraints); + if (expr) + setExpression(expr); +} + +Field::Field(const QString& name, Type ctype, + uint cconst, uint options, uint length, uint precision, + QVariant defaultValue, const QString& caption, const QString& description, + uint width) + : m_parent(0) + ,m_name(name.lower()) + ,m_length(length) + ,m_precision(precision) + ,m_visibleDecimalPlaces(-1) + ,m_options(options) + ,m_defaultValue(defaultValue) + ,m_order(-1) + ,m_caption(caption) + ,m_desc(description) + ,m_width(width) + ,m_expr(0) + ,m_customProperties(0) + ,m_type(ctype) +{ + setConstraints(cconst); + if (m_length==0) {//0 means default length: + if (m_type==Field::Text) + m_length = defaultTextLength(); + } +} + +/*! Copy constructor. */ +Field::Field(const Field& f) +{ + (*this) = f; + if (f.m_customProperties) + m_customProperties = new CustomPropertiesMap( f.customProperties() ); + + if (f.m_expr) {//deep copy the expression +//TODO m_expr = new BaseExpr(*f.m_expr); + +// m_expr->m_field = this; + } else + m_expr = 0; +} + +Field::~Field() +{ + delete m_expr; + delete m_customProperties; +} + +Field* Field::copy() const +{ + return new Field(*this); +} + +void Field::init() +{ + m_parent = 0; + m_name = ""; + m_type = InvalidType; + m_length = 0; + m_precision = 0; + m_visibleDecimalPlaces = -1; + m_options = NoOptions; + m_defaultValue = QVariant(QString::null); + m_order = -1; + m_width = 0; + m_expr = 0; + m_customProperties = 0; +} + +Field::Type Field::type() const +{ + if (m_expr) + return m_expr->type(); + return m_type; +} + +QVariant::Type Field::variantType(uint type) +{ + switch(type) + { + case Byte: + case ShortInteger: + case Integer: + case BigInteger: + return QVariant::Int; + case Boolean: + return QVariant::Bool; + case Date: + return QVariant::Date; + case DateTime: + return QVariant::DateTime; + case Time: + return QVariant::Time; + case Float: + case Double: + return QVariant::Double; + case Text: + case LongText: + return QVariant::String; + case BLOB: + return QVariant::ByteArray; + default: + return QVariant::Invalid; + } + + return QVariant::Invalid; +} + +QString Field::typeName(uint type) +{ + m_typeNames.init(); + return (type <= LastType) ? m_typeNames.at(type) : QString::number(type); +} + +QString Field::typeString(uint type) +{ + m_typeNames.init(); + return (type <= LastType) ? m_typeNames.at((int)LastType+1 + type) : QString("Type%1").arg(type); +} + +QString Field::typeGroupName(uint typeGroup) +{ + m_typeGroupNames.init(); + return (typeGroup <= LastTypeGroup) ? m_typeGroupNames.at(typeGroup) : typeGroupString(typeGroup); +} + +QString Field::typeGroupString(uint typeGroup) +{ + m_typeGroupNames.init(); + return (typeGroup <= LastTypeGroup) ? m_typeGroupNames.at((int)LastTypeGroup+1 + typeGroup) : QString("TypeGroup%1").arg(typeGroup); +} + +Field::Type Field::typeForString(const QString& typeString) +{ + m_typeNames.init(); + QMap<QString,Type>::ConstIterator it = m_typeNames.str2num.find(typeString.lower()); + if (it==m_typeNames.str2num.end()) + return InvalidType; + return it.data(); +} + +Field::TypeGroup Field::typeGroupForString(const QString& typeGroupString) +{ + m_typeGroupNames.init(); + QMap<QString,TypeGroup>::ConstIterator it = m_typeGroupNames.str2num.find(typeGroupString.lower()); + if (it==m_typeGroupNames.str2num.end()) + return InvalidGroup; + return it.data(); +} + +bool Field::isIntegerType( uint type ) +{ + switch (type) { + case Field::Byte: + case Field::ShortInteger: + case Field::Integer: + case Field::BigInteger: + return true; + default:; + } + return false; +} + +bool Field::isNumericType( uint type ) +{ + switch (type) { + case Field::Byte: + case Field::ShortInteger: + case Field::Integer: + case Field::BigInteger: + case Field::Float: + case Field::Double: + return true; + default:; + } + return false; +} + +bool Field::isFPNumericType( uint type ) +{ + return type==Field::Float || type==Field::Double; +} + +bool Field::isDateTimeType(uint type) +{ + switch (type) { + case Field::Date: + case Field::DateTime: + case Field::Time: + return true; + default:; + } + return false; +} + +bool Field::isTextType( uint type ) +{ + switch (type) { + case Field::Text: + case Field::LongText: + return true; + default:; + } + return false; +} + +bool Field::hasEmptyProperty(uint type) +{ + return Field::isTextType(type) || type==BLOB; +} + +bool Field::isAutoIncrementAllowed(uint type) +{ + return Field::isIntegerType(type); +} + +Field::TypeGroup Field::typeGroup(uint type) +{ + if (Field::isTextType(type)) + return TextGroup; + else if (Field::isIntegerType(type)) + return IntegerGroup; + else if (Field::isFPNumericType(type)) + return FloatGroup; + else if (type==Boolean) + return BooleanGroup; + else if (Field::isDateTimeType(type)) + return DateTimeGroup; + else if (type==BLOB) + return BLOBGroup; + + return InvalidGroup; //unknown +} + +TableSchema* +Field::table() const +{ + return dynamic_cast<TableSchema*>(m_parent); +} + +void +Field::setTable(TableSchema *tableSchema) +{ + m_parent = tableSchema; +} + +QuerySchema* +Field::query() const +{ + return dynamic_cast<QuerySchema*>(m_parent); +} + +void +Field::setQuery(QuerySchema *querySchema) +{ + m_parent = querySchema; +} + +void +Field::setName(const QString& n) +{ + m_name = n.lower(); +} + +void +Field::setType(Type t) +{ + if (m_expr) { + KexiDBWarn << QString("Field::setType(%1)").arg(t) + << " could not set type because the field has expression assigned!" << endl; + return; + } + m_type = t; +} + +void +Field::setConstraints(uint c) +{ + m_constraints = c; + //pkey must be unique notnull + if (isPrimaryKey()) { + setPrimaryKey(true); + } + if (isIndexed()) { + setIndexed(true); + } + if (isAutoIncrement() && !isAutoIncrementAllowed()) { + setAutoIncrement(false); + } +} + +void +Field::setLength(uint l) +{ + if (type()!=Field::Text) + return; + m_length = l; +} + +void +Field::setPrecision(uint p) +{ + if (!isFPNumericType()) + return; + m_precision = p; +} + +void +Field::setScale(uint s) +{ + if (!isFPNumericType()) + return; + m_length = s; +} + +void +Field::setVisibleDecimalPlaces(int p) +{ + if (!KexiDB::supportsVisibleDecimalPlacesProperty(type())) + return; + m_visibleDecimalPlaces = p < 0 ? -1 : p; +} + +void +Field::setUnsigned(bool u) +{ + m_options |= Unsigned; + m_options ^= (!u * Unsigned); +} + +void +Field::setDefaultValue(const QVariant& def) +{ + m_defaultValue = def; +} + +bool +Field::setDefaultValue(const QCString& def) +{ + if (def.isNull()) { + m_defaultValue = QVariant(); + return true; + } + + bool ok; + switch(type()) + { + case Byte: { + unsigned int v = def.toUInt(&ok); + if (!ok || v > 255) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant(v); + break; + }case ShortInteger: { + int v = def.toInt(&ok); + if (!ok || (!(m_options & Unsigned) && (v < -32768 || v > 32767)) || ((m_options & Unsigned) && (v < 0 || v > 65535))) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant(v); + break; + }case Integer: {//4 bytes + long v = def.toLong(&ok); +//js: FIXME if (!ok || (!(m_options & Unsigned) && (-v > 0x080000000 || v > (0x080000000-1))) || ((m_options & Unsigned) && (v < 0 || v > 0x100000000))) + if (!ok || (!(m_options & Unsigned) && (-v > (int)0x07FFFFFFF || v > (int)(0x080000000-1)))) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant((Q_LLONG)v); + break; + }case BigInteger: {//8 bytes +//! @todo BigInteger support +/* + Q_LLONG long v = def.toLongLong(&ok); +//TODO: 2-part decoding + if (!ok || (!(m_options & Unsigned) && (-v > 0x080000000 || v > (0x080000000-1)))) + m_defaultValue = QVariant(); + else + if (m_options & Unsigned) + m_defaultValue=QVariant((Q_ULLONG) v); + else + m_defaultValue = QVariant((Q_LLONG)v);*/ + break; + }case Boolean: { + unsigned short v = def.toUShort(&ok); + if (!ok || v > 1) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant((bool)v); + break; + }case Date: {//YYYY-MM-DD + QDate date = QDate::fromString( def, Qt::ISODate ); + if (!date.isValid()) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant(date); + break; + }case DateTime: {//YYYY-MM-DDTHH:MM:SS + QDateTime dt = QDateTime::fromString( def, Qt::ISODate ); + if (!dt.isValid()) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant(dt); + break; + }case Time: {//HH:MM:SS + QTime time = QTime::fromString( def, Qt::ISODate ); + if (!time.isValid()) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant(time); + break; + }case Float: { + float v = def.toFloat(&ok); + if (!ok || ((m_options & Unsigned) && (v < 0.0))) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant(v); + break; + }case Double: { + double v = def.toDouble(&ok); + if (!ok || ((m_options & Unsigned) && (v < 0.0))) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant(v); + break; + }case Text: { + if (def.isNull() || (def.length() > 255)) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant((QString)def); + break; + }case LongText: { + if (def.isNull()) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant((QString)def); + break; + }case BLOB: { +//TODO + if (def.isNull()) + m_defaultValue = QVariant(); + else + m_defaultValue = QVariant(def); + break; + }default: + m_defaultValue = QVariant(); + } + return m_defaultValue.isNull(); +} + +void +Field::setAutoIncrement(bool a) +{ + if (a && !isAutoIncrementAllowed()) + return; + if (isAutoIncrement() != a) + m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::AutoInc); +} + +void +Field::setPrimaryKey(bool p) +{ + if(isPrimaryKey() != p) + m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::PrimaryKey); + if (p) {//also set implied constraints + setUniqueKey(true); + setNotNull(true); + setNotEmpty(true); + setIndexed(true); + } + else { +//! \todo is this ok for all engines? + setAutoIncrement(false); + } +} + +void +Field::setUniqueKey(bool u) +{ + if(isUniqueKey() != u) { + m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::Unique); + if (u) + setNotNull(true); + } +} + +void +Field::setForeignKey(bool f) +{ + if (isForeignKey() != f) + m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::ForeignKey); +} + +void +Field::setNotNull(bool n) +{ + if (isNotNull() != n) + m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::NotNull); +} + +void Field::setNotEmpty(bool n) +{ + if (isNotEmpty() != n) + m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::NotEmpty); +} + +void Field::setIndexed(bool s) +{ + if (isIndexed() != s) + m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::Indexed); + if (!s) {//also set implied constraints + setPrimaryKey(false); + setUniqueKey(false); + setNotNull(false); + setNotEmpty(false); + } +} + + +QString Field::debugString() const +{ + KexiDB::Connection *conn = table() ? table()->connection() : 0; + QString dbg = (m_name.isEmpty() ? "<NONAME> " : m_name + " "); + if (m_options & Field::Unsigned) + dbg += " UNSIGNED "; + dbg += (conn && conn->driver()) ? conn->driver()->sqlTypeName(type()) : Driver::defaultSQLTypeName(type()); + if (isFPNumericType() && m_precision>0) { + if (scale()>0) + dbg += QString::fromLatin1("(%1,%2)").arg(m_precision).arg(scale()); + else + dbg += QString::fromLatin1("(%1)").arg(m_precision); + } + else if (m_type==Field::Text && m_length>0) + dbg += QString::fromLatin1("(%1)").arg(m_length); + if (m_constraints & Field::AutoInc) + dbg += " AUTOINC"; + if (m_constraints & Field::Unique) + dbg += " UNIQUE"; + if (m_constraints & Field::PrimaryKey) + dbg += " PKEY"; + if (m_constraints & Field::ForeignKey) + dbg += " FKEY"; + if (m_constraints & Field::NotNull) + dbg += " NOTNULL"; + if (m_constraints & Field::NotEmpty) + dbg += " NOTEMPTY"; + if (!m_defaultValue.isNull()) + dbg += QString(" DEFAULT=[%1]").arg(m_defaultValue.typeName()) + KexiDB::variantToString(m_defaultValue); + if (m_expr) + dbg += " EXPRESSION=" + m_expr->debugString(); + if (m_customProperties && !m_customProperties->isEmpty()) { + dbg += QString(" CUSTOM PROPERTIES (%1): ").arg(m_customProperties->count()); + bool first = true; + foreach (CustomPropertiesMap::ConstIterator, it, *m_customProperties) { + if (first) + first = false; + else + dbg += ", "; + dbg += QString("%1 = %2 (%3)").arg(it.key()).arg(it.data().toString()).arg(it.data().typeName()); + } + } + return dbg; +} + +void Field::debug() +{ + KexiDBDbg << debugString() << endl; +} + +void Field::setExpression(KexiDB::BaseExpr *expr) +{ + assert(!m_parent || dynamic_cast<QuerySchema*>(m_parent)); + if (m_expr==expr) + return; + if (m_expr) { + delete m_expr; + } + m_expr = expr; +} + +QVariant Field::customProperty(const QCString& propertyName, + const QVariant& defaultValue) const +{ + if (!m_customProperties) + return defaultValue; + CustomPropertiesMap::ConstIterator it(m_customProperties->find(propertyName)); + if (it==m_customProperties->constEnd()) + return defaultValue; + return it.data(); +} + +void Field::setCustomProperty(const QCString& propertyName, const QVariant& value) +{ + if (propertyName.isEmpty()) + return; + if (!m_customProperties) + m_customProperties = new CustomPropertiesMap(); + m_customProperties->insert(propertyName, value); +} + +//------------------------------------------------------- +#define ADDTYPE(type, i18, str) this->at(Field::type) = i18; \ + this->at(Field::type+Field::LastType+1) = str; \ + str2num.insert(QString::fromLatin1(str).lower(), type) +#define ADDGROUP(type, i18, str) this->at(Field::type) = i18; \ + this->at(Field::type+Field::LastTypeGroup+1) = str; \ + str2num.insert(QString::fromLatin1(str).lower(), type) + +Field::FieldTypeNames::FieldTypeNames() + : QValueVector<QString>() + , m_initialized(false) +{ +} + +void Field::FieldTypeNames::init() +{ + if (m_initialized) + return; + m_initialized = true; + resize((Field::LastType + 1)*2); + + ADDTYPE( InvalidType, i18n("Invalid Type"), "InvalidType" ); + ADDTYPE( Byte, i18n("Byte"), "Byte" ); + ADDTYPE( ShortInteger, i18n("Short Integer Number"), "ShortInteger" ); + ADDTYPE( Integer, i18n("Integer Number"), "Integer" ); + ADDTYPE( BigInteger, i18n("Big Integer Number"), "BigInteger" ); + ADDTYPE( Boolean, i18n("Yes/No Value"), "Boolean" ); + ADDTYPE( Date, i18n("Date"), "Date" ); + ADDTYPE( DateTime, i18n("Date and Time"), "DateTime" ); + ADDTYPE( Time, i18n("Time"), "Time" ); + ADDTYPE( Float, i18n("Single Precision Number"), "Float" ); + ADDTYPE( Double, i18n("Double Precision Number"), "Double" ); + ADDTYPE( Text, i18n("Text"), "Text" ); + ADDTYPE( LongText, i18n("Long Text"), "LongText" ); + ADDTYPE( BLOB, i18n("Object"), "BLOB" ); +} + +//------------------------------------------------------- + +Field::FieldTypeGroupNames::FieldTypeGroupNames() + : QValueVector<QString>() + , m_initialized(false) +{ +} + +void Field::FieldTypeGroupNames::init() +{ + if (m_initialized) + return; + m_initialized = true; + resize((Field::LastTypeGroup + 1)*2); + + ADDGROUP( InvalidGroup, i18n("Invalid Group"), "InvalidGroup" ); + ADDGROUP( TextGroup, i18n("Text"), "TextGroup" ); + ADDGROUP( IntegerGroup, i18n("Integer Number"), "IntegerGroup" ); + ADDGROUP( FloatGroup, i18n("Floating Point Number"), "FloatGroup" ); + ADDGROUP( BooleanGroup, i18n("Yes/No"), "BooleanGroup" ); + ADDGROUP( DateTimeGroup, i18n("Date/Time"), "DateTimeGroup" ); + ADDGROUP( BLOBGroup, i18n("Object"), "BLOBGroup" ); +} + +//------------------------------------------------------- + diff --git a/kexi/kexidb/field.h b/kexi/kexidb/field.h new file mode 100644 index 00000000..1fec04f6 --- /dev/null +++ b/kexi/kexidb/field.h @@ -0,0 +1,632 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <[email protected]> + Copyright (C) 2002 Joseph Wenninger <[email protected]> + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIDB_FIELD_H +#define KEXIDB_FIELD_H + +#include <qvariant.h> +#include <qstring.h> +#include <qpair.h> +#include <qvaluevector.h> +#include <qptrvector.h> +#include "kexidb/kexidb_export.h" +namespace KexiDB { + +class TableSchema; +class QuerySchema; +class FieldList; +class BaseExpr; + +//! Meta-data for a field +/*! KexiDB::Field provides information about single database field. + + Field class has defined following members: + - name + - type + - database constraints + - additional options + - length (make sense mostly for string types) + - precision (for floating-point type) + - defaultValue + - caption (user readable name that can be e.g. translated) + - description (user readable name additional text, can be useful for developers) + - width (a hint for displaying in tabular mode or as text box) + + Field can also have assigned expression (see KexiDB::BaseExpr class, + and expression() method). + If an expression is defined, then field's name is + + Note that aliases for fields are defined within query, not in Field object, + because the same field can be used in different queries with different alias. + + Notes for advanced use: Field obeject is designed to be owned by a parent object. + Such a parent object can be KexiDB::TableSchema, if the field defines single table column, + or KexiDB::QuerySchema, if the field defines an expression (KexiDB::BaseExpr class). + + Using expression class for fields allos to define expressions within queries like + "SELECT AVG(price) FROM products" + + You can choose whether your field is owned by query or table, + using appropriate constructor, or using parameterless constructor and + calling setTable() or setQuery() later. + +*/ +class KEXI_DB_EXPORT Field +{ + public: + typedef QPtrList<Field> List; //!< list of fields + typedef QPtrVector<Field> Vector; //!< vector of fields + typedef QPtrListIterator<Field> ListIterator; //!< iterator for list of fields + typedef QPair<Field*,Field*> Pair; //!< fields pair + typedef QPtrList<Pair> PairList; //!< list of fields pair + + /*! Unified (most common used) types of fields. */ + enum Type + { + InvalidType = 0, /*!< Unsupported/Unimplemented type */ + Byte = 1, /*!< 1 byte, signed or unsigned */ + ShortInteger = 2,/*!< 2 bytes, signed or unsigned */ + Integer = 3, /*!< 4 bytes, signed or unsigned */ + BigInteger = 4, /*!< 8 bytes, signed or unsigned */ + Boolean = 5, /*!< 0 or 1 */ + Date = 6, /*!< */ + DateTime = 7, /*!< */ + Time = 8, /*!< */ + Float = 9, /*!< 4 bytes */ + Double = 10, /*!< 8 bytes */ + Text = 11, /*!< Other name: Varchar; no more than 200 bytes, for efficiency */ + LongText = 12, /*!< Other name: Memo. More than 200 bytes*/ + BLOB = 13, /*!< Large binary object */ + + LastType = 13, /*!< This line should be at the end of the list of types! */ + + Null = 64, /*!< Used for fields that are "NULL" expressions. */ + + //! Special, internal types: + Asterisk = 128, /*!< Used in QueryAsterisk subclass objects only, + not used in table definitions, + but only in query definitions */ + Enum = 129, /*!< An integer internal with a string list of hints */ + Map = 130 /*!< Mapping from string to string list (more generic than Enum */ + }; + +//TODO: make this configurable + static uint defaultTextLength() { return 200; } + + /*! Type groups for fields. */ + enum TypeGroup + { + InvalidGroup = 0, + TextGroup = 1, + IntegerGroup = 2, + FloatGroup = 3, + BooleanGroup = 4, + DateTimeGroup = 5, + BLOBGroup = 6, /* large binary object */ + + LastTypeGroup = 6 // This line should be at the end of the enum! + }; + + /*! Possible constraints defined for a field. */ + enum Constraints + { + NoConstraints = 0, + AutoInc = 1, + Unique = 2, + PrimaryKey = 4, + ForeignKey = 8, + NotNull = 16, + NotEmpty = 32, //!< only legal for string-like and blob fields + Indexed = 64 + }; + + /*! Possible options defined for a field. */ + enum Options + { + NoOptions = 0, + Unsigned = 1 + }; + + /*! Creates a database field as a child of \a tableSchema table + No other properties are set (even the name), so these should be set later. */ + Field(TableSchema *tableSchema); + + /*! Creates a database field without any properties set. + These should be set later. */ + Field(); + + /*! Creates a database field with specified properties. */ + Field(const QString& name, Type ctype, + uint cconst=NoConstraints, + uint options = NoOptions, + uint length=0, uint precision=0, + QVariant defaultValue=QVariant(), + const QString& caption = QString::null, + const QString& description = QString::null, + uint width = 0); + + /*! Copy constructor. */ + Field(const Field& f); + + virtual ~Field(); + + //! Converts type \a type to QVariant equivalent as accurate as possible + static QVariant::Type variantType(uint type); + + /*! \return a i18n'd type name for \a type (\a type has to be an element from Field::Type, + not greater than Field::LastType) */ + static QString typeName(uint type); + + /*! \return type string for \a type, e.g. "Integer" for Integer type + (not-i18n'd, \a type has to be an element from Field::Type, + not greater than Field::LastType) */ + static QString typeString(uint type); + + /*! \return type for a given \a typeString */ + static Type typeForString(const QString& typeString); + + /*! \return type group for a given \a typeGroupString */ + static TypeGroup typeGroupForString(const QString& typeGroupString); + + /*! \return group for \a type */ + static TypeGroup typeGroup(uint type); + + /*! \return a i18n'd group name for \a typeGroup + (\a typeGroup has to be an element from Field::TypeGroup) */ + static QString typeGroupName(uint typeGroup); + + /*! \return type group string for \a typeGroup, e.g. "IntegerGroup" for IntegerGroup type + (not-i18n'd, \a type has to be an element from Field::Type, + not greater than Field::LastType) */ + static QString typeGroupString(uint typeGroup); + + /* ! \return the name of this field */ + inline QString name() const { return m_name; } + + /*! \return table schema of table that owns this field + or null if it has no table assigned. + @see query() */ + virtual TableSchema* table() const; + + /*! Sets \a table schema of table that owns this field. + This does not adds the field to \a table object. + You do not need to call this method by hand. + Call TableSchema::addField(Field *field) instead. + @see setQuery() */ + virtual void setTable(TableSchema *table); + + /*! For special use when the field defines expression. + \return query schema of query that owns this field + or null if it has no query assigned. + @see table() */ + QuerySchema* query() const; + + /*! For special use when field defines expression. + Sets \a query schema of query that owns this field. + This does not adds the field to \a query object. + You do not need to call this method by hand. + Call QuerySchema::addField() instead. + @see setQuery() */ + void setQuery(QuerySchema *query); + + /*! \return true if the field is autoincrement (e.g. integer/numeric) */ + inline bool isAutoIncrement() const { return constraints() & AutoInc; } + + /*! \return true if the field is member of single-field primary key */ + inline bool isPrimaryKey() const { return constraints() & PrimaryKey; } + + /*! \return true if the field is member of single-field unique key */ + inline bool isUniqueKey() const { return constraints() & Unique; } + + /*! \return true if the field is member of single-field foreign key */ + inline bool isForeignKey() const { return constraints() & ForeignKey; } + + /*! \return true if the field is not allowed to be null */ + inline bool isNotNull() const { return constraints() & NotNull; } + + /*! \return true if the field is not allowed to be null */ + inline bool isNotEmpty() const { return constraints() & NotEmpty; } + + /*! \return true if the field is indexed using single-field database index. */ + inline bool isIndexed() const { return constraints() & Indexed; } + + /*! \return true if the field is of any numeric type (integer or floating point) */ + inline bool isNumericType() const { return Field::isNumericType(type()); } + + /*! static version of isNumericType() method + *! \return true if the field is of any numeric type (integer or floating point)*/ + static bool isNumericType(uint type); + + /*! \return true if the field is of any integer type */ + inline bool isIntegerType() const { return Field::isIntegerType(type()); } + + /*! static version of isIntegerType() method + *! \return true if the field is of any integer type */ + static bool isIntegerType(uint type); + + /*! \return true if the field is of any floating point numeric type */ + inline bool isFPNumericType() const { return Field::isFPNumericType(type()); } + + /*! static version of isFPNumericType() method + *! \return true if the field is of any floating point numeric type */ + static bool isFPNumericType(uint type); + + /*! \return true if the field is of any date or time related type */ + inline bool isDateTimeType() const { return Field::isDateTimeType(type()); } + + /*! static version of isDateTimeType() method + *! \return true if the field is of any date or time related type */ + static bool isDateTimeType(uint type); + + /*! @return true if the field is of any text type */ + inline bool isTextType() const { return Field::isTextType(type()); } + + /*! static version of isTextType() method + *! \return true if the field is of any text type */ + static bool isTextType(uint type); + + uint options() const { return m_options; } + + void setOptions(uint options) { m_options = options; } + + //! Converts field's type to QVariant equivalent as accurate as possible + inline QVariant::Type variantType() const { return variantType(type()); } + + /*! \return a type for this field. If there's expression assigned, + type of the expression is returned instead. */ + Type type() const; + + //! \return a i18n'd type name for this field + inline QString typeName() const { return Field::typeName(type()); } + + //! \return type group for this field + inline TypeGroup typeGroup() const { return Field::typeGroup(type()); } + + //! \return a i18n'd type group name for this field + inline QString typeGroupName() const { return Field::typeGroupName(type()); } + + //! \return a type string for this field, + //! for example "Integer" string for Field::Integer type. + inline QString typeString() const { return Field::typeString(type()); } + + //! \return a type group string for this field, + //! for example "Integer" string for Field::IntegerGroup. + inline QString typeGroupString() const { return Field::typeGroupString(type()); } + + /*! \return (optional) subtype for this field. + Subtype is a string providing additional hint for field's type. + E.g. for BLOB type, it can be a MIME type or certain QVariant type name, + for example: "QPixmap", "QColor" or "QFont" */ + inline QString subType() const { return m_subType; } + + /*! Sets (optional) subtype for this field. + \sa subType() */ + inline void setSubType(const QString& subType) { m_subType = subType; } + + //! \return default value for this field. Null value means there + //! is no default value declared. The variant value is compatible with field's type. + inline QVariant defaultValue() const { return m_defaultValue; } + + /*! \return length of text, only meaningful if the field type is text. + 0 means "default length". */ + inline uint length() const { return m_length; } + + /*! \return precision for numeric and other fields that have both length (scale) + and precision (floating point types). */ + inline uint precision() const { return m_precision; } + + /*! \return scale for numeric and other fields that have both length (scale) + and precision (floating point types). + The scale of a numeric is the count of decimal digits in the fractional part, + to the right of the decimal point. The precision of a numeric is the total count + of significant digits in the whole number, that is, the number of digits + to both sides of the decimal point. So the number 23.5141 has a precision + of 6 and a scale of 4. Integers can be considered to have a scale of zero. */ + inline uint scale() const { return m_length; } + +//! @todo should we keep extended properties here or move them to a QVariant dictionary? + /*! \return number of decimal places that should be visible to the user, + e.g. within table view widget, form or printout. + Only meaningful if the field type is floating point or (in the future: decimal or currency). + + - Any value less than 0 (-1 is the default) means that there should be displayed all digits + of the fractional part, except the ending zeros. This is known as "auto" mode. + For example, 12.345000 becomes 12.345. + + - Value of 0 means that all the fractional part should be hidden (as well as the dot or comma). + For example, 12.345000 becomes 12. + + - Value N > 0 means that the fractional part should take exactly N digits. + If the fractional part is shorter than N, additional zeros are appended. + For example, "12.345" becomes "12.345000" if N=6. + */ + inline int visibleDecimalPlaces() const { return m_visibleDecimalPlaces; } + + /*! \return the constraints defined for this field. */ + inline uint constraints() const { return m_constraints; } + + /*! \return order of this field in containing table (counting starts from 0) + (-1 if unspecified). */ + inline int order() const { return m_order; } + + /*! \return caption of this field. */ + inline QString caption() const { return m_caption; } + + /*! \return caption of this field or - if empty - return its name. */ + inline QString captionOrName() const { return m_caption.isEmpty() ? m_name : m_caption; } + + /*! \return description text for this field. */ + inline QString description() const { return m_desc; } + + /*! \return width of this field (usually in pixels or points) + 0 (the default) means there is no hint for the width. */ + inline uint width() const { return m_width; } + + //! if the type has the unsigned attribute + inline bool isUnsigned() const { return m_options & Unsigned; } + + /*! \return true if this field has EMPTY property (i.e. it is of type + string or is a BLOB). */ + inline bool hasEmptyProperty() const { return Field::hasEmptyProperty(type()); } + + /*! static version of hasEmptyProperty() method + \return true if this field type has EMPTY property (i.e. it is string or BLOB type) */ + static bool hasEmptyProperty(uint type); + + /*! \return true if this field can be auto-incremented. + Actually, returns true for integer field type. \sa IntegerType, isAutoIncrement() */ + inline bool isAutoIncrementAllowed() const { return Field::isAutoIncrementAllowed(type()); } + + /*! static version of isAutoIncrementAllowed() method + \return true if this field type can be auto-incremented. */ + static bool isAutoIncrementAllowed(uint type); + + /*! Sets type \a t for this field. This does nothing if there's already expression assigned, + see expression(). */ + void setType(Type t); + + /*! Sets name \a name for this field. */ + void setName(const QString& name); + + /*! Sets constraints to \a c. If PrimaryKey is set in \a c, also + constraits implied by being primary key are enforced (see setPrimaryKey()). + If Indexed is not set in \a c, constraits implied by not being are + enforced as well (see setIndexed()). */ + void setConstraints(uint c); + + /*! Sets length for this field. Only works for Text Type (even not LongText!). + 0 means "default length". @see length() */ + void setLength(uint l); + + /*! Sets scale for this field. Only works for floating-point types. + @see scale() */ + void setScale(uint s); + + /*! Sets number of decimal places that should be visible to the user. + @see visibleDecimalPlaces() */ + void setVisibleDecimalPlaces(int p); + + /*! Sets scale for this field. Only works for floating-point types. */ + void setPrecision(uint p); + + /*! Sets unsigned flag for this field. Only works for integer types. */ + void setUnsigned(bool u); + + /*! Sets default value for this field. Setting null value removes the default value. + @see defaultValue() */ + void setDefaultValue(const QVariant& def); + + /*! Sets default value decoded from QCString. + Decoding errors are detected (value is strictly checked against field type) + - if one is encountered, default value is cleared (defaultValue()==QVariant()). + \return true if given value was valid for field type. */ + bool setDefaultValue(const QCString& def); + + /*! Sets auto increment flag. Only available to set true, + if isAutoIncrementAllowed() is true. */ + void setAutoIncrement(bool a); + + /*! Specifies whether the field is single-field primary key or not + (KexiDB::PrimeryKey item). + Use this with caution. Setting this to true implies setting: + - setUniqueKey(true) + - setNotNull(true) + - setNotEmpty(true) + - setIndexed(true) + + Setting this to false implies setting setAutoIncrement(false). */ + void setPrimaryKey(bool p); + + /*! Specifies whether the field has single-field unique constraint or not + (KexiDB::Unique item). Setting this to true implies setting Indexed flag + to true (setIndexed(true)), because index is required it control unique constraint. */ + void setUniqueKey(bool u); + + /*! Sets whether the field has to be declared with single-field foreign key. + Used in IndexSchema::setForeigKey(). */ + void setForeignKey(bool f); + + /*! Specifies whether the field has single-field unique constraint or not + (KexiDB::NotNull item). Setting this to true implies setting Indexed flag + to true (setIndexed(true)), because index is required it control + not null constraint. */ + void setNotNull(bool n); + + /*! Specifies whether the field has single-field unique constraint or not + (KexiDB::NotEmpty item). Setting this to true implies setting Indexed flag + to true (setIndexed(true)), because index is required it control + not empty constraint. */ + void setNotEmpty(bool n); + + /*! Specifies whether the field is indexed (KexiDB::Indexed item) + (by single-field implicit index) or not. + Use this with caution. Since index is used to control unique, + not null/empty constratins, setting this to false implies setting: + - setPrimaryKey(false) + - setUniqueKey(false) + - setNotNull(false) + - setNotEmpty(false) + because above flags need index to be present. + Similarly, setting one of the above flags to true, will automatically + do setIndexed(true) for the same reason. */ + void setIndexed(bool s); + + /*! Sets caption for this field to \a caption. */ + void setCaption(const QString& caption) { m_caption=caption; } + + /*! Sets description for this field to \a description. */ + void setDescription(const QString& description) { m_desc=description; } + + /*! Sets visible width for this field to \a w + (usually in pixels or points). 0 means there is no hint for the width. */ + void setWidth(uint w) { m_width=w; } + + /*! There can be added asterisks (QueryAsterisk objects) + to query schemas' field list. QueryAsterisk subclasses Field class, + and to check if the given object (pointed by Field*) + is asterisk or just ordinary field definition, + you can call this method. This is just effective version of QObject::isA(). + Every QueryAsterisk object returns true here, + and every Field object returns false. + */ + virtual bool isQueryAsterisk() const { return false; } + + /*! \return string for debugging purposes. */ + virtual QString debugString() const; + + /*! Shows debug information about this field. */ + void debug(); + + /*! \return KexiDB::BaseExpr object if the field value is an + expression. Unless the expression is set with setExpression(), it is null. + */ + inline KexiDB::BaseExpr *expression() { return m_expr; } + + /*! Sets expression data \a expr. If there was + already expression set, it is destroyed before new assignment. + This Field object becames owner of \a expr object, + so you do not have to worry about deleting it later. + If the \a expr is null, current field's expression is deleted, if exists. + + Because the field defines an expression, it should be assigned to a query, + not to a table. + */ + void setExpression(KexiDB::BaseExpr *expr); + + /*! \return true if there is expression defined for this field. + This method is provided for better readibility + - does the same as expression()!=NULL but */ + inline bool isExpression() const { return m_expr!=NULL; } + +//<TMP> + /*! \return the hints for enum fields. */ + QValueVector<QString> enumHints() const { return m_hints; } + QString enumHint(uint num) { return (num < m_hints.size()) ? m_hints.at(num) : QString::null; } + /*! sets the hint for enum fields */ + void setEnumHints(const QValueVector<QString> &l) { m_hints = l; } +//</TMP> + + /*! \return custom property \a propertyName. + If there is no such a property, \a defaultValue is returned. */ + QVariant customProperty(const QCString& propertyName, + const QVariant& defaultValue = QVariant()) const; + + //! Sets value \a value for custom property \a propertyName + void setCustomProperty(const QCString& propertyName, const QVariant& value); + + //! A data type used for handling custom properties of a field + typedef QMap<QCString,QVariant> CustomPropertiesMap; + + //! \return all custom properties + inline const CustomPropertiesMap customProperties() const { + return m_customProperties ? *m_customProperties : CustomPropertiesMap(); } + + protected: + /*! Creates a database field as a child of \a querySchema table + Assigns \a expr expression to this field, if present. + Used internally by query schemas, e.g. to declare asterisks or + to add expression columns. + No other properties are set, so these should be set later. */ + Field(QuerySchema *querySchema, BaseExpr* expr = 0); + + /*! @internal Used by constructors. */ + void init(); + + //! \return a deep copy of this object. Used in @ref FieldList(const FieldList& fl). + virtual Field* copy() const; + + FieldList *m_parent; //!< In most cases this points to a TableSchema + //!< object that field is assigned. + QString m_name; + QString m_subType; + uint m_constraints; + uint m_length; //!< also used for storing scale for floating point types + uint m_precision; + int m_visibleDecimalPlaces; //!< used in visibleDecimalPlaces() + uint m_options; + QVariant m_defaultValue; + int m_order; + QString m_caption; + QString m_desc; + uint m_width; + QValueVector<QString> m_hints; + + KexiDB::BaseExpr *m_expr; + CustomPropertiesMap* m_customProperties; + + //! @internal Used in m_typeNames member to handle i18n'd type names + class KEXI_DB_EXPORT FieldTypeNames : public QValueVector<QString> { + public: + FieldTypeNames(); + void init(); + QMap<QString,Type> str2num; + protected: + bool m_initialized : 1; + }; + + //! @internal Used in m_typeGroupNames member to handle i18n'd type group names + class KEXI_DB_EXPORT FieldTypeGroupNames : public QValueVector<QString> { + public: + FieldTypeGroupNames(); + void init(); + QMap<QString,TypeGroup> str2num; + protected: + bool m_initialized : 1; + }; + + //! real i18n'd type names (and not-i18n'd type name strings) + static FieldTypeNames m_typeNames; + + //! real i18n'd type group names (and not-i18n'd group name strings) + static FieldTypeGroupNames m_typeGroupNames; + + private: + Type m_type; + + friend class Connection; + friend class FieldList; + friend class TableSchema; + friend class QuerySchema; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/fieldlist.cpp b/kexi/kexidb/fieldlist.cpp new file mode 100644 index 00000000..ee159c72 --- /dev/null +++ b/kexi/kexidb/fieldlist.cpp @@ -0,0 +1,278 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2007 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/fieldlist.h> +#include <kexidb/object.h> + +#include <kdebug.h> + +#include <assert.h> + +using namespace KexiDB; + +FieldList::FieldList(bool owner) + //reasonable sizes: TODO + : m_fields_by_name(101, false) +{ + m_fields.setAutoDelete( owner ); + m_fields_by_name.setAutoDelete( false ); + m_autoinc_fields = 0; +} + +FieldList::FieldList(const FieldList& fl, bool deepCopyFields) + : m_fields_by_name( fl.m_fields_by_name.size() ) +{ + m_fields.setAutoDelete( fl.m_fields.autoDelete() ); + m_fields_by_name.setAutoDelete( false ); + m_autoinc_fields = 0; + + if (deepCopyFields) { + //deep copy for the fields + for (Field::ListIterator f_it(fl.m_fields); f_it.current(); ++f_it) { + Field *f = f_it.current()->copy(); + if (f_it.current()->m_parent == &fl) + f->m_parent = this; + addField( f ); + } + } +} + +FieldList::~FieldList() +{ + delete m_autoinc_fields; +} + +void FieldList::clear() +{ +// m_name = QString::null; + m_fields.clear(); + m_fields_by_name.clear(); + m_sqlFields = QString::null; + delete m_autoinc_fields; + m_autoinc_fields = 0; +} + +FieldList& FieldList::insertField(uint index, KexiDB::Field *field) +{ + assert(field); + if (!field) + return *this; + if (index>m_fields.count()) { + KexiDBFatal << "FieldList::insertField(): index (" << index << ") out of range" << endl; + return *this; + } + if (!m_fields.insert(index, field)) + return *this; + if (!field->name().isEmpty()) + m_fields_by_name.insert(field->name().lower(),field); + m_sqlFields = QString::null; + return *this; +} + +void FieldList::renameField(const QString& oldName, const QString& newName) +{ + renameField( m_fields_by_name[ oldName ], newName ); +} + +void FieldList::renameField(KexiDB::Field *field, const QString& newName) +{ + if (!field || field != m_fields_by_name[ field->name() ]) { + KexiDBFatal << "FieldList::renameField() no field found " + << (field ? QString("\"%1\"").arg(field->name()) : QString::null) << endl; + return; + } + m_fields_by_name.take( field->name() ); + field->setName( newName ); + m_fields_by_name.insert( field->name(), field ); +} + + +FieldList& FieldList::addField(KexiDB::Field *field) +{ + return insertField(m_fields.count(), field); +} + +void FieldList::removeField(KexiDB::Field *field) +{ + assert(field); + if (!field) + return; + m_fields_by_name.remove(field->name()); + m_fields.remove(field); + m_sqlFields = QString::null; +} + +Field* FieldList::field(const QString& name) +{ + return m_fields_by_name[name]; +} + +QString FieldList::debugString() +{ + QString dbg; + dbg.reserve(512); + Field::ListIterator it( m_fields ); + Field *field; + bool start = true; + if (!it.current()) + dbg = "<NO FIELDS>"; + for (; (field = it.current())!=0; ++it) { + if (!start) + dbg += ",\n"; + else + start = false; + dbg += " "; + dbg += field->debugString(); + } + return dbg; +} + +void FieldList::debug() +{ + KexiDBDbg << debugString() << endl; +} + +#define _ADD_FIELD(fname) \ +{ \ + if (fname.isEmpty()) return fl; \ + f = m_fields_by_name[fname]; \ + if (!f) { KexiDBWarn << subListWarning1(fname) << endl; delete fl; return 0; } \ + fl->addField(f); \ +} + +static QString subListWarning1(const QString& fname) { + return QString("FieldList::subList() could not find field \"%1\"").arg(fname); +} + +FieldList* FieldList::subList(const QString& n1, const QString& n2, + const QString& n3, const QString& n4, + const QString& n5, const QString& n6, + const QString& n7, const QString& n8, + const QString& n9, const QString& n10, + const QString& n11, const QString& n12, + const QString& n13, const QString& n14, + const QString& n15, const QString& n16, + const QString& n17, const QString& n18) +{ + if (n1.isEmpty()) + return 0; + Field *f; + FieldList *fl = new FieldList(false); + _ADD_FIELD(n1); + _ADD_FIELD(n2); + _ADD_FIELD(n3); + _ADD_FIELD(n4); + _ADD_FIELD(n5); + _ADD_FIELD(n6); + _ADD_FIELD(n7); + _ADD_FIELD(n8); + _ADD_FIELD(n9); + _ADD_FIELD(n10); + _ADD_FIELD(n11); + _ADD_FIELD(n12); + _ADD_FIELD(n13); + _ADD_FIELD(n14); + _ADD_FIELD(n15); + _ADD_FIELD(n16); + _ADD_FIELD(n17); + _ADD_FIELD(n18); + return fl; +} + +FieldList* FieldList::subList(const QStringList& list) +{ + Field *f; + FieldList *fl = new FieldList(false); + for(QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { + _ADD_FIELD( (*it) ); + } + return fl; +} + +FieldList* FieldList::subList(const QValueList<uint>& list) +{ + Field *f; + FieldList *fl = new FieldList(false); + foreach(QValueList<uint>::ConstIterator, it, list) { + f = field(*it); + if (!f) { + KexiDBWarn << QString("FieldList::subList() could not find field at position %1").arg(*it) << endl; + delete fl; + return 0; + } + fl->addField(f); + } + return fl; +} + +QStringList FieldList::names() const +{ + QStringList r; +// for (QDictIterator<Field> it(m_fields_by_name);it.current();++it) { +// r += it.currentKey().lower(); +// } + for (Field::ListIterator it(m_fields); it.current(); ++it) { + r += it.current()->name().lower(); + } + return r; +} + +//static +QString FieldList::sqlFieldsList(Field::List* list, Driver *driver, + const QString& separator, const QString& tableAlias, int drvEscaping) +{ + if (!list) + return QString::null; + QString result; + result.reserve(256); + bool start = true; + const QString tableAliasAndDot( tableAlias.isEmpty() ? QString::null : (tableAlias + ".") ); + for (Field::ListIterator it( *list ); it.current(); ++it) { + if (!start) + result += separator; + else + start = false; + result += (tableAliasAndDot + driver->escapeIdentifier( it.current()->name(), drvEscaping )); + } + return result; +} + +QString FieldList::sqlFieldsList(Driver *driver, + const QString& separator, const QString& tableAlias, int drvEscaping) +{ + if (!m_sqlFields.isEmpty()) + return m_sqlFields; + + m_sqlFields = FieldList::sqlFieldsList( &m_fields, driver, separator, tableAlias, drvEscaping ); + return m_sqlFields; +} + +Field::List* FieldList::autoIncrementFields() +{ + if (!m_autoinc_fields) { + m_autoinc_fields = new Field::List(); + Field *f; + for (Field::ListIterator f_it(m_fields); (f = f_it.current()); ++f_it) { + if (f->isAutoIncrement()) { + m_autoinc_fields->append( f_it.current() ); + } + } + } + return m_autoinc_fields; +} diff --git a/kexi/kexidb/fieldlist.h b/kexi/kexidb/fieldlist.h new file mode 100644 index 00000000..fd47459e --- /dev/null +++ b/kexi/kexidb/fieldlist.h @@ -0,0 +1,175 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2007 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_FIELDLIST_H +#define KEXIDB_FIELDLIST_H + +#include <qvaluelist.h> +#include <qdict.h> +#include <qstring.h> + +#include <kexidb/field.h> +#include <kexidb/driver.h> + +namespace KexiDB { + +class Connection; + +/*! Helper class that stores list of fields. +*/ + +class KEXI_DB_EXPORT FieldList +{ + public: + /*! Creates empty list of fields. If \a owner is true, the list will be + owner of any added field, what means that these field + will be removed on the list destruction. Otherwise, the list + just points any field that was added. + \sa isOwner() + */ + FieldList(bool owner = false); + + /*! Copy constructor. + If \a deepCopyFields is true, all fields are deeply copied, else only pointer are copied. + Reimplemented in QuerySchema constructor. */ + FieldList(const FieldList& fl, bool deepCopyFields = true); + + /*! Destroys the list. If the list owns fields (see constructor), + these are also deleted. */ + virtual ~FieldList(); + + /*! \return number of fields in the list. */ + inline uint fieldCount() const { return m_fields.count(); } + + /*! Adds \a field at the and of field list. */ + FieldList& addField(Field *field); + + /*! Inserts \a field into a specified position (\a index). + + Note: You can reimplement this method but you should still call + this implementation in your subclass. */ + virtual FieldList& insertField(uint index, Field *field); + + /*! Removes field from the field list. Use with care. + + Note: You can reimplement this method but you should still call + this implementation in your subclass. */ + virtual void removeField(KexiDB::Field *field); + + /*! \return field id or NULL if there is no such a field. */ + inline Field* field(uint id) { return (id < m_fields.count()) ? m_fields.at(id) : 0; } + + /*! \return field with name \a name or NULL if there is no such a field. */ + virtual Field* field(const QString& name); + + /*! \return true if this list contains given \a field. */ + inline bool hasField(const Field* field) { return m_fields.findRef(field)!=-1; } + + /*! \return first occurrence of \a field in the list + or -1 if this list does not contain this field. */ + inline int indexOf(const Field* field) { return m_fields.findRef(field); } + + /*! \return list of field names for this list. */ + QStringList names() const; + + Field::ListIterator fieldsIterator() const { return Field::ListIterator(m_fields); } + + inline Field::List* fields() { return &m_fields; } + + /*! \return list of autoincremented fields. The list is owned by this FieldList object. */ + Field::List* autoIncrementFields(); + + /*! \return true if fields in the list are owned by this list. */ + inline bool isOwner() const { return m_fields.autoDelete(); } + + /*! Removes all fields from the list. */ + virtual void clear(); + + /*! \return String for debugging purposes. */ + virtual QString debugString(); + + /*! Shows debug information about all fields in the list. */ + void debug(); + + /*! Creates and returns a list that contain fields selected by name. + At least one field (exising on this list) should be selected, otherwise 0 is + returned. Returned FieldList object is not owned by any parent (so you need + to destroy yourself) and Field objects included in it are not owned by it + (but still as before, by 'this' object). + Returned list can be usable e.g. as argument for Connection::insertRecord(). + 0 is returned if at least one name cannot be found. + */ + FieldList* subList(const QString& n1, const QString& n2 = QString::null, + const QString& n3 = QString::null, const QString& n4 = QString::null, + const QString& n5 = QString::null, const QString& n6 = QString::null, + const QString& n7 = QString::null, const QString& n8 = QString::null, + const QString& n9 = QString::null, const QString& n10 = QString::null, + const QString& n11 = QString::null, const QString& n12 = QString::null, + const QString& n13 = QString::null, const QString& n14 = QString::null, + const QString& n15 = QString::null, const QString& n16 = QString::null, + const QString& n17 = QString::null, const QString& n18 = QString::null + ); + + /*! Like above, but with a QStringList */ + FieldList* subList(const QStringList& list); + + /*! Like above, but with a list of field indices */ + FieldList* subList(const QValueList<uint>& list); + + /*! \return a string that is a result of all field names concatenated + and with \a separator. This is usable e.g. as argument like "field1,field2" + for "INSERT INTO (xxx) ..". The result of this method is effectively cached, + and it is invalidated when set of fields changes (e.g. using clear() + or addField()). + \a tableAlias, if provided is prepended to each field, so the resulting + names will be in form tableAlias.fieldName. This option is used for building + queries with joins, where fields have to be spicified without ambiguity. + See @ref Connection::selectStatement() for example use. + \a drvEscaping can be used to alter default escaping type. + */ + QString sqlFieldsList(Driver *driver, const QString& separator = QString::fromLatin1(","), + const QString& tableAlias = QString::null, + int drvEscaping = Driver::EscapeDriver|Driver::EscapeAsNecessary); + + /*! Like above, but this is convenient static function, so you can pass any \a list here. */ + static QString sqlFieldsList(Field::List* list, Driver *driver, + const QString& separator = QString::fromLatin1(","), const QString& tableAlias = QString::null, + int drvEscaping = Driver::EscapeDriver|Driver::EscapeAsNecessary); + + /*! @internal Renames field \a oldName to \a newName. + Do not use this for physical renaming columns. Use AlterTableHandler instead. */ + void renameField(const QString& oldName, const QString& newName); + + /*! @internal + \overload void renameField(const QString& oldName, const QString& newName) */ + void renameField(KexiDB::Field *field, const QString& newName); + + protected: + Field::List m_fields; + QDict<Field> m_fields_by_name; //!< Fields collected by name. Not used by QuerySchema. + Field::List *m_autoinc_fields; + + private: + //! cached + QString m_sqlFields; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/fieldvalidator.cpp b/kexi/kexidb/fieldvalidator.cpp new file mode 100644 index 00000000..e657d2fa --- /dev/null +++ b/kexi/kexidb/fieldvalidator.cpp @@ -0,0 +1,100 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "fieldvalidator.h" +#include "field.h" + +#include <kexiutils/longlongvalidator.h> +#include <knumvalidator.h> +#include <qwidget.h> + +using namespace KexiDB; + +FieldValidator::FieldValidator( const Field &field, QWidget * parent, const char * name ) + : KexiUtils::MultiValidator(parent, name) +{ +//! @todo merge this code with KexiTableEdit code! +//! @todo set maximum length validator +//! @todo handle input mask (via QLineEdit::setInputMask() + const Field::Type t = field.type(); + if (field.isIntegerType()) { + QValidator *validator = 0; + const bool u = field.isUnsigned(); + int bottom, top; + if (t==Field::Byte) { + bottom = u ? 0 : -0x80; + top = u ? 0xff : 0x7f; + } + else if (t==Field::ShortInteger) { + bottom = u ? 0 : -0x8000; + top = u ? 0xffff : 0x7fff; + } + else if (t==Field::Integer) { + bottom = u ? 0 : -0x7fffffff-1; + top = u ? 0xffffffff : 0x7fffffff; + } + else if (t==Field::BigInteger) { +//! @todo handle unsigned (using ULongLongValidator) + validator = new KexiUtils::LongLongValidator(0); + } + + if (!validator) + validator = new KIntValidator(bottom, top, 0); //the default + addSubvalidator( validator ); + } + else if (field.isFPNumericType()) { + QValidator *validator; + if (t==Field::Float) { + if (field.isUnsigned()) //ok? + validator = new KDoubleValidator(0, 3.4e+38, field.scale(), 0); + else + validator = new KDoubleValidator(this); + } + else {//double + if (field.isUnsigned()) //ok? + validator = new KDoubleValidator(0, 1.7e+308, field.scale(), 0); + else + validator = new KDoubleValidator(this); + } + addSubvalidator( validator ); + } + else if (t==Field::Date) { +//! @todo add validator +// QValidator *validator = new KDateValidator(this); +// setValidator( validator ); +//moved setInputMask( dateFormatter()->inputMask() ); + } + else if (t==Field::Time) { +//! @todo add validator +//moved setInputMask( timeFormatter()->inputMask() ); + } + else if (t==Field::DateTime) { +//moved setInputMask( +//moved dateTimeInputMask( *dateFormatter(), *timeFormatter() ) ); + } + else if (t==Field::Boolean) { +//! @todo add BooleanValidator + addSubvalidator( new KIntValidator(0, 1) ); + } +} + +FieldValidator::~FieldValidator() +{ +} + diff --git a/kexi/kexidb/fieldvalidator.h b/kexi/kexidb/fieldvalidator.h new file mode 100644 index 00000000..4c5dbbfe --- /dev/null +++ b/kexi/kexidb/fieldvalidator.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXI_DB_FIELDVALIDATOR_H +#define KEXI_DB_FIELDVALIDATOR_H + +#include <kexidb/kexidb_export.h> +#include <kexiutils/validator.h> + +class QWidget; + +namespace KexiDB { +class Field; + +//! @short A validator for KexiDB data types +/*! This can be used by QLineEdit or subclass to provide validated + text entry. Curently is supports all integer types, floating point types and booleans. + Internal validators like KIntValidator or KexiUtils::LongLongValidator are used. + 'unsigned' and 'scale' parameters are taken into account when setting up internal validators. + @todo date/time support for types + @todo add validation of the maximum length and other field's properties +*/ +class KEXI_DB_EXPORT FieldValidator : public KexiUtils::MultiValidator +{ + public: + //! Setups the validator for \a field. Does not keep a pointer to \a field. + FieldValidator( const Field &field, QWidget * parent, const char * name = 0 ); + ~FieldValidator(); +}; + +} + +#endif diff --git a/kexi/kexidb/global.cpp b/kexi/kexidb/global.cpp new file mode 100644 index 00000000..cbbfb723 --- /dev/null +++ b/kexi/kexidb/global.cpp @@ -0,0 +1,55 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/global.h> + +using namespace KexiDB; + +DatabaseVersionInfo::DatabaseVersionInfo() +{ + major = 0; + minor = 0; +} + +DatabaseVersionInfo::DatabaseVersionInfo(uint majorVersion, uint minorVersion) +{ + major = majorVersion; + minor = minorVersion; +} + +//------------------------ + +ServerVersionInfo::ServerVersionInfo() +{ + major = 0; + minor = 0; + release = 0; +} + +void ServerVersionInfo::clear() +{ + major = 0; + minor = 0; + release = 0; + string = QString::null; +} + +//------------------------ + +DatabaseVersionInfo KexiDB::version() { return KEXIDB_VERSION; } diff --git a/kexi/kexidb/global.h b/kexi/kexidb/global.h new file mode 100644 index 00000000..78c1b68b --- /dev/null +++ b/kexi/kexidb/global.h @@ -0,0 +1,171 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_GLOBAL_H +#define KEXIDB_GLOBAL_H + +#include <kexidb/kexidb_export.h> +#include <qstring.h> + +//global public definitions + +/*! KexiDB implementation version. + It is altered after every API change: + - major number is increased after KexiDB storage format change, + - minor is increased after adding binary-incompatible change. + In external code: do not use this to get library version information: + use KexiDB::versionMajor() and KexiDB::versionMinor() instead to get real version. +*/ +#define KEXIDB_VERSION_MAJOR 1 +#define KEXIDB_VERSION_MINOR 8 + +/*! KexiDB implementation version. @see KEXIDB_VERSION_MAJOR, KEXIDB_VERSION_MINOR */ +#define KEXIDB_VERSION KexiDB::DatabaseVersionInfo(KEXIDB_VERSION_MAJOR, KEXIDB_VERSION_MINOR) + +/*! \namespace KexiDB +\brief High-level database connectivity library with database backend drivers + +@author Jaroslaw Staniek <[email protected]> + +\section Framework +DriverManager + +Database access + - Connection + - ConnectionData + +Database structure + - Schema + - tableschema + - queryschema + - indexschema + +Stored in the database. + + +Data representation + - Record + - Field + + +\section Drivers + +Drivers are loaded using DriverManager::driver(const QString& name). The names +of drivers are given in their drivers .desktop file in the +X-Kexi-DriverName field. + +KexiDB supports two kinds of databases: file-based and network-based databases. +The type of a driver is available from several places. The X-Kexi-DriverType +field in the driver's .desktop file, is read by the DriverManager and +available by calling DriverManager::driverInfo(const QString &name) and using +the Driver::Info#fileBased member from the result. Given a reference to a +Driver, its type can also be found directly using Driver::isFileDriver() const. + +Each database backend driver consists of three main classes: a driver, +a connection and a cursor class, e.g SQLiteDriver, SQLiteConnection, +SQLiteCursor. + +The driver classes subclass the Driver class. They set Driver#m_typeNames, +which maps KexiDB's Field::Type on to the types supported by the database. They also +provide functions for escaping strings and checking table names. These may be +used, for example, on a database backend that uses the database name as a +filename. In this case, it should be ensured that all the characters in the +database name are valid characters in a filename. + +The connection classes subclass the Connection class, and include most of the +calls to the native database API. + +The cursor classes subclass Cursor, and implement cursor functionality specific +to the database backend. + +*/ +namespace KexiDB { + +#define KexiDBDbg kdDebug(44000) //! Debug area for core KexiDB code +#define KexiDBDrvDbg kdDebug(44001) //! Debug area for KexiDB's drivers implementation code +#define KexiDBWarn kdWarning(44000) +#define KexiDBDrvWarn kdWarning(44001) +#define KexiDBFatal kdFatal(44000) + +/*! @short Contains database version information about a Kexi-compatible database. + The version is stored as internal database properties. */ +class KEXI_DB_EXPORT DatabaseVersionInfo +{ + public: + DatabaseVersionInfo(); + DatabaseVersionInfo(uint majorVersion, uint minorVersion); + + //! Major version number, e.g. 1 for 1.8 + uint major; + + //! Minor version number, e.g. 8 for 1.8 + uint minor; +}; + +//! \return KexiDB version info +KEXI_DB_EXPORT DatabaseVersionInfo version(); + +/*! @short Contains version information about a database backend. */ +class KEXI_DB_EXPORT ServerVersionInfo +{ + public: + ServerVersionInfo(); + + //! Clears the information - integers will be set to 0 and string to null + void clear(); + + //! Major version number, e.g. 1 for 1.2.3 + uint major; + + //! Minor version number, e.g. 2 for 1.2.3 + uint minor; + + //! Release version number, e.g. 3 for 1.2.3 + uint release; + + //! Version string, as returned by the server + QString string; +}; + +/*! Object types set like table or query. */ +enum ObjectTypes { + UnknownObjectType = -1, //!< helper + AnyObjectType = 0, //!< helper + TableObjectType = 1, + QueryObjectType = 2, + LastObjectType = 2, //ALWAYS UPDATE THIS + + KexiDBSystemTableObjectType = 128,//!< helper, not used in storage + //!< (allows to select kexidb system tables + //!< may be or'd with TableObjectType) + IndexObjectType = 256 //!< special +}; + +} + +#ifndef futureI18n +# define futureI18n QString +# define futureI18n2(a,b) QString(b) +#endif + +#ifndef FUTURE_I18N_NOOP +# define FUTURE_I18N_NOOP(x) (x) +#endif + +#endif diff --git a/kexi/kexidb/indexschema.cpp b/kexi/kexidb/indexschema.cpp new file mode 100644 index 00000000..20dc9e82 --- /dev/null +++ b/kexi/kexidb/indexschema.cpp @@ -0,0 +1,199 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/indexschema.h> + +#include <kexidb/driver.h> +#include <kexidb/connection.h> +#include <kexidb/tableschema.h> + +#include <assert.h> + +#include <kdebug.h> + +using namespace KexiDB; + +IndexSchema::IndexSchema(TableSchema *tableSchema) + : FieldList(false)//fields are not owned by IndexSchema object + , SchemaData(KexiDB::IndexObjectType) + , m_tableSchema(tableSchema) + , m_primary( false ) + , m_unique( false ) + , m_isAutoGenerated( false ) + , m_isForeignKey( false ) +{ + m_master_owned_rels.setAutoDelete(true); //rels at the master-side are owned +} + +IndexSchema::IndexSchema(const IndexSchema& idx, TableSchema& parentTable) +// : FieldList(static_cast<const FieldList&>(idx))//fields are not owned by IndexSchema object + : FieldList(false)//fields are not owned by IndexSchema object + , SchemaData(static_cast<const SchemaData&>(idx)) + , m_tableSchema(&parentTable) + , m_primary( idx.m_primary ) + , m_unique( idx.m_unique ) + , m_isAutoGenerated( idx.m_isAutoGenerated ) + , m_isForeignKey( idx.m_isForeignKey ) +{ + m_master_owned_rels.setAutoDelete(true); //rels at the master-side are owned + + //deep copy of the fields + for (Field::ListIterator f_it(idx.m_fields); f_it.current(); ++f_it) { + Field *parentTableField = parentTable.field( f_it.current()->name() ); + if (!parentTableField) { + KexiDBWarn << "IndexSchema::IndexSchema(const IndexSchema& idx, const TableSchema& parentTable): " + "cannot find field '" << f_it.current()->name() << " in parentTable. Empty index will be created!" << endl; + FieldList::clear(); + break; + } + addField( parentTableField ); + } + +//js TODO: copy relationships! +// Reference::List m_refs_to; //! list of references to table (of this index) +// Reference::List m_refs_from; //! list of references from the table (of this index), +// //! this index is foreign key for these references +// //! and therefore - owner of these +} + +IndexSchema::~IndexSchema() +{ + /* It's a list of relationships to the table (of this index), i.e. any such relationship in which + the table is at 'master' side will be cleared and relationships will be destroyed. + So, we need to detach all these relationships from details-side, corresponding indices. + */ + + QPtrListIterator<Relationship> it(m_master_owned_rels); + for (;it.current();++it) { + if (it.current()->detailsIndex()) { + it.current()->detailsIndex()->detachRelationship(it.current()); + } + } + //ok, now m_master_owned_rels will be just cleared automatically +} + +FieldList& IndexSchema::addField(Field *field) +{ + if (field->table() != m_tableSchema) { + KexiDBDbg << "IndexSchema::addField(" << (field ? field->name() : 0) + << "): WARNING: field doas not belong to the same table '" + << (field && field->table() ? field->table()->name() : 0) + << "'as index!" << endl; + return *this; + } + return FieldList::addField(field); +} + + +KexiDB::TableSchema* IndexSchema::table() const +{ + return m_tableSchema; +} + +bool IndexSchema::isAutoGenerated() const +{ + return m_isAutoGenerated; +} + +void IndexSchema::setAutoGenerated(bool set) +{ + m_isAutoGenerated = set; +} + +bool IndexSchema::isPrimaryKey() const +{ + return m_primary; +} + +void IndexSchema::setPrimaryKey(bool set) +{ + m_primary = set; + if (m_primary) + m_unique = true; +} + +bool IndexSchema::isUnique() const +{ + return m_unique; +} + +void IndexSchema::setUnique(bool set) +{ + m_unique=set; + if (!m_unique) + m_primary=false; +} + +void IndexSchema::setForeignKey(bool set) +{ + m_isForeignKey = set; + if (m_isForeignKey) { + setUnique(false); + } + if (fieldCount()==1) { + m_fields.first()->setForeignKey(true); + } +} + +QString IndexSchema::debugString() +{ + return QString("INDEX ") + schemaDataDebugString() + "\n" + + (m_isForeignKey ? "FOREIGN KEY " : "") + + (m_isAutoGenerated ? "AUTOGENERATED " : "") + + (m_primary ? "PRIMARY " : "") + + ((!m_primary) && m_unique ? "UNIQUE " : "") + + FieldList::debugString(); +} + +void IndexSchema::attachRelationship(Relationship *rel) +{ + attachRelationship(rel, true); +} + +void IndexSchema::attachRelationship(Relationship *rel, bool ownedByMaster) +{ + if (!rel) + return; + if (rel->masterIndex()==this) { + if (ownedByMaster) { + if (m_master_owned_rels.findRef(rel)==-1) { + m_master_owned_rels.append(rel); + } + } + else {//not owned + if (m_master_rels.findRef(rel)==-1) { + m_master_rels.append(rel); + } + } + } + else if (rel->detailsIndex()==this) { + if (m_details_rels.findRef(rel)==-1) { + m_details_rels.append(rel); + } + } +} + +void IndexSchema::detachRelationship(Relationship *rel) +{ + if (!rel) + return; + m_master_owned_rels.take( m_master_owned_rels.findRef(rel) ); //for sanity + m_master_rels.take( m_master_rels.findRef(rel) ); //for sanity + m_details_rels.take( m_details_rels.findRef(rel) ); //for sanity +} diff --git a/kexi/kexidb/indexschema.h b/kexi/kexidb/indexschema.h new file mode 100644 index 00000000..a8bec433 --- /dev/null +++ b/kexi/kexidb/indexschema.h @@ -0,0 +1,209 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_INDEX_H +#define KEXIDB_INDEX_H + +#include <qvaluelist.h> +#include <qstring.h> + +#include <kexidb/fieldlist.h> +#include <kexidb/schemadata.h> +#include <kexidb/relationship.h> + +namespace KexiDB { + +class Connection; +class TableSchema; +class QuerySchema; +class Relationship; + +/*! @short Provides information about database index that can be created for a database table. + + IndexSchema object stores information about table fields that + defines this index and additional properties like: whether index is unique + or primary key (requires unique). Single-field index can be also auto generated. +*/ +class KEXI_DB_EXPORT IndexSchema : public FieldList, public SchemaData +{ + public: + typedef QPtrList<IndexSchema> List; + typedef QPtrListIterator<IndexSchema> ListIterator; + + /*! Constructs empty index schema object + that is assigned to \a table, and will be owned by this table. + Any fields added with addField() won't be owned by index, + but by its table. Do not forget to add these fields to table, + because adding these to IndexSchema is not enough. + */ + IndexSchema(TableSchema *tableSchema); + + /*! Copy constructor. Copies all attributes from index \a idx, and + fields assigned with it but the fields are taken (by name) from + \a parentTable, not from \a idx itself, so it's possible to copy of index + for a copy of table. + + To copy an index within the same table it's enough to call: + \code + new IndexSchema(idx, *idx.table()); + \endcode + @todo All relationships should be also copied + */ + IndexSchema(const IndexSchema& idx, TableSchema& parentTable); + + /*! Destroys the index. Field objects are not deleted. + All Relationship objects listed in masterRelationships() list + are destroyed (these are also detached from + detail-side indices before destruction). + Relationship objects listed in detailsRelationships() are not touched. */ + virtual ~IndexSchema(); + + /*! Adds field at the end of field list. + Field will not be owned by index. Field must belong to a table + the index is bulit on, otherwise field couldn't be added. */ + virtual FieldList& addField(Field *field); + + /*! \return table that index is defined for. */ + TableSchema* table() const; + + /*! \return list of relationships from the table (of this index), + i.e. any such relationship in which this table is at 'master' side. + See Relationship class documentation for more information. + All objects listed here will be automatically destroyed on this IndexSchema object destruction. */ + Relationship::List* masterRelationships() { return &m_master_rels; } + + /*! \return list of relationships to the table (of this index), + i.e. any such relationship in which this table is at 'details' side. + See Relationship class documentation for more information. */ + Relationship::List* detailsRelationships() { return &m_details_rels; } + + /*! Attaches relationship definition \a rel to this IndexSchema object. + If \a rel relationship has this IndexSchema defined at the master-side, + \a rel is added to the list of master relationships (available with masterRelationships()). + If \a rel relationship has this IndexSchema defined at the details-side, + \a rel is added to the list of details relationships (available with detailsRelationships()). + For the former case, attached \a rel object is now owned by this IndexSchema object. + + Note: call detachRelationship() for IndexSchema object that \a rel + was previously attached to, if any. */ + void attachRelationship(Relationship *rel); + + /*! Detaches relationship definition \a rel for this IndexSchema object + from the list of master relationships (available with masterRelationships()), + or from details relationships list, depending for which side of the relationship + is this IndexSchem object assigned. + + Note: If \a rel was detached from masterRelationships() list, + this object now has no parent, so you need to attach it to somewhere or destruct it. + */ + void detachRelationship(Relationship *rel); + + /*! \return true if index is auto-generated. + Auto-generated index is one-field index + that was automatically generated + for CREATE TABLE statement when the field has + UNIQUE or PRIMARY KEY constraint enabled. + + Any newly created IndexSchema object + has this flag set to false. + + This flag is handled internally by TableSchema. + It can be usable for GUI application if we do not + want display implicity/auto generated indices + on the indices list or we if want to show these + indices to the user in a special way. + */ + bool isAutoGenerated() const; + + /*! \return true if this index is primary key of its table. + This can be one or multifield. */ + bool isPrimaryKey() const; + + /*! Sets PRIMARY KEY flag. \sa isPrimary(). + Note: Setting PRIMARY KEY on (true), + UNIQUE flag will be also implicity set. */ + void setPrimaryKey(bool set); + + /*! \return true if this is unique index. + This can be one or multifield. */ + bool isUnique() const; + + /*! Sets UNIQUE flag. \sa isUnique(). + Note: Setting UNIQUE off (false), PRIMARY KEY flag will + be also implicity set off, because this UNIQUE + is the requirement for PRIMARY KEYS. */ + void setUnique(bool set); + + /*! \return String for debugging purposes. */ + virtual QString debugString(); + protected: + + /*! Internal constructor for convenience. + Constructs a new index schema object + that is assigned, and adds one \a field to it. + The field must be assigned to a table. + This results in the implicit index that is usually used interanlly + to declare foreign keys, used in relationships. + */ +// IndexSchema(Field* singleField); + + /*! Sets auto-generated flag. This method should be called only + from TableSchema code + \sa isAutoGenerated(). */ + void setAutoGenerated(bool set); + + /*! If \a set is true, declares that the index defines a foreign key, + created implicity for Relationship object. Setting this to true, implies + clearing 'primary key', 'unique' and 'auto generated' flags. + If this index contains just single field, it's 'foreign field' + flag will be set to true as well. */ + void setForeignKey(bool set); + + /*! Internal version of attachRelationship(). If \a ownedByMaster is true, + attached \a rel object will be owned by this index. */ + void attachRelationship(Relationship *rel, bool ownedByMaster); + + TableSchema *m_tableSchema; //! table on that index is built + + /*! a list of master relationships for the table (of this index), + this index is a master key for these relationships + and therefore - owner of these */ + + Relationship::List m_master_owned_rels; + + /*! a list of master relationships that are not owned by this schema */ + Relationship::List m_master_rels; + + /*! a list of relationships to table (of this index) */ + Relationship::List m_details_rels; + + bool m_primary : 1; + bool m_unique : 1; + bool m_isAutoGenerated : 1; + bool m_isForeignKey : 1; + + friend class Connection; + friend class TableSchema; + friend class QuerySchema; + friend class Relationship; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/kexidb.pro b/kexi/kexidb/kexidb.pro new file mode 100644 index 00000000..d16e3f59 --- /dev/null +++ b/kexi/kexidb/kexidb.pro @@ -0,0 +1,51 @@ +TEMPLATE = lib + +include( $(KEXI)/kexidb/common.pro ) + +# needed to export library classes: +DEFINES += MAKE_KEXI_DB_LIB + +TARGET = kexidb$$KDEBUG + +DEFINES += YYERROR_VERBOSE=1 + +system( bash kmoc ) + +SOURCES = \ +object.cpp \ +drivermanager.cpp \ +driver.cpp \ +driver_p.cpp \ +admin.cpp \ +connectiondata.cpp \ +connection.cpp \ +utils.cpp \ +field.cpp \ +schemadata.cpp \ +tableschema.cpp \ +queryschema.cpp \ +queryschemaparameter.cpp \ +transaction.cpp \ +indexschema.cpp \ +cursor.cpp \ +fieldlist.cpp \ +global.cpp \ +relationship.cpp \ +roweditbuffer.cpp \ +msghandler.cpp \ +dbobjectnamevalidator.cpp \ +fieldvalidator.cpp \ +dbproperties.cpp \ +\ +parser/parser.cpp \ +parser/parser_p.cpp \ +parser/sqlparser.cpp \ +parser/sqlscanner.cpp \ +expression.cpp \ +keywords.cpp \ +preparedstatement.cpp \ +alter.cpp \ +lookupfieldschema.cpp \ +simplecommandlineapp.cpp + +#HEADERS = diff --git a/kexi/kexidb/kexidb_driver.desktop b/kexi/kexidb/kexidb_driver.desktop new file mode 100644 index 00000000..db0e7172 --- /dev/null +++ b/kexi/kexidb/kexidb_driver.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Kexi/DBDriver +Comment=Kexi SQL-Driver plugin +Comment[ar]=ملحق سوّاقة SQL لدى Kexi +Comment[bg]=Приставка на Kexi за SQL драйвери +Comment[br]=Lugent SQL-Driver evit Kexi +Comment[ca]=Extensió del controlador SQL de Kexi +Comment[cy]=Ategyn Gyrrydd-SQL Kexi +Comment[da]=Kexi SQL-driver-plugin +Comment[de]=Kexi SQL-Treiber-Modul +Comment[el]=Πρόσθετο οδηγού SQL του Kexi +Comment[eo]=Kexi SQL-pelila kromaĵo +Comment[es]=Complemento de SQL-Driver de Kexi +Comment[et]=Kexi SQL-draiveri plugin +Comment[eu]=Kexi-en SQL-kontrolatzailearen plugina +Comment[fa]=وصلۀ گردانندۀ Kexi SQL +Comment[fi]=Kexi SQL-laajennus +Comment[fr]=Module de pilotage SQL de Kexi +Comment[fy]=Kexi SQL-stjoerprogramma plugin +Comment[gl]=Plugin do Controlador de SQL de Kexi +Comment[he]=תוסף מנהל התקן SQL ל־Kexi +Comment[hr]=Dodatak za Kexi SQL upravljački program +Comment[hu]=Kexi SQL-elérési bővítőmodul +Comment[is]=Kexi SQL-rekil íforrit +Comment[it]=Driver SQL per Kexi +Comment[ja]=Kexi SQL ドライバ プラグイン +Comment[km]=កម្មវិធីជំនួយកម្មវិធីបញ្ជា SQL សម្រាប់ Kexi +Comment[lo]=ປລັກອິນພາບ +Comment[lt]=Kexi SQL-Driver įskiepis +Comment[lv]=Kexi SQL-Draivera spraudnis +Comment[ms]=Plugin Pemacu SQL Kexi +Comment[nb]=SQL-drivermodul for Kexi +Comment[nds]=SQL-Drievermoduul för Kexi +Comment[ne]=केक्सी SQL-ड्राइभर प्लगइन +Comment[nl]=Kexi SQL-stuurprogramma-plugin +Comment[nn]=SQL-drivarmodul for Kexi +Comment[pl]=Wtyczka sterownika SQL dla programu Kexi +Comment[pt]='Plugin' do Controlador de SQL do Kexi +Comment[pt_BR]=Plugin de Driver-SQL do Kexi +Comment[ru]=Модуль драйвера SQL Kexi +Comment[se]=Kexi SQL-stivrran lassemoduvla +Comment[sk]=Modul ovládača SQL pre Kexi +Comment[sl]=Vstavek Kexi SQL-Driver +Comment[sr]=SQL-управљачки прикључак Kexi-ја +Comment[sr@Latn]=SQL-upravljački priključak Kexi-ja +Comment[sv]=Kexi SQL-insticksdrivrutin +Comment[ta]=kexi SQL இயக்கி சொருகு +Comment[tg]=Модули драйвери Kexi SQL +Comment[tr]=Kexi SQl-Sürücüsü eki +Comment[uk]=Втулок драйвера SQL Kexi +Comment[uz]=Kexi SQL-drayver plagini +Comment[uz@cyrillic]=Kexi SQL-драйвер плагини +Comment[zh_CN]=Kexi SQL 驱动插件 +Comment[zh_TW]=Kexi SQL-驅動外掛程式 + +[PropertyDef::X-Kexi-FileDBDriverMime] +Type=QString + +[PropertyDef::X-Kexi-FileDBDriverMimeList] +Type=QStringList + +[PropertyDef::X-Kexi-DriverName] +Type=QString + +[PropertyDef::X-Kexi-DriverType] +Type=QString + +[PropertyDef::X-Kexi-KexiDBVersion] +Type=QString + +[PropertyDef::X-Kexi-DoNotAllowProjectImportingTo] +Type=bool diff --git a/kexi/kexidb/kexidb_export.h b/kexi/kexidb/kexidb_export.h new file mode 100644 index 00000000..a9f4a640 --- /dev/null +++ b/kexi/kexidb/kexidb_export.h @@ -0,0 +1,61 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + Copyright (C) 2005 Martin Ellis <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ +#ifndef _KEXIDB_EXPORT_H_ +#define _KEXIDB_EXPORT_H_ + +#ifdef __cplusplus +# include <kdeversion.h> /* this will also include <kdelibs_export.h>, if available */ +#endif +/* KDE_EXPORT will be defined multiple times without this on kdelibs 3.3 (tested on 3.3.1) */ +#include <kdemacros.h> + +/* workaround for KDElibs < 3.2 on !win32 */ +#ifndef KDE_EXPORT +# define KDE_EXPORT +#endif + +/* TODO: #include <koffice_export.h> ??? */ +#ifdef MAKE_KEXI_DB_LIB +# define KEXI_DB_EXPORT KDE_EXPORT +#elif defined(KDE_MAKE_LIB) +# define KEXI_DB_EXPORT KDE_IMPORT +#else +# define KEXI_DB_EXPORT +#endif + +#ifdef MAKE_KEXIMIGR_LIB +# define KEXIMIGR_EXPORT KDE_EXPORT +#elif defined(KDE_MAKE_LIB) +# define KEXIMIGR_EXPORT KDE_IMPORT +#else +# define KEXIMIGR_EXPORT //for apps +#endif + +/* -- compile-time settings -- */ +#if defined(Q_WS_WIN) || defined(KEXI_OPTIONS) +/* defined in a .pro file or 'KEXI_OPTIONS' env. variable */ +#else + +#endif + +/* Might want to add GUI defines here if widgets are to be + * distributed as part of kexidb - mart */ + +#endif //KEXI_EXPORT_H diff --git a/kexi/kexidb/keywords.cpp b/kexi/kexidb/keywords.cpp new file mode 100644 index 00000000..05563497 --- /dev/null +++ b/kexi/kexidb/keywords.cpp @@ -0,0 +1,92 @@ + /* + * This file has been automatically generated from + * koffice/kexi/tools/sql_keywords/sql_keywords.sh and + * koffice/kexi/kexidb/parser/sqlscanner.l + * and koffice/kexi/tools/sql_keywords/kexi__reserved. + * + * Please edit the sql_keywords.sh, not this file! + */ +#include <driver_p.h> + +namespace KexiDB { + const char* DriverPrivate::kexiSQLKeywords[] = { + "AND", + "AS", + "CREATE", + "FROM", + "IN", + "INTEGER", + "IS", + "JOIN", + "LEFT", + "LIKE", + "NOT", + "NULL", + "ON", + "OR", + "RIGHT", + "SELECT", + "SIMILAR", + "TABLE", + "TO", + "WHERE", + "XOR", + "AFTER", + "ALL", + "ASC", + "BEFORE", + "BEGIN", + "BETWEEN", + "BY", + "CASCADE", + "CASE", + "CHECK", + "COLLATE", + "COMMIT", + "CONSTRAINT", + "CROSS", + "DATABASE", + "DEFAULT", + "DELETE", + "DESC", + "DISTINCT", + "DROP", + "END", + "ELSE", + "EXPLAIN", + "FOR", + "FOREIGN", + "FULL", + "GROUP", + "HAVING", + "IGNORE", + "INDEX", + "INNER", + "INSERT", + "INTO", + "KEY", + "LIMIT", + "MATCH", + "NATURAL", + "OFFSET", + "ORDER", + "OUTER", + "PRIMARY", + "REFERENCES", + "REPLACE", + "RESTRICT", + "ROLLBACK", + "ROW", + "SET", + "TEMPORARY", + "THEN", + "TRANSACTION", + "UNION", + "UNIQUE", + "UPDATE", + "USING", + "VALUES", + "WHEN", + 0 + }; +} diff --git a/kexi/kexidb/lookupfieldschema.cpp b/kexi/kexidb/lookupfieldschema.cpp new file mode 100644 index 00000000..f21ec588 --- /dev/null +++ b/kexi/kexidb/lookupfieldschema.cpp @@ -0,0 +1,394 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "lookupfieldschema.h" +#include "utils.h" + +#include <qdom.h> +#include <qvariant.h> +#include <kdebug.h> + +using namespace KexiDB; + + +LookupFieldSchema::RowSource::RowSource() +: m_type(NoType) +, m_values(0) +{ +} + +LookupFieldSchema::RowSource::~RowSource() +{ + delete m_values; +} + +void LookupFieldSchema::RowSource::setName(const QString& name) +{ + m_name = name; + if (m_values) + m_values->clear(); +} + +QString LookupFieldSchema::RowSource::typeName() const +{ + switch (m_type) { + case Table: return "table"; + case Query: return "query"; + case SQLStatement: return "sql"; + case ValueList: return "valuelist"; + case FieldList: return "fieldlist"; + default:; + } + return QString::null; +} + +void LookupFieldSchema::RowSource::setTypeByName( const QString& typeName ) +{ + if (typeName=="table") + setType( Table ); + else if (typeName=="query") + setType( Query ); + else if (typeName=="sql") + setType( SQLStatement ); + else if (typeName=="valuelist") + setType( ValueList ); + else if (typeName=="fieldlist") + setType( FieldList ); + else + setType( NoType ); +} + +QStringList LookupFieldSchema::RowSource::values() const +{ + return m_values ? *m_values : QStringList(); +} + +void LookupFieldSchema::RowSource::setValues(const QStringList& values) +{ + m_name = QString::null; + if (m_values) + *m_values = values; + else + m_values = new QStringList(values); +} + +QString LookupFieldSchema::RowSource::debugString() const +{ + return QString("rowSourceType:'%1' rowSourceName:'%2' rowSourceValues:'%3'\n") + .arg(typeName()).arg(name()).arg(m_values ? m_values->join("|") : QString::null); +} + +void LookupFieldSchema::RowSource::debug() const +{ + KexiDBDbg << debugString() << endl; +} + +//--------------------------------------- + +LookupFieldSchema::LookupFieldSchema() + : m_boundColumn(-1) + , m_maximumListRows(KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS) + , m_displayWidget(KEXIDB_LOOKUP_FIELD_DEFAULT_DISPLAY_WIDGET) + , m_columnHeadersVisible(KEXIDB_LOOKUP_FIELD_DEFAULT_HEADERS_VISIBLE) + , m_limitToList(KEXIDB_LOOKUP_FIELD_DEFAULT_LIMIT_TO_LIST) +{ +} + +LookupFieldSchema::~LookupFieldSchema() +{ +} + +void LookupFieldSchema::setMaximumListRows(uint rows) +{ + if (rows==0) + m_maximumListRows = KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS; + else if (rows>KEXIDB_LOOKUP_FIELD_MAX_LIST_ROWS) + m_maximumListRows = KEXIDB_LOOKUP_FIELD_MAX_LIST_ROWS; + else + m_maximumListRows = rows; +} + +QString LookupFieldSchema::debugString() const +{ + QString columnWidthsStr; + foreach (QValueList<int>::ConstIterator, it, m_columnWidths) { + if (!columnWidthsStr.isEmpty()) + columnWidthsStr.append(";"); + columnWidthsStr.append( QString::number(*it) ); + } + + QString visibleColumnsString; + foreach (QValueList<uint>::ConstIterator, it, m_visibleColumns) { + if (!visibleColumnsString.isEmpty()) + visibleColumnsString.append(";"); + visibleColumnsString.append(QString::number(*it)); + } + + return QString("LookupFieldSchema( %1\n" + " boundColumn:%2 visibleColumns:%3 maximumListRows:%4 displayWidget:%5\n" + " columnHeadersVisible:%6 limitToList:%7\n" + " columnWidths:%8 )") + .arg(m_rowSource.debugString()) + .arg(m_boundColumn).arg(visibleColumnsString).arg(m_maximumListRows) + .arg( m_displayWidget==ComboBox ? "ComboBox" : "ListBox") + .arg(m_columnHeadersVisible).arg(m_limitToList) + .arg(columnWidthsStr); +} + +void LookupFieldSchema::debug() const +{ + KexiDBDbg << debugString() << endl; +} + +/* static */ +LookupFieldSchema *LookupFieldSchema::loadFromDom(const QDomElement& lookupEl) +{ + LookupFieldSchema *lookupFieldSchema = new LookupFieldSchema(); + for (QDomNode node = lookupEl.firstChild(); !node.isNull(); node = node.nextSibling()) { + QDomElement el = node.toElement(); + QString name( el.tagName() ); + if (name=="row-source") { + /*<row-source> + empty + | <type>table|query|sql|valuelist|fieldlist</type> #required because there can be table and query with the same name + "fieldlist" (basically a list of column names of a table/query, + "Field List" as in MSA) + <name>string</name> #table/query name, etc. or KEXISQL SELECT QUERY + <values><value>...</value> #for "valuelist" type + <value>...</value> + </values> + </row-source> */ + for (el = el.firstChild().toElement(); !el.isNull(); el=el.nextSibling().toElement()) { + if (el.tagName()=="type") + lookupFieldSchema->rowSource().setTypeByName( el.text() ); + else if (el.tagName()=="name") + lookupFieldSchema->rowSource().setName( el.text() ); +//! @todo handle fieldlist (retrieve from external table or so?), use lookupFieldSchema.rowSource().setValues() + } + } + else if (name=="bound-column") { + /* <bound-column> + <number>number</number> #in later implementation there can be more columns + </bound-column> */ + const QVariant val = KexiDB::loadPropertyValueFromDom( el.firstChild() ); + if (val.type()==QVariant::Int) + lookupFieldSchema->setBoundColumn( val.toInt() ); + } + else if (name=="visible-column") { + /* <visible-column> #a column that has to be visible in the combo box + <number>number 1</number> + <number>number 2</number> + [..] + </visible-column> */ + QValueList<uint> list; + for (QDomNode childNode = el.firstChild(); !childNode.isNull(); childNode = childNode.nextSibling()) { + const QVariant val = KexiDB::loadPropertyValueFromDom( childNode ); + if (val.type()==QVariant::Int) + list.append( val.toUInt() ); + } + lookupFieldSchema->setVisibleColumns( list ); + } + else if (name=="column-widths") { + /* <column-widths> #column widths, -1 means 'default' + <number>int</number> + ... + <number>int</number> + </column-widths> */ + QVariant val; + QValueList<int> columnWidths; + for (el = el.firstChild().toElement(); !el.isNull(); el=el.nextSibling().toElement()) { + QVariant val = KexiDB::loadPropertyValueFromDom( el ); + if (val.type()==QVariant::Int) + columnWidths.append(val.toInt()); + } + lookupFieldSchema->setColumnWidths( columnWidths ); + } + else if (name=="show-column-headers") { + /* <show-column-headers> + <bool>true/false</bool> + </show-column-headers> */ + const QVariant val = KexiDB::loadPropertyValueFromDom( el.firstChild() ); + if (val.type()==QVariant::Bool) + lookupFieldSchema->setColumnHeadersVisible( val.toBool() ); + } + else if (name=="list-rows") { + /* <list-rows> + <number>1..100</number> + </list-rows> */ + const QVariant val = KexiDB::loadPropertyValueFromDom( el.firstChild() ); + if (val.type()==QVariant::Int) + lookupFieldSchema->setMaximumListRows( val.toUInt() ); + } + else if (name=="limit-to-list") { + /* <limit-to-list> + <bool>true/false</bool> + </limit-to-list> */ + const QVariant val = KexiDB::loadPropertyValueFromDom( el.firstChild() ); + if (val.type()==QVariant::Bool) + lookupFieldSchema->setLimitToList( val.toBool() ); + } + else if (name=="display-widget") { + if (el.text()=="combobox") + lookupFieldSchema->setDisplayWidget( LookupFieldSchema::ComboBox ); + else if (el.text()=="listbox") + lookupFieldSchema->setDisplayWidget( LookupFieldSchema::ListBox ); + } + } + return lookupFieldSchema; +} + +/* static */ +void LookupFieldSchema::saveToDom(LookupFieldSchema& lookupSchema, QDomDocument& doc, QDomElement& parentEl) +{ + QDomElement lookupColumnEl, rowSourceEl, rowSourceTypeEl, nameEl; + if (!lookupSchema.rowSource().name().isEmpty()) { + lookupColumnEl = doc.createElement("lookup-column"); + parentEl.appendChild( lookupColumnEl ); + + rowSourceEl = doc.createElement("row-source"); + lookupColumnEl.appendChild( rowSourceEl ); + + rowSourceTypeEl = doc.createElement("type"); + rowSourceEl.appendChild( rowSourceTypeEl ); + rowSourceTypeEl.appendChild( doc.createTextNode(lookupSchema.rowSource().typeName()) ); //can be empty + + nameEl = doc.createElement("name"); + rowSourceEl.appendChild( nameEl ); + nameEl.appendChild( doc.createTextNode(lookupSchema.rowSource().name()) ); + } + + const QStringList& values( lookupSchema.rowSource().values() ); + if (!values.isEmpty()) { + QDomElement valuesEl( doc.createElement("values") ); + rowSourceEl.appendChild( valuesEl ); + for (QStringList::ConstIterator it = values.constBegin(); it!=values.constEnd(); ++it) { + QDomElement valueEl( doc.createElement("value") ); + valuesEl.appendChild( valueEl ); + valueEl.appendChild( doc.createTextNode(*it) ); + } + } + + if (lookupSchema.boundColumn()>=0) + KexiDB::saveNumberElementToDom(doc, lookupColumnEl, "bound-column", lookupSchema.boundColumn()); + + QValueList<uint> visibleColumns(lookupSchema.visibleColumns()); + if (!visibleColumns.isEmpty()) { + QDomElement visibleColumnEl( doc.createElement("visible-column") ); + lookupColumnEl.appendChild( visibleColumnEl ); + foreach (QValueList<uint>::ConstIterator, it, visibleColumns) { + QDomElement numberEl( doc.createElement("number") ); + visibleColumnEl.appendChild( numberEl ); + numberEl.appendChild( doc.createTextNode( QString::number(*it) ) ); + } + } + + const QValueList<int> columnWidths(lookupSchema.columnWidths()); + if (!columnWidths.isEmpty()) { + QDomElement columnWidthsEl( doc.createElement("column-widths") ); + lookupColumnEl.appendChild( columnWidthsEl ); + for (QValueList<int>::ConstIterator it = columnWidths.constBegin(); it!=columnWidths.constEnd(); ++it) { + QDomElement columnWidthEl( doc.createElement("number") ); + columnWidthsEl.appendChild( columnWidthEl ); + columnWidthEl.appendChild( doc.createTextNode( QString::number(*it) ) ); + } + } + + if (lookupSchema.columnHeadersVisible()!=KEXIDB_LOOKUP_FIELD_DEFAULT_HEADERS_VISIBLE) + KexiDB::saveBooleanElementToDom(doc, lookupColumnEl, "show-column-headers", lookupSchema.columnHeadersVisible()); + if (lookupSchema.maximumListRows()!=KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS) + KexiDB::saveNumberElementToDom(doc, lookupColumnEl, "list-rows", lookupSchema.maximumListRows()); + if (lookupSchema.limitToList()!=KEXIDB_LOOKUP_FIELD_DEFAULT_LIMIT_TO_LIST) + KexiDB::saveBooleanElementToDom(doc, lookupColumnEl, "limit-to-list", lookupSchema.limitToList()); + + if (lookupSchema.displayWidget()!=KEXIDB_LOOKUP_FIELD_DEFAULT_DISPLAY_WIDGET) { + QDomElement displayWidgetEl( doc.createElement("display-widget") ); + lookupColumnEl.appendChild( displayWidgetEl ); + displayWidgetEl.appendChild( + doc.createTextNode( (lookupSchema.displayWidget()==ListBox) ? "listbox" : "combobox" ) ); + } +} + +//static +bool LookupFieldSchema::setProperty( + LookupFieldSchema& lookup, const QCString& propertyName, const QVariant& value ) +{ + bool ok; + if ("rowSource" == propertyName || "rowSourceType" == propertyName || "rowSourceValues" == propertyName) { + LookupFieldSchema::RowSource rowSource( lookup.rowSource() ); + if ("rowSource" == propertyName) + rowSource.setName(value.toString()); + else if ("rowSourceType" == propertyName) + rowSource.setTypeByName(value.toString()); + else if ("rowSourceValues" == propertyName) + rowSource.setValues(value.toStringList()); + lookup.setRowSource(rowSource); + } + else if ("boundColumn" == propertyName ) { + const int ival = value.toInt(&ok); + if (!ok) + return false; + lookup.setBoundColumn( ival ); + } + else if ("visibleColumn" == propertyName ) { + QValueList<QVariant> variantList; + if (value.type()==QVariant::Int) { +//! @todo Remove this case: it's for backward compatibility with Kexi's 1.1.2 table designer GUI +//! supporting only single lookup column. + variantList.append( value.toInt() ); + } + else { + variantList = value.toList(); + } + QValueList<uint> visibleColumns; + foreach (QValueList<QVariant>::ConstIterator, it, variantList) { + const uint ival = (*it).toUInt(&ok); + if (!ok) + return false; + visibleColumns.append( ival ); + } + lookup.setVisibleColumns( visibleColumns ); + } + else if ("columnWidths" == propertyName ) { + QValueList<QVariant> variantList( value.toList() ); + QValueList<int> widths; + foreach (QValueList<QVariant>::ConstIterator, it, variantList) { + const uint ival = (*it).toInt(&ok); + if (!ok) + return false; + widths.append( ival ); + } + lookup.setColumnWidths( widths ); + } + else if ("showColumnHeaders" == propertyName ) { + lookup.setColumnHeadersVisible( value.toBool() ); + } + else if ("listRows" == propertyName ) { + lookup.setMaximumListRows( value.toBool() ); + } + else if ("limitToList" == propertyName ) { + lookup.setLimitToList( value.toBool() ); + } + else if ("displayWidget" == propertyName ) { + const uint ival = value.toUInt(&ok); + if (!ok || ival > LookupFieldSchema::ListBox) + return false; + lookup.setDisplayWidget((LookupFieldSchema::DisplayWidget)ival); + } + return true; +} diff --git a/kexi/kexidb/lookupfieldschema.h b/kexi/kexidb/lookupfieldschema.h new file mode 100644 index 00000000..9494b348 --- /dev/null +++ b/kexi/kexidb/lookupfieldschema.h @@ -0,0 +1,236 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIDB_LOOKUPFIELDSCHEMA_H +#define KEXIDB_LOOKUPFIELDSCHEMA_H + +#include <qvaluelist.h> +#include <qstringlist.h> + +class QDomElement; +class QDomDocument; +class QVariant; + +namespace KexiDB { + +//! default value for LookupFieldSchema::columnHeadersVisible() +#define KEXIDB_LOOKUP_FIELD_DEFAULT_HEADERS_VISIBLE false + +//! default value for LookupFieldSchema::maximumListRows() +#define KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS 8 + +//! maximum value for LookupFieldSchema::maximumListRows() +#define KEXIDB_LOOKUP_FIELD_MAX_LIST_ROWS 100 + +//! default value for LookupFieldSchema::limitToList() +#define KEXIDB_LOOKUP_FIELD_DEFAULT_LIMIT_TO_LIST true + +//! default value for LookupFieldSchema::displayWidget() +#define KEXIDB_LOOKUP_FIELD_DEFAULT_DISPLAY_WIDGET KexiDB::LookupFieldSchema::ComboBox + + +//! @short Provides information about lookup field's setup. +/*! + LookupFieldSchema object is owned by TableSchema and created upon creating or retrieving the table schema + from the database metadata. + + @see LookupFieldSchema *TableSchema::lookupFieldSchema( Field& field ) const +*/ +class KEXI_DB_EXPORT LookupFieldSchema +{ + public: + + //! Row source information that can be specified for the lookup field schema + class KEXI_DB_EXPORT RowSource { + public: + //! Row source type + enum Type { + NoType, //!< used for invalid schema + Table, //!< table as lookup row source + Query, //!< named query as lookup row source + SQLStatement, //!< anonymous query as lookup row source + ValueList, //!< a fixed list of values as lookup row source + FieldList //!< a list of column names from a table/query will be displayed + }; + + RowSource(); + ~RowSource(); + + /*! @return row source type: table, query, anonymous; in the future it will + be also fixed value list and field list. The latter is basically a list + of column names of a table/query, "Field List" in MSA. */ + Type type() const { return m_type; } + + /*! Sets row source type to \a type. */ + void setType(Type type) { m_type = type; } + + /*! @return row source type name. @see setTypeByName() */ + QString typeName() const; + + /*! Sets row source type by name using \a typeName. Accepted (cast sensitive) + names are "table", "query", "sql", "valuelist", "fieldlist". + For other value NoType type is set. */ + void setTypeByName( const QString& typeName ); + + /*! @return a string for row source: table name, query name or anonymous query + provided as KEXISQL string. If rowSourceType() is a ValueList, + rowSourceValues() should be used instead. If rowSourceType() is a FieldList, + rowSource() should return table or query name. */ + QString name() const { return m_name; } + + /*! Sets row source value. @see value() */ + void setName(const QString& name); + + /*! @return row source values specified if type() is ValueList. */ + QStringList values() const; + + /*! Sets row source values used if type() is ValueList. + Using it clears name (see name()). */ + void setValues(const QStringList& values); + + /*! \return String for debugging purposes. */ + QString debugString() const; + + /*! Shows debug information. */ + void debug() const; + private: + Type m_type; + QString m_name; + QStringList *m_values; + }; + + LookupFieldSchema(); + + ~LookupFieldSchema(); + + /*! @return row source information for the lookup field schema */ + RowSource& rowSource() { return m_rowSource; } + + /*! Sets row source for the lookup field schema */ + void setRowSource(const RowSource& rowSource) { m_rowSource = rowSource; } + + /*! @return bound column: an integer specifying a column that is bound + (counted from 0). -1 means unspecified value. */ +//! @todo in later implementation there can be more columns + int boundColumn() const { return m_boundColumn; } + + /*! Sets bound column number to \a column. @see boundColumn() */ + void setBoundColumn(int column) { m_boundColumn = column>=0 ? column : -1; } + + /*! @return a list of visible column: a list of integers specifying a column that has + to be visible in the combo box (counted from 0). + Empty list means unspecified value. */ + QValueList<uint> visibleColumns() const { return m_visibleColumns; } + + /*! Sets a list of visible columns to \a list. + Columns will be separated with a single space character when displayed. */ + void setVisibleColumns(const QValueList<uint>& list) { m_visibleColumns = list; } + + /*! A helper method. + If visibleColumns() contains one item, this item is returned (a typical case). + If visibleColumns() contains no item, -1 is returned. + If visibleColumns() multiple items, \a fieldsCount - 1 is returned. */ + inline int visibleColumn(uint fieldsCount) const { + if (m_visibleColumns.count()==1) + return (m_visibleColumns.first() < fieldsCount) + ? (int)m_visibleColumns.first() : -1; + if (m_visibleColumns.isEmpty()) + return -1; + return fieldsCount - 1; + } + + /*! @return a number of ordered integers specifying column widths; + -1 means 'default width' for a given column. */ + const QValueList<int> columnWidths() const { return m_columnWidths; } + + /*! Sets column widths. @see columnWidths */ + void setColumnWidths(const QValueList<int>& widths) { m_columnWidths = widths; } + + /*! @return true if column headers are visible in the associated + combo box popup or the list view. The default is false. */ + bool columnHeadersVisible() const { return m_columnHeadersVisible; } + + /*! Sets "column headers visibility" flag. @see columnHeadersVisible() */ + void setColumnHeadersVisible(bool set) { m_columnHeadersVisible = set; } + + /*! @return integer property specifying a maximum number of rows + that can be displayed in a combo box popup or a list box. The default is + equal to KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS constant. */ + uint maximumListRows() const { return m_maximumListRows; } + + /*! Sets maximum number of rows that can be displayed in a combo box popup + or a list box. If \a rows is 0, KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS is set. + If \a rows is greater than KEXIDB_LOOKUP_FIELD_MAX_LIST_ROWS, + KEXIDB_LOOKUP_FIELD_MAX_LIST_ROWS is set. */ + void setMaximumListRows(uint rows); + + /*! @return true if , only values present on the list can be selected using + the combo box. The default is true. */ + bool limitToList() const { return m_limitToList; } + + /*! Sets "limit to list" flag. @see limitToList() */ + void setLimitToList(bool set) { m_limitToList = set; } + + //! used in displayWidget() + enum DisplayWidget { + ComboBox = 0, //!< (the default) combobox widget should be displayed in forms for this lookup field + ListBox = 1 //!< listbox widget should be displayed in forms for this lookup field + }; + + /*! @return the widget type that should be displayed within + the forms for this lookup field. The default is ComboBox. + For the Table View, combo box is always displayed. */ + DisplayWidget displayWidget() const { return m_displayWidget; } + + /*! Sets type of widget to display within the forms for this lookup field. @see displayWidget() */ + void setDisplayWidget(DisplayWidget widget) { m_displayWidget = widget; } + + /*! \return String for debugging purposes. */ + QString debugString() const; + + /*! Shows debug information. */ + void debug() const; + + /*! Loads data of lookup column schema from DOM tree. + The data can be outdated or invalid, so the app should handle such cases. + @return a new LookupFieldSchema object even if lookupEl contains no valid contents. */ + static LookupFieldSchema* loadFromDom(const QDomElement& lookupEl); + + /*! Saves data of lookup column schema to \a parentEl DOM element of \a doc document. */ + static void saveToDom(LookupFieldSchema& lookupSchema, QDomDocument& doc, QDomElement& parentEl); + + /*! Sets property of name \a propertyName and value \a value for the lookup schema \a lookup + \return true on successful set and false on failure because of invalid value or invalid property name. */ + static bool setProperty( + LookupFieldSchema& lookup, const QCString& propertyName, const QVariant& value ); + + protected: + RowSource m_rowSource; + int m_boundColumn; + QValueList<uint> m_visibleColumns; + QValueList<int> m_columnWidths; + uint m_maximumListRows; + DisplayWidget m_displayWidget; + bool m_columnHeadersVisible : 1; + bool m_limitToList : 1; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/msghandler.cpp b/kexi/kexidb/msghandler.cpp new file mode 100644 index 00000000..1cacae5e --- /dev/null +++ b/kexi/kexidb/msghandler.cpp @@ -0,0 +1,62 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2005 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/msghandler.h> + +using namespace KexiDB; + +MessageTitle::MessageTitle(Object* o, const QString& msg) + : m_obj(o) + , m_prevMsgTitle(o->m_msgTitle) +{ + m_obj->m_msgTitle = msg; +} + +MessageTitle::~MessageTitle() +{ + m_obj->m_msgTitle = m_prevMsgTitle; +} + +//------------------------------------------------ + +MessageHandler::MessageHandler(QWidget *parent) + : m_messageHandlerParentWidget(parent) + , m_enableMessages(true) +{ +} + +MessageHandler::~MessageHandler() +{ +} + +int MessageHandler::askQuestion( const QString& message, + KMessageBox::DialogType dlgType, KMessageBox::ButtonCode defaultResult, + const KGuiItem &buttonYes, + const KGuiItem &buttonNo, + const QString &dontShowAskAgainName, + int options ) +{ + Q_UNUSED(message); + Q_UNUSED(dlgType); + Q_UNUSED(buttonYes); + Q_UNUSED(buttonNo); + Q_UNUSED(dontShowAskAgainName); + Q_UNUSED(options); + return defaultResult; +} diff --git a/kexi/kexidb/msghandler.h b/kexi/kexidb/msghandler.h new file mode 100644 index 00000000..da907c7e --- /dev/null +++ b/kexi/kexidb/msghandler.h @@ -0,0 +1,98 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2005 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_MSGHANDLER_H +#define KEXIDB_MSGHANDLER_H + +#include <kexidb/object.h> +#include <qguardedptr.h> +#include <qwidget.h> + +namespace KexiDB { + +/*! A helper class for setting temporary message title for an KexiDB::Object. + Message title is a text prepended to error or warning messages. + Use it this way: + \code + KexiDB::MessageTitle title(myKexiDBObject, i18n("Terrible error occurred")); + \endcode + After leaving current from code block, object's message title will be reverted + to previous value. +*/ +class KEXI_DB_EXPORT MessageTitle +{ + public: + MessageTitle(KexiDB::Object* o, const QString& msg = QString::null); + ~MessageTitle(); + + protected: + Object* m_obj; + QString m_prevMsgTitle; +}; + +/*! A prototype for Message Handler usable + for reacting on messages sent by KexiDB::Object object(s). +*/ +class KEXI_DB_EXPORT MessageHandler +{ + public: + enum MessageType { Error, Sorry, Warning }; + + /*! Constructs mesage handler, \a parent is a widget that will be a parent + for displaying gui elements (e.g. message boxes). Can be 0 for non-gui usage. */ + MessageHandler(QWidget *parent = 0); + virtual ~MessageHandler(); + + /*! This method can be used to block/unblock messages. + Sometimes you are receiving both lower- and higher-level messages, + but you do not need to display two message boxes but only one (higher level with details). + All you need is to call enableMessages(false) before action that can fail + and restore messages by enableMessages(true) after the action. + See KexiMainWindowImpl::renameObject() implementation for example. */ + inline void enableMessages(bool enable) { m_enableMessages = enable; } + + /*! Shows error message with \a title (it is not caption) and details. */ + virtual void showErrorMessage(const QString &title, + const QString &details = QString::null) = 0; + + /*! Shows error message with \a msg text. Existing error message from \a obj object + is also copied, if present. */ + virtual void showErrorMessage(KexiDB::Object *obj, const QString& msg = QString::null) = 0; + + /*! Interactively asks a question. For GUI version, KMessageBox class is used. + See KMessageBox documentation for explanation of the parameters. + \a defaultResult is returned in case when no message handler is installed. + \a message should be i18n's string. + Value from KMessageBox::ButtonCode enum is returned. + Reimplement this. This implementation does nothing, just returns \a defaultResult. */ + virtual int askQuestion( const QString& message, + KMessageBox::DialogType dlgType, KMessageBox::ButtonCode defaultResult, + const KGuiItem &buttonYes=KStdGuiItem::yes(), + const KGuiItem &buttonNo=KStdGuiItem::no(), + const QString &dontShowAskAgainName = QString::null, + int options = KMessageBox::Notify ); + + protected: + QGuardedPtr<QWidget> m_messageHandlerParentWidget; + bool m_enableMessages : 1; +}; + +} + +#endif diff --git a/kexi/kexidb/object.cpp b/kexi/kexidb/object.cpp new file mode 100644 index 00000000..f4228bc7 --- /dev/null +++ b/kexi/kexidb/object.cpp @@ -0,0 +1,191 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2005 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/object.h> +#include <kexidb/error.h> +#include <kexidb/msghandler.h> + +#include <klocale.h> +#include <kdebug.h> + +using namespace KexiDB; + +#define ERRMSG(a) \ + { if (m_msgHandler) m_msgHandler->showErrorMessage(a); } + +Object::Object(MessageHandler* handler) +: m_previousServerResultNum(0) +, m_previousServerResultNum2(0) +, m_msgHandler(handler) +, d(0) //empty +{ + clearError(); +} + +Object::~Object() +{ +} + +#define STORE_PREV_ERR \ + m_previousServerResultNum = m_previousServerResultNum2; \ + m_previousServerResultName = m_previousServerResultName2; \ + m_previousServerResultNum2 = serverResult(); \ + m_previousServerResultName2 = serverResultName(); \ + KexiDBDbg << "Object ERROR: " << m_previousServerResultNum2 << ": " \ + << m_previousServerResultName2 <<endl + +void Object::setError( int code, const QString &msg ) +{ + STORE_PREV_ERR; + + m_errno=code; + m_errorSql = m_sql; + if (m_errno==ERR_OTHER && msg.isEmpty()) + m_errMsg = i18n("Unspecified error encountered"); + else + m_errMsg = msg; + m_hasError = code!=ERR_NONE; + + if (m_hasError) + ERRMSG(this); +} + +void Object::setError( const QString &msg ) +{ + setError( ERR_OTHER, msg ); +} + +void Object::setError( const QString& title, const QString &msg ) +{ + STORE_PREV_ERR; + + m_errno=ERR_OTHER; + QString origMsgTitle( m_msgTitle ); //store + + m_msgTitle += title; + m_errMsg = msg; + m_errorSql = m_sql; + m_hasError = true; + if (m_hasError) + ERRMSG(this); + + m_msgTitle = origMsgTitle; //revert +} + +void Object::setError( KexiDB::Object *obj, const QString& prependMessage ) +{ + setError( obj, obj ? obj->errorNum() : ERR_OTHER, prependMessage ); +} + +void Object::setError( KexiDB::Object *obj, int code, const QString& prependMessage ) +{ + if (obj && (obj->errorNum()!=0 || !obj->serverErrorMsg().isEmpty())) { + STORE_PREV_ERR; + + m_errno = obj->errorNum(); + m_hasError = obj->error(); + if (m_errno==0) { + m_errno = code; + m_hasError = true; + } + m_errMsg = (prependMessage.isEmpty() ? QString::null : (prependMessage + " ")) + + obj->errorMsg(); + m_sql = obj->m_sql; + m_errorSql = obj->m_errorSql; + m_serverResult = obj->serverResult(); + if (m_serverResult==0) //try copied + m_serverResult = obj->m_serverResult; + m_serverResultName = obj->serverResultName(); + if (m_serverResultName.isEmpty()) //try copied + m_serverResultName = obj->m_serverResultName; + m_serverErrorMsg = obj->serverErrorMsg(); + if (m_serverErrorMsg.isEmpty()) //try copied + m_serverErrorMsg = obj->m_serverErrorMsg; + //override + if (code!=0 && code!=ERR_OTHER) + m_errno = code; + if (m_hasError) + ERRMSG(this); + } + else { + setError( code!=0 ? code : ERR_OTHER, prependMessage ); + } +} + +void Object::clearError() +{ + m_errno = 0; + m_hasError = false; + m_errMsg = QString::null; + m_sql = QString::null; + m_errorSql = QString::null; + m_serverResult = 0; + m_serverResultName = QString::null; + m_serverErrorMsg = QString::null; + drv_clearServerResult(); +} + +QString Object::serverErrorMsg() +{ + return m_serverErrorMsg; +} + +int Object::serverResult() +{ + return m_serverResult; +} + +QString Object::serverResultName() +{ + return m_serverResultName; +} + +void Object::debugError() +{ + if (error()) { + KexiDBDbg << "KEXIDB ERROR: " << errorMsg() << endl; + QString s = serverErrorMsg(), sn = serverResultName(); + if (!s.isEmpty()) + KexiDBDbg << "KEXIDB SERVER ERRMSG: " << s << endl; + if (!sn.isEmpty()) + KexiDBDbg << "KEXIDB SERVER RESULT NAME: " << sn << endl; + if (serverResult()!=0) + KexiDBDbg << "KEXIDB SERVER RESULT #: " << serverResult() << endl; + } else + KexiDBDbg << "KEXIDB OK." << endl; +} + +int Object::askQuestion( const QString& message, + KMessageBox::DialogType dlgType, KMessageBox::ButtonCode defaultResult, + const KGuiItem &buttonYes, + const KGuiItem &buttonNo, + const QString &dontShowAskAgainName, + int options, + MessageHandler* msgHandler ) +{ + if (msgHandler) + return msgHandler->askQuestion(message, dlgType, defaultResult, buttonYes, buttonNo, + dontShowAskAgainName, options); + + if (m_msgHandler) + return m_msgHandler->askQuestion(message, dlgType, defaultResult, buttonYes, buttonNo, + dontShowAskAgainName, options); + + return defaultResult; +} diff --git a/kexi/kexidb/object.h b/kexi/kexidb/object.h new file mode 100644 index 00000000..aff98491 --- /dev/null +++ b/kexi/kexidb/object.h @@ -0,0 +1,186 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2005 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_OBJECT_H +#define KEXIDB_OBJECT_H + +#include <kexidb/error.h> +#include <kmessagebox.h> +#include <kstdguiitem.h> +#include <qstring.h> + +namespace KexiDB { + +class MessageHandler; + +/*! Prototype of KexiDB object, handles result of last operation. +*/ +class KEXI_DB_EXPORT Object +{ + public: + /*! \return true if there was error during last operation on the object. */ + bool error() const { return m_hasError; } + + /*! \return (localized) error message if there was error during last operation on the object, + else: 0. */ + const QString& errorMsg() const { return m_errMsg; } + + /*! \return error number of if there was error during last operation on the object, + else: 0. */ + int errorNum() const { return m_errno; } + + //! \return previous server result number, for error displaying purposes. + int previousServerResult() const { return m_previousServerResultNum; } + + QString previousServerResultName() const { return m_previousServerResultName; } + + /*! Sends errorMsg() to debug output. */ + void debugError(); + + /*! Clears error flag. + Also calls drv_clearServerResult(). + You can reimplement this method in subclasses to clear even more members, + but remember to also call Object::clearError(). */ + virtual void clearError(); + + /*! KexiDB library offers detailed error numbers using errorNum() + and detailed error i18n'd messages using errorMsg() - + this information is not engine-dependent (almost). + Use this in your application to give users more information on what's up. + + This method returns (non-i18n'd !) engine-specific error message, + if there was any error during last server-side operation, + otherwise null string. + Reimplement this for your driver + - default implementation just returns null string. + \sa serverErrorMsg() + */ + virtual QString serverErrorMsg(); + + /*! \return engine-specific last server-side operation result number. + Use this in your application to give users more information on what's up. + + Reimplement this for your driver - default implementation just returns 0. + Note that this result value is not the same as the one returned + by errorNum() (Object::m_errno member) + \sa serverErrorMsg(), drv_clearServerResult() + */ + virtual int serverResult(); + + /*! \return engine-specific last server-side operation result name, + (name for serverResult()). + Use this in your application to give users more information on what's up. + + Reimplement this for your driver - default implementation + just returns null string. + Note that this result name is not the same as the error message returned + by serverErorMsg() or erorMsg() + \sa serverErrorMsg(), drv_clearServerResult() + */ + virtual QString serverResultName(); + + /*! \return message title that sometimes is provided and prepended + to the main warning/error message. Used by MessageHandler. */ + QString msgTitle() const { return m_msgTitle; } + + /*! \return sql string of actually executed SQL statement, + usually using drv_executeSQL(). If there was error during executing SQL statement, + before, that string is returned instead. */ + const QString recentSQLString() const { return m_errorSql.isEmpty() ? m_sql : m_errorSql; } + + protected: + /* Constructs a new object. + \a handler can be provided to receive error messages. */ + Object(MessageHandler* handler = 0); + + virtual ~Object(); + + /*! Sets the (localized) error code to \a code and message to \a msg. + You have to set at least nonzero error code \a code, + although it is also adviced to set descriptive message \a msg. + Eventually, if you omit all parameters, ERR_OTHER code will be set + and default message for this will be set. + Use this in KexiDB::Object subclasses to informa the world about your + object's state. */ + virtual void setError(int code = ERR_OTHER, const QString &msg = QString::null ); + + /*! \overload void setError(int code, const QString &msg = QString::null ) + Sets error code to ERR_OTHER. Use this if you don't care about + setting error code. + */ + virtual void setError( const QString &msg ); + + /*! \overload void setError(const QString &msg) + Also sets \a title. */ + virtual void setError( const QString &title, const QString &msg ); + + /*! Copies the (localized) error message and code from other KexiDB::Object. */ + void setError( KexiDB::Object *obj, const QString& prependMessage = QString::null ); + + /*! Copies the (localized) error message and code from other KexiDB::Object + with custom error \a code. */ + virtual void setError( KexiDB::Object *obj, int code, + const QString& prependMessage = QString::null ); + + /*! Interactively asks a question. Console or GUI can be used for this, + depending on installed message handler. For GUI version, KMessageBox class is used. + See KexiDB::MessageHandler::askQuestion() for details. */ + virtual int askQuestion( const QString& message, + KMessageBox::DialogType dlgType, KMessageBox::ButtonCode defaultResult, + const KGuiItem &buttonYes=KStdGuiItem::yes(), + const KGuiItem &buttonNo=KStdGuiItem::no(), + const QString &dontShowAskAgainName = QString::null, + int options = KMessageBox::Notify, + MessageHandler* msgHandler = 0 ); + + /*! Clears number of last server operation's result stored + as a single integer. Formally, this integer should be set to value + that means "NO ERRORS" or "OK". This method is called by clearError(). + For reimplementation. By default does nothing. + \sa serverErrorMsg() + */ + virtual void drv_clearServerResult() {}; + + //! used to store of actually executed SQL statement + QString m_sql, m_errorSql; + int m_serverResult; + QString m_serverResultName, m_serverErrorMsg; + QString m_errMsg; + + private: + int m_errno; + bool m_hasError; + + //! previous server result number, for error displaying purposes. + int m_previousServerResultNum, m_previousServerResultNum2; + //! previous server result name, for error displaying purposes. + QString m_previousServerResultName, m_previousServerResultName2; + + QString m_msgTitle; + MessageHandler *m_msgHandler; + + class Private; + Private *d; //!< for future extensions + + friend class MessageTitle; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/parser/Makefile.am b/kexi/kexidb/parser/Makefile.am new file mode 100644 index 00000000..31cfbc1c --- /dev/null +++ b/kexi/kexidb/parser/Makefile.am @@ -0,0 +1,38 @@ +include $(top_srcdir)/kexi/Makefile.global + +lib_LTLIBRARIES = libkexidbparser.la +libkexidbparser_la_SOURCES = sqlscanner.cpp sqlparser.cpp parser.cpp parser_p.cpp +libkexidbparser_la_LIBADD = $(LIB_KPARTS) $(LIB_KDEUI) ../libkexidb.la +libkexidbparser_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(VER_INFO) + +noinst_HEADERS = parser_p.h + +INCLUDES = -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/kexidb $(all_includes) +METASOURCES = AUTO + +parser: + cd $(srcdir); \ + lex -osqlscanner.cpp sqlscanner.l; \ + bison -dv sqlparser.y; \ + echo '#ifndef _SQLPARSER_H_' > sqlparser.h; \ + echo '#define _SQLPARSER_H_' >> sqlparser.h; \ + echo '#include "field.h"' >> sqlparser.h; \ + echo '#include "parser.h"' >> sqlparser.h; \ + echo '#include "sqltypes.h"' >> sqlparser.h; \ + echo '' >> sqlparser.h; \ + echo 'bool parseData(KexiDB::Parser *p, const char *data);' >> sqlparser.h; \ + cat sqlparser.tab.h >> sqlparser.h; \ + echo '#endif' >> sqlparser.h; \ + cat sqlparser.tab.c > sqlparser.cpp; \ + echo "const char * const tname(int offset) { return yytname[offset]; }" >> sqlparser.cpp; \ + ./extract_tokens.sh > tokens.cpp; \ + rm -f sqlparser.tab.h sqlparser.tab.c + +coffie: + echo 'making coffie...' + sleep 5 + +KDE_OPTIONS=nofinal +KDE_CXXFLAGS += -DYYERROR_VERBOSE=1 + +.PHONY: parser coffie diff --git a/kexi/kexidb/parser/TODO b/kexi/kexidb/parser/TODO new file mode 100644 index 00000000..dbef3942 --- /dev/null +++ b/kexi/kexidb/parser/TODO @@ -0,0 +1,9 @@ +- interpretion + - CREATE TABLE + - missing keys + - SELECT + - ALTER + - UPDATE + - only op code for now, ignore rest + - INSERT + - only op code for now, ignore rest diff --git a/kexi/kexidb/parser/extract_tokens.sh b/kexi/kexidb/parser/extract_tokens.sh new file mode 100755 index 00000000..4be9fdb1 --- /dev/null +++ b/kexi/kexidb/parser/extract_tokens.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +echo "/* WARNING! All changes made in this file will be lost! Run 'make parser' instead. */" +for t in `grep "\"[a-zA-Z_]*\"" sqlscanner.l | sed -e "s/\(^[^\"]*\)\"\([^\"]*\)\".*$/\2/g" | sort | uniq` ; do + if [ "$t" = "ZZZ" ] ; then break ; fi + echo "INS(\"$t\");"; +done diff --git a/kexi/kexidb/parser/parser.cpp b/kexi/kexidb/parser/parser.cpp new file mode 100644 index 00000000..f64ec96d --- /dev/null +++ b/kexi/kexidb/parser/parser.cpp @@ -0,0 +1,155 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <[email protected]> + Copyright (C) 2004 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <connection.h> +#include <tableschema.h> +#include "parser.h" +#include "parser_p.h" +#include "sqlparser.h" + +extern const char * reserved_keywords[]; + +using namespace KexiDB; + +Parser::Parser(Connection *db) + : d(new ParserPrivate) +{ + d->db = db; +} + +Parser::~Parser() +{ + delete d; +} + +Parser::OPCode Parser::operation() const { return (OPCode)d->operation; } + +QString +Parser::operationString() const +{ + switch((OPCode)d->operation) { + case OP_Error: + return "Error"; + case OP_CreateTable: + return "CreateTable"; + case OP_AlterTable: + return "AlterTable"; + case OP_Select: + return "Select"; + case OP_Insert: + return "Insert"; + case OP_Update: + return "Update"; + case OP_Delete: + return "Delete"; + default: //OP_None + return "None"; + } +} + +TableSchema *Parser::table() { TableSchema *t = d->table; d->table=0; return t; } + +QuerySchema *Parser::query() { QuerySchema *s = d->select; d->select=0; return s; } + +Connection *Parser::db() const { return d->db; } + +ParserError Parser::error() const { return d->error; } + +QString Parser::statement() const { return d->statement; } + +void Parser::setOperation(OPCode op) { d->operation = op; } + +QuerySchema *Parser::select() const { return d->select; } + +void Parser::setError(const ParserError &err) { d->error = err; } + +void +Parser::createTable(const char *t) +{ + if (d->table) + return; + + d->table = new KexiDB::TableSchema(t); +} + +void +Parser::setQuerySchema(QuerySchema *query) +{ + if (d->select) + delete d->select; + + d->select = query; +} + +void Parser::init() +{ + if (d->initialized) + return; +#define INS(p) d->reservedKeywords.insert(p, (char*)1, 0) +#include "tokens.cpp" + d->initialized = true; +} + +bool Parser::isReservedKeyword(const char *str) +{ + return d->reservedKeywords.find(str); +} + +bool +Parser::parse(const QString &statement) +{ + init(); + clear(); + d->statement = statement; + + KexiDB::Parser *oldParser = parser; + KexiDB::Field *oldField = field; + bool res = parseData(this, statement.utf8()); + parser = oldParser; + field = oldField; + return res; +} + +void +Parser::clear() +{ + d->clear(); +} + +//------------------------------------- + +ParserError::ParserError() +: m_at(-1) +{ +// m_isNull = true; +} + +ParserError::ParserError(const QString &type, const QString &error, const QString &hint, int at) +{ + m_type = type; + m_error = error; + m_hint = hint; + m_at = at; +} + +ParserError::~ParserError() +{ +} + diff --git a/kexi/kexidb/parser/parser.h b/kexi/kexidb/parser/parser.h new file mode 100644 index 00000000..ec2942e1 --- /dev/null +++ b/kexi/kexidb/parser/parser.h @@ -0,0 +1,240 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <[email protected]> + Copyright (C) 2004 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDBPARSER_H +#define KEXIDBPARSER_H + +#include <qobject.h> +#include <qptrlist.h> +#include <qvariant.h> + +#include <kexidb/field.h> +#include <kexidb/expression.h> + +namespace KexiDB +{ + +class Connection; +class QuerySchema; +class TableSchema; +class Field; + +/** + * Provides detailed i18n'ed error description about the \a Parser . + */ +class KEXI_DB_EXPORT ParserError +{ + public: + + /** + * Empty constructor. + */ + ParserError(); + + /** + * Constructor. + * + * \param type The errortype. + * \param error A description of the error. + * \param hint Token where the error happend. + * \param at The position where the error happend. + */ + ParserError(const QString &type, const QString &error, const QString &hint, int at); + + /** + * Destructor. + */ + ~ParserError(); + + /** + * \return the errortype. + */ + QString type() { return m_type; } + + /** + * \return a descriping error message. + */ + QString error() { return m_error; } + + /** + * \return position where the error happend. + */ + int at() { return m_at; } + + private: + QString m_type; + QString m_error; + QString m_hint; + int m_at; +// bool m_isNull; +}; + +class ParserPrivate; + +/** + * Parser for SQL statements. + * + * The best and prefeerred way to run queries is using the KexiDB::Parser functionality + * and use the resulting QuerySchema object since this offers a database-backend-independent + * way to deal with SQL statements on the one hand and offers high level + * functionality on the other. Also BLOBs like images are handled that way. + * + * For example if we like to use the SELECT statement + * "SELECT dir.path, media.filename FROM dir, media WHERE dir.id=media.dirId AND media.id=%s" + * we are able to use the \a Connection::prepareStatement method which takes the type of + * the statement (in our case \a PreparedStatement::SelectStatement ), a list of fields (in + * our case dir.path and media.filename) and returns a \a PreparedStatement::Ptr instance. + * By using the \a QuerySchema::addRelationship and \a QuerySchema::addToWhereExpression methods + * the SQL statement could be extended with relationships and WHERE expressions. + * + * For more, see \a KexiDB::PreparedStatement and \a Connection::selectStatement() . A more + * complex example that looks at what the user has defined and carefully builds + * \a KexiDB::QuerySchema object, including the WHERE expression can be found in + * the Query Designer's source code in the method \a KexiQueryDesignerGuiEditor::buildSchema(). + */ +class KEXI_DB_EXPORT Parser +{ + public: + + /** + * The operation-code of the statement. + */ + enum OPCode + { + OP_None = 0, /// No statement parsed or reseted. + OP_Error, /// Error while parsing. + OP_CreateTable, /// Create a table. + OP_AlterTable, /// Alter an existing table + OP_Select, /// Query-statement. + OP_Insert, /// Insert new content. + OP_Update, /// Update existing content. + OP_Delete /// Delete existing content. + }; + + /** + * constructs an empty object of the parser + * \param connection is used for things like wildcard resolution. If 0 parser works in "pure mode" + */ + Parser(Connection *connection); + ~Parser(); + + /** + * clears previous results and runs the parser + */ + bool parse(const QString &statement); + + /** + * rests results + */ + void clear(); + + /** + * \return the resulting operation or OP_Error if failed + */ + OPCode operation() const; + + /** + * \return the resulting operation as string. + */ + QString operationString() const; + + /** + * \return a pointer to a KexiDBTable on CREATE TABLE + * or 0 on any other operation or error. Returned object is owned by you. + * You can call this method only once every time after doing parse(). + * Next time, the call will return 0. + */ + TableSchema *table(); + + /** + * \return a pointer to KexiDBSelect if 'SELECT ...' was called + * or 0 on any other operation or error. Returned object is owned by you. + * You can call this method only once every time after doing parse(). + * Next time, the call will return 0. + */ + QuerySchema *query(); + + /** + * \return a pointer to the used database connection or 0 if not set + * You can call this method only once every time after doing parse(). + * Next time, the call will return 0. + */ + Connection *db() const; + + /** + * \return detailed information about last error. + * If no error occurred ParserError isNull() + */ + ParserError error() const; + + /** + * \return the statement passed on the last \a parse method-call. + */ + QString statement() const; + + /** + * \internal + * sets the operation (only parser will need to call this) + */ + void setOperation(OPCode op); + + /** + * \internal + * creates a new table (only parser will need to call this) + */ + void createTable(const char *t); + + /** + * \internal + * sets \a query schema object (only parser will need to call this) + */ +//todo: other query types + void setQuerySchema(QuerySchema *query); + + /** + * \internal + * \return query schema + */ + QuerySchema *select() const; + + /** + * \internal + * INTERNAL use only: sets a error + */ + void setError(const ParserError &err); + + /** + * \return true if the \param str is an reserved + * keyword (see tokens.cpp for a list of reserved + * keywords). + */ + bool isReservedKeyword(const char *str); + + protected: + void init(); + + ParserError m_error; //!< detailed information about last error. + ParserPrivate *d; //!< \internal d-pointer class. +}; + +} + +#endif + diff --git a/kexi/kexidb/parser/parser_p.cpp b/kexi/kexidb/parser/parser_p.cpp new file mode 100644 index 00000000..ca935d60 --- /dev/null +++ b/kexi/kexidb/parser/parser_p.cpp @@ -0,0 +1,641 @@ +/* This file is part of the KDE project + Copyright (C) 2004, 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "parser_p.h" +#include "sqlparser.h" + +#include <kdebug.h> +#include <klocale.h> + +#include <qregexp.h> + +#include <assert.h> + +using namespace KexiDB; + +Parser *parser = 0; +Field *field = 0; +//bool requiresTable; +QPtrList<Field> fieldList; +int current = 0; +QString ctoken = ""; + +//------------------------------------- + +ParserPrivate::ParserPrivate() + : reservedKeywords(997, 997, false) + , initialized(false) +{ + clear(); + table = 0; + select = 0; + db = 0; +} + +ParserPrivate::~ParserPrivate() +{ + delete select; + delete table; +} + +void ParserPrivate::clear() +{ + operation = Parser::OP_None; + error = ParserError(); +} + +//------------------------------------- + +ParseInfo::ParseInfo(KexiDB::QuerySchema *query) + : repeatedTablesAndAliases(997, false) + , querySchema(query) +{ + repeatedTablesAndAliases.setAutoDelete(true); +} + +ParseInfo::~ParseInfo() +{ +} + +//------------------------------------- + +extern int yyparse(); +extern void tokenize(const char *data); + +void yyerror(const char *str) +{ + KexiDBDbg << "error: " << str << endl; + KexiDBDbg << "at character " << current << " near tooken " << ctoken << endl; + parser->setOperation(Parser::OP_Error); + + const bool otherError = (qstrnicmp(str, "other error", 11)==0); + + if (parser->error().type().isEmpty() + && (str==0 || strlen(str)==0 + || qstrnicmp(str, "syntax error", 12)==0 || qstrnicmp(str, "parse error", 11)==0) + || otherError) + { + KexiDBDbg << parser->statement() << endl; + QString ptrline = ""; + for(int i=0; i < current; i++) + ptrline += " "; + + ptrline += "^"; + + KexiDBDbg << ptrline << endl; + + //lexer may add error messages + QString lexerErr = parser->error().error(); + + QString errtypestr(str); + if (lexerErr.isEmpty()) { +#if 0 + if (errtypestr.startsWith("parse error, unexpected ")) { + //something like "parse error, unexpected IDENTIFIER, expecting ',' or ')'" + QString e = errtypestr.mid(24); + KexiDBDbg << e <<endl; + QString token = "IDENTIFIER"; + if (e.startsWith(token)) { + QRegExp re("'.'"); + int pos=0; + pos = re.search(e, pos); + QStringList captured=re.capturedTexts(); + if (captured.count()>=2) { +// KexiDBDbg << "**" << captured.at(1) << endl; +// KexiDBDbg << "**" << captured.at(2) << endl; + } + } + + + +// IDENTIFIER, expecting '")) { + e = errtypestr.mid(47); + KexiDBDbg << e <<endl; +// ,' or ')' +// lexerErr i18n("identifier was expected"); + + } else +#endif + if (errtypestr.startsWith("parse error, expecting `IDENTIFIER'")) + lexerErr = i18n("identifier was expected"); + } + + if (!otherError) { + if (!lexerErr.isEmpty()) + lexerErr.prepend(": "); + + if (parser->isReservedKeyword(ctoken.latin1())) + parser->setError( ParserError(i18n("Syntax Error"), + i18n("\"%1\" is a reserved keyword").arg(ctoken)+lexerErr, ctoken, current) ); + else + parser->setError( ParserError(i18n("Syntax Error"), + i18n("Syntax Error near \"%1\"").arg(ctoken)+lexerErr, ctoken, current) ); + } + } +} + +void setError(const QString& errName, const QString& errDesc) +{ + parser->setError( ParserError(errName, errDesc, ctoken, current) ); + yyerror(errName.latin1()); +} + +void setError(const QString& errDesc) +{ + setError("other error", errDesc); +} + +/* this is better than assert() */ +#define IMPL_ERROR(errmsg) setError("Implementation error", errmsg) + +bool parseData(Parser *p, const char *data) +{ +/* todo: make this REENTRANT */ + parser = p; + parser->clear(); + field = 0; + fieldList.clear(); +// requiresTable = false; + + if (!data) { + ParserError err(i18n("Error"), i18n("No query specified"), ctoken, current); + parser->setError(err); + yyerror(""); + parser = 0; + return false; + } + + tokenize(data); + if (!parser->error().type().isEmpty()) { + parser = 0; + return false; + } + yyparse(); + + bool ok = true; + if(parser->operation() == Parser::OP_Select) + { + KexiDBDbg << "parseData(): ok" << endl; +// KexiDBDbg << "parseData(): " << tableDict.count() << " loaded tables" << endl; +/* TableSchema *ts; + for(QDictIterator<TableSchema> it(tableDict); TableSchema *s = tableList.first(); s; s = tableList.next()) + { + KexiDBDbg << " " << s->name() << endl; + }*/ +/*removed + Field::ListIterator it = parser->select()->fieldsIterator(); + for(Field *item; (item = it.current()); ++it) + { + if(tableList.findRef(item->table()) == -1) + { + ParserError err(i18n("Field List Error"), i18n("Unknown table '%1' in field list").arg(item->table()->name()), ctoken, current); + parser->setError(err); + + yyerror("fieldlisterror"); + ok = false; + } + }*/ + //take the dummy table out of the query +// parser->select()->removeTable(dummy); + } + else { + ok = false; + } + +// tableDict.clear(); + parser = 0; + return ok; +} + + +/* Adds \a column to \a querySchema. \a column can be in a form of + table.field, tableAlias.field or field +*/ +bool addColumn( ParseInfo& parseInfo, BaseExpr* columnExpr ) +{ + if (!columnExpr->validate(parseInfo)) { + setError(parseInfo.errMsg, parseInfo.errDescr); + return false; + } + + VariableExpr *v_e = columnExpr->toVariable(); + if (columnExpr->exprClass() == KexiDBExpr_Variable && v_e) { + //it's a variable: + if (v_e->name=="*") {//all tables asterisk + if (parseInfo.querySchema->tables()->isEmpty()) { + setError(i18n("\"*\" could not be used if no tables are specified")); + return false; + } + parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema) ); + } + else if (v_e->tableForQueryAsterisk) {//one-table asterisk + parseInfo.querySchema->addAsterisk( + new QueryAsterisk(parseInfo.querySchema, v_e->tableForQueryAsterisk) ); + } + else if (v_e->field) {//"table.field" or "field" (bound to a table or not) + parseInfo.querySchema->addField(v_e->field, v_e->tablePositionForField); + } + else { + IMPL_ERROR("addColumn(): unknown case!"); + return false; + } + return true; + } + + //it's complex expression + parseInfo.querySchema->addExpression(columnExpr); + +#if 0 + KexiDBDbg << "found variable name: " << varName << endl; + int dotPos = varName.find('.'); + QString tableName, fieldName; +//TODO: shall we also support db name? + if (dotPos>0) { + tableName = varName.left(dotPos); + fieldName = varName.mid(dotPos+1); + } + if (tableName.isEmpty()) {//fieldname only + fieldName = varName; + if (fieldName=="*") { + parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema) ); + } + else { + //find first table that has this field + Field *firstField = 0; + for (TableSchema::ListIterator it(*parseInfo.querySchema->tables()); it.current(); ++it) { + Field *f = it.current()->field(fieldName); + if (f) { + if (!firstField) { + firstField = f; + } else if (f->table()!=firstField->table()) { + //ambiguous field name + setError(i18n("Ambiguous field name"), + i18n("Both table \"%1\" and \"%2\" have defined \"%3\" field. " + "Use \"<tableName>.%4\" notation to specify table name.") + .arg(firstField->table()->name()).arg(f->table()->name()) + .arg(fieldName).arg(fieldName)); + return false; + } + } + } + if (!firstField) { + setError(i18n("Field not found"), + i18n("Table containing \"%1\" field not found").arg(fieldName)); + return false; + } + //ok + parseInfo.querySchema->addField(firstField); + } + } + else {//table.fieldname or tableAlias.fieldname + tableName = tableName.lower(); + TableSchema *ts = parseInfo.querySchema->table( tableName ); + if (ts) {//table.fieldname + //check if "table" is covered by an alias + const QValueList<int> tPositions = parseInfo.querySchema->tablePositions(tableName); + QValueList<int>::ConstIterator it = tPositions.constBegin(); + QCString tableAlias; + bool covered = true; + for (; it!=tPositions.constEnd() && covered; ++it) { + tableAlias = parseInfo.querySchema->tableAlias(*it); + if (tableAlias.isEmpty() || tableAlias.lower()==tableName.latin1()) + covered = false; //uncovered + KexiDBDbg << " --" << "covered by " << tableAlias << " alias" << endl; + } + if (covered) { + setError(i18n("Could not access the table directly using its name"), + i18n("Table \"%1\" is covered by aliases. Instead of \"%2\", " + "you can write \"%3\"").arg(tableName) + .arg(tableName+"."+fieldName).arg(tableAlias+"."+fieldName.latin1())); + return false; + } + } + + int tablePosition = -1; + if (!ts) {//try to find tableAlias + tablePosition = parseInfo.querySchema->tablePositionForAlias( tableName.latin1() ); + if (tablePosition>=0) { + ts = parseInfo.querySchema->tables()->at(tablePosition); + if (ts) { +// KexiDBDbg << " --it's a tableAlias.name" << endl; + } + } + } + + + if (ts) { + QValueList<int> *positionsList = repeatedTablesAndAliases[ tableName ]; + if (!positionsList) { + IMPL_ERROR(tableName + "." + fieldName + ", !positionsList "); + return false; + } + + if (fieldName=="*") { + if (positionsList->count()>1) { + setError(i18n("Ambiguous \"%1.*\" expression").arg(tableName), + i18n("More than one \"%1\" table or alias defined").arg(tableName)); + return false; + } + parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema, ts) ); + } + else { +// KexiDBDbg << " --it's a table.name" << endl; + Field *realField = ts->field(fieldName); + if (realField) { + // check if table or alias is used twice and both have the same column + // (so the column is ambiguous) + int numberOfTheSameFields = 0; + for (QValueList<int>::iterator it = positionsList->begin(); + it!=positionsList->end();++it) + { + TableSchema *otherTS = parseInfo.querySchema->tables()->at(*it); + if (otherTS->field(fieldName)) + numberOfTheSameFields++; + if (numberOfTheSameFields>1) { + setError(i18n("Ambiguous \"%1.%2\" expression").arg(tableName).arg(fieldName), + i18n("More than one \"%1\" table or alias defined containing \"%2\" field") + .arg(tableName).arg(fieldName)); + return false; + } + } + + parseInfo.querySchema->addField(realField, tablePosition); + } + else { + setError(i18n("Field not found"), i18n("Table \"%1\" has no \"%2\" field") + .arg(tableName).arg(fieldName)); + return false; + } + } + } + else { + tableNotFoundError(tableName); + return false; + } + } +#endif + return true; +} + +//! clean up no longer needed temporary objects +#define CLEANUP \ + delete colViews; \ + delete tablesList; \ + delete options + +QuerySchema* buildSelectQuery( + QuerySchema* querySchema, NArgExpr* colViews, NArgExpr* tablesList, + SelectOptionsInternal* options ) +{ + ParseInfo parseInfo(querySchema); + + //-------tables list +// assert( tablesList ); //&& tablesList->exprClass() == KexiDBExpr_TableList ); + + uint columnNum = 0; +/*TODO: use this later if there are columns that use database fields, + e.g. "SELECT 1 from table1 t, table2 t") is ok however. */ + //used to collect information about first repeated table name or alias: +// QDict<char> tableNamesAndTableAliases(997, false); +// QString repeatedTableNameOrTableAlias; + if (tablesList) { + for (int i=0; i<tablesList->args(); i++, columnNum++) { + BaseExpr *e = tablesList->arg(i); + VariableExpr* t_e = 0; + QCString aliasString; + if (e->exprClass() == KexiDBExpr_SpecialBinary) { + BinaryExpr* t_with_alias = e->toBinary(); + assert(t_with_alias); + assert(t_with_alias->left()->exprClass() == KexiDBExpr_Variable); + assert(t_with_alias->right()->exprClass() == KexiDBExpr_Variable + && (t_with_alias->token()==AS || t_with_alias->token()==0)); + t_e = t_with_alias->left()->toVariable(); + aliasString = t_with_alias->right()->toVariable()->name.latin1(); + } + else { + t_e = e->toVariable(); + } + assert(t_e); + QCString tname = t_e->name.latin1(); + TableSchema *s = parser->db()->tableSchema(tname); + if(!s) { + setError(//i18n("Field List Error"), + i18n("Table \"%1\" does not exist").arg(tname)); + // yyerror("fieldlisterror"); + CLEANUP; + return 0; + } + QCString tableOrAliasName; + if (!aliasString.isEmpty()) { + tableOrAliasName = aliasString; +// KexiDBDbg << "- add alias for table: " << aliasString << endl; + } else { + tableOrAliasName = tname; + } + // 1. collect information about first repeated table name or alias + // (potential ambiguity) + QValueList<int> *list = parseInfo.repeatedTablesAndAliases[tableOrAliasName]; + if (list) { + //another table/alias with the same name + list->append( i ); +// KexiDBDbg << "- another table/alias with name: " << tableOrAliasName << endl; + } + else { + list = new QValueList<int>(); + list->append( i ); + parseInfo.repeatedTablesAndAliases.insert( tableOrAliasName, list ); +// KexiDBDbg << "- first table/alias with name: " << tableOrAliasName << endl; + } + /* if (repeatedTableNameOrTableAlias.isEmpty()) { + if (tableNamesAndTableAliases[tname]) + repeatedTableNameOrTableAlias=tname; + else + tableNamesAndTableAliases.insert(tname, (const char*)1); + } + if (!aliasString.isEmpty()) { + KexiDBDbg << "- add alias for table: " << aliasString << endl; + // querySchema->setTableAlias(columnNum, aliasString); + //2. collect information about first repeated table name or alias + // (potential ambiguity) + if (repeatedTableNameOrTableAlias.isEmpty()) { + if (tableNamesAndTableAliases[aliasString]) + repeatedTableNameOrTableAlias=aliasString; + else + tableNamesAndTableAliases.insert(aliasString, (const char*)1); + } + }*/ +// KexiDBDbg << "addTable: " << tname << endl; + querySchema->addTable( s, aliasString ); + } + } + + /* set parent table if there's only one */ +// if (parser->select()->tables()->count()==1) + if (querySchema->tables()->count()==1) + querySchema->setMasterTable(querySchema->tables()->first()); + + //-------add fields + if (colViews) { + BaseExpr *e; + columnNum = 0; + const uint colCount = colViews->list.count(); +// for (BaseExpr::ListIterator it(colViews->list);(e = it.current()); columnNum++) + colViews->list.first(); + for (; columnNum<colCount; columnNum++) { + e = colViews->list.current(); + bool moveNext = true; //used to avoid ++it when an item is taken from the list + BaseExpr *columnExpr = e; + VariableExpr* aliasVariable = 0; + if (e->exprClass() == KexiDBExpr_SpecialBinary && e->toBinary() + && (e->token()==AS || e->token()==0)) + { + //KexiDBExpr_SpecialBinary: with alias + columnExpr = e->toBinary()->left(); + // isFieldWithAlias = true; + aliasVariable = e->toBinary()->right()->toVariable(); + if (!aliasVariable) { + setError(i18n("Invalid alias definition for column \"%1\"") + .arg(columnExpr->toString())); //ok? + CLEANUP; + return 0; + } + } + + const int c = columnExpr->exprClass(); + const bool isExpressionField = + c == KexiDBExpr_Const + || c == KexiDBExpr_Unary + || c == KexiDBExpr_Arithm + || c == KexiDBExpr_Logical + || c == KexiDBExpr_Relational + || c == KexiDBExpr_Const + || c == KexiDBExpr_Function + || c == KexiDBExpr_Aggregation; + + if (c == KexiDBExpr_Variable) { + //just a variable, do nothing, addColumn() will handle this + } + else if (isExpressionField) { + //expression object will be reused, take, will be owned, do not destroy +// KexiDBDbg << colViews->list.count() << " " << it.current()->debugString() << endl; + colViews->list.take(); //take() doesn't work + moveNext = false; + } + else if (aliasVariable) { + //take first (left) argument of the special binary expr, will be owned, do not destroy + e->toBinary()->m_larg = 0; + } + else { + setError(i18n("Invalid \"%1\" column definition").arg(e->toString())); //ok? + CLEANUP; + return 0; + } + + if (!addColumn( parseInfo, columnExpr )) { + CLEANUP; + return 0; + } + + if (aliasVariable) { +// KexiDBDbg << "ALIAS \"" << aliasVariable->name << "\" set for column " +// << columnNum << endl; + querySchema->setColumnAlias(columnNum, aliasVariable->name.latin1()); + } + /* if (e->exprClass() == KexiDBExpr_SpecialBinary && dynamic_cast<BinaryExpr*>(e) + && (e->type()==AS || e->type()==0)) + { + //also add alias + VariableExpr* aliasVariable = + dynamic_cast<VariableExpr*>(dynamic_cast<BinaryExpr*>(e)->right()); + if (!aliasVariable) { + setError(i18n("Invalid column alias definition")); //ok? + return 0; + } + kdDebug() << "ALIAS \"" << aliasVariable->name << "\" set for column " + << columnNum << endl; + querySchema->setColumnAlias(columnNum, aliasVariable->name.latin1()); + }*/ + + if (moveNext) { + colViews->list.next(); +// ++it; + } + } + } + //----- SELECT options + if (options) { + //----- WHERE expr. + if (options->whereExpr) { + if (!options->whereExpr->validate(parseInfo)) { + setError(parseInfo.errMsg, parseInfo.errDescr); + CLEANUP; + return false; + } + querySchema->setWhereExpression(options->whereExpr); + } + //----- ORDER BY + if (options->orderByColumns) { + OrderByColumnList &orderByColumnList = querySchema->orderByColumnList(); + OrderByColumnInternal::ListConstIterator it = options->orderByColumns->constEnd(); + uint count = options->orderByColumns->count(); + --it; + for (;count>0; --it, --count) + /*opposite direction due to parser specifics*/ + { + //first, try to find a column name or alias (outside of asterisks) + QueryColumnInfo *columnInfo = querySchema->columnInfo( (*it).aliasOrName, false/*outside of asterisks*/ ); + if (columnInfo) { + orderByColumnList.appendColumn( *columnInfo, (*it).ascending ); + } + else { + //failed, try to find a field name within all the tables + if ((*it).columnNumber != -1) { + if (!orderByColumnList.appendColumn( *querySchema, + (*it).ascending, (*it).columnNumber-1 )) + { + setError(i18n("Could not define sorting - no column at position %1") + .arg((*it).columnNumber)); + CLEANUP; + return 0; + } + } + else { + Field * f = querySchema->findTableField((*it).aliasOrName); + if (!f) { + setError(i18n("Could not define sorting - " + "column name or alias \"%1\" does not exist").arg((*it).aliasOrName)); + CLEANUP; + return 0; + } + orderByColumnList.appendField( *f, (*it).ascending ); + } + } + } + } + } + +// KexiDBDbg << "Select ColViews=" << (colViews ? colViews->debugString() : QString::null) +// << " Tables=" << (tablesList ? tablesList->debugString() : QString::null) << endl; + + CLEANUP; + return querySchema; +} + +#undef CLEANUP + diff --git a/kexi/kexidb/parser/parser_p.h b/kexi/kexidb/parser/parser_p.h new file mode 100644 index 00000000..6a695bc5 --- /dev/null +++ b/kexi/kexidb/parser/parser_p.h @@ -0,0 +1,86 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_PARSER_P_H +#define KEXIDB_PARSER_P_H + +#include <qvaluelist.h> +#include <qdict.h> +#include <qasciicache.h> +#include <qstring.h> + +#include <kexidb/queryschema.h> +#include <kexidb/tableschema.h> +#include <kexidb/connection.h> +#include <kexidb/expression.h> +#include "sqltypes.h" +#include "parser.h" + +namespace KexiDB { + +//! @internal +class ParserPrivate +{ + public: + ParserPrivate(); + ~ParserPrivate(); + + void clear(); + + int operation; + TableSchema *table; + QuerySchema *select; + Connection *db; + QString statement; + ParserError error; + QAsciiCache<char> reservedKeywords; + bool initialized : 1; +}; + + +/*! Data used on parsing. @internal */ +class ParseInfo +{ + public: + ParseInfo(QuerySchema *query); + ~ParseInfo(); + + //! collects positions of tables/aliases with the same names + QDict< QValueList<int> > repeatedTablesAndAliases; + + QString errMsg, errDescr; //helpers + QuerySchema *querySchema; +}; + +} + +void yyerror(const char *str); +void setError(const QString& errName, const QString& errDesc); +void setError(const QString& errDesc); +//bool parseData(KexiDB::Parser *p, const char *data); +bool addColumn( KexiDB::ParseInfo& parseInfo, KexiDB::BaseExpr* columnExpr ); +KexiDB::QuerySchema* buildSelectQuery( + KexiDB::QuerySchema* querySchema, KexiDB::NArgExpr* colViews, + KexiDB::NArgExpr* tablesList = 0, SelectOptionsInternal* options = 0 ); //KexiDB::BaseExpr* whereExpr = 0 ); + +extern KexiDB::Parser *parser; +extern KexiDB::Field *field; + + +#endif diff --git a/kexi/kexidb/parser/sqlparser.cpp b/kexi/kexidb/parser/sqlparser.cpp new file mode 100644 index 00000000..682b8aa0 --- /dev/null +++ b/kexi/kexidb/parser/sqlparser.cpp @@ -0,0 +1,3472 @@ +/* A Bison parser, made by GNU Bison 2.2. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.2" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Using locations. */ +#define YYLSP_NEEDED 0 + + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + UMINUS = 258, + SQL_TYPE = 259, + SQL_ABS = 260, + ACOS = 261, + AMPERSAND = 262, + SQL_ABSOLUTE = 263, + ADA = 264, + ADD = 265, + ADD_DAYS = 266, + ADD_HOURS = 267, + ADD_MINUTES = 268, + ADD_MONTHS = 269, + ADD_SECONDS = 270, + ADD_YEARS = 271, + ALL = 272, + ALLOCATE = 273, + ALTER = 274, + AND = 275, + ANY = 276, + ARE = 277, + AS = 278, + ASIN = 279, + ASC = 280, + ASCII = 281, + ASSERTION = 282, + ATAN = 283, + ATAN2 = 284, + AUTHORIZATION = 285, + AUTO_INCREMENT = 286, + AVG = 287, + BEFORE = 288, + SQL_BEGIN = 289, + BETWEEN = 290, + BIGINT = 291, + BINARY = 292, + BIT = 293, + BIT_LENGTH = 294, + BITWISE_SHIFT_LEFT = 295, + BITWISE_SHIFT_RIGHT = 296, + BREAK = 297, + BY = 298, + CASCADE = 299, + CASCADED = 300, + CASE = 301, + CAST = 302, + CATALOG = 303, + CEILING = 304, + CENTER = 305, + SQL_CHAR = 306, + CHAR_LENGTH = 307, + CHARACTER_STRING_LITERAL = 308, + CHECK = 309, + CLOSE = 310, + COALESCE = 311, + COBOL = 312, + COLLATE = 313, + COLLATION = 314, + COLUMN = 315, + COMMIT = 316, + COMPUTE = 317, + CONCAT = 318, + CONCATENATION = 319, + CONNECT = 320, + CONNECTION = 321, + CONSTRAINT = 322, + CONSTRAINTS = 323, + CONTINUE = 324, + CONVERT = 325, + CORRESPONDING = 326, + COS = 327, + COT = 328, + COUNT = 329, + CREATE = 330, + CURDATE = 331, + CURRENT = 332, + CURRENT_DATE = 333, + CURRENT_TIME = 334, + CURRENT_TIMESTAMP = 335, + CURTIME = 336, + CURSOR = 337, + DATABASE = 338, + SQL_DATE = 339, + DATE_FORMAT = 340, + DATE_REMAINDER = 341, + DATE_VALUE = 342, + DAY = 343, + DAYOFMONTH = 344, + DAYOFWEEK = 345, + DAYOFYEAR = 346, + DAYS_BETWEEN = 347, + DEALLOCATE = 348, + DEC = 349, + DECLARE = 350, + DEFAULT = 351, + DEFERRABLE = 352, + DEFERRED = 353, + SQL_DELETE = 354, + DESC = 355, + DESCRIBE = 356, + DESCRIPTOR = 357, + DIAGNOSTICS = 358, + DICTIONARY = 359, + DIRECTORY = 360, + DISCONNECT = 361, + DISPLACEMENT = 362, + DISTINCT = 363, + DOMAIN_TOKEN = 364, + SQL_DOUBLE = 365, + DOUBLE_QUOTED_STRING = 366, + DROP = 367, + ELSE = 368, + END = 369, + END_EXEC = 370, + EQUAL = 371, + ESCAPE = 372, + EXCEPT = 373, + SQL_EXCEPTION = 374, + EXEC = 375, + EXECUTE = 376, + EXISTS = 377, + EXP = 378, + EXPONENT = 379, + EXTERNAL = 380, + EXTRACT = 381, + SQL_FALSE = 382, + FETCH = 383, + FIRST = 384, + SQL_FLOAT = 385, + FLOOR = 386, + FN = 387, + FOR = 388, + FOREIGN = 389, + FORTRAN = 390, + FOUND = 391, + FOUR_DIGITS = 392, + FROM = 393, + FULL = 394, + GET = 395, + GLOBAL = 396, + GO = 397, + GOTO = 398, + GRANT = 399, + GREATER_OR_EQUAL = 400, + HAVING = 401, + HOUR = 402, + HOURS_BETWEEN = 403, + IDENTITY = 404, + IFNULL = 405, + SQL_IGNORE = 406, + IMMEDIATE = 407, + SQL_IN = 408, + INCLUDE = 409, + INDEX = 410, + INDICATOR = 411, + INITIALLY = 412, + INNER = 413, + INPUT = 414, + INSENSITIVE = 415, + INSERT = 416, + INTEGER = 417, + INTERSECT = 418, + INTERVAL = 419, + INTO = 420, + IS = 421, + ISOLATION = 422, + JOIN = 423, + JUSTIFY = 424, + KEY = 425, + LANGUAGE = 426, + LAST = 427, + LCASE = 428, + LEFT = 429, + LENGTH = 430, + LESS_OR_EQUAL = 431, + LEVEL = 432, + LIKE = 433, + LINE_WIDTH = 434, + LOCAL = 435, + LOCATE = 436, + LOG = 437, + SQL_LONG = 438, + LOWER = 439, + LTRIM = 440, + LTRIP = 441, + MATCH = 442, + SQL_MAX = 443, + MICROSOFT = 444, + SQL_MIN = 445, + MINUS = 446, + MINUTE = 447, + MINUTES_BETWEEN = 448, + MOD = 449, + MODIFY = 450, + MODULE = 451, + MONTH = 452, + MONTHS_BETWEEN = 453, + MUMPS = 454, + NAMES = 455, + NATIONAL = 456, + NCHAR = 457, + NEXT = 458, + NODUP = 459, + NONE = 460, + NOT = 461, + NOT_EQUAL = 462, + NOT_EQUAL2 = 463, + NOW = 464, + SQL_NULL = 465, + SQL_IS = 466, + SQL_IS_NULL = 467, + SQL_IS_NOT_NULL = 468, + NULLIF = 469, + NUMERIC = 470, + OCTET_LENGTH = 471, + ODBC = 472, + OF = 473, + SQL_OFF = 474, + SQL_ON = 475, + ONLY = 476, + OPEN = 477, + OPTION = 478, + OR = 479, + ORDER = 480, + OUTER = 481, + OUTPUT = 482, + OVERLAPS = 483, + PAGE = 484, + PARTIAL = 485, + SQL_PASCAL = 486, + PERSISTENT = 487, + CQL_PI = 488, + PLI = 489, + POSITION = 490, + PRECISION = 491, + PREPARE = 492, + PRESERVE = 493, + PRIMARY = 494, + PRIOR = 495, + PRIVILEGES = 496, + PROCEDURE = 497, + PRODUCT = 498, + PUBLIC = 499, + QUARTER = 500, + QUIT = 501, + RAND = 502, + READ_ONLY = 503, + REAL = 504, + REFERENCES = 505, + REPEAT = 506, + REPLACE = 507, + RESTRICT = 508, + REVOKE = 509, + RIGHT = 510, + ROLLBACK = 511, + ROWS = 512, + RPAD = 513, + RTRIM = 514, + SCHEMA = 515, + SCREEN_WIDTH = 516, + SCROLL = 517, + SECOND = 518, + SECONDS_BETWEEN = 519, + SELECT = 520, + SEQUENCE = 521, + SETOPT = 522, + SET = 523, + SHOWOPT = 524, + SIGN = 525, + SIMILAR_TO = 526, + NOT_SIMILAR_TO = 527, + INTEGER_CONST = 528, + REAL_CONST = 529, + DATE_CONST = 530, + DATETIME_CONST = 531, + TIME_CONST = 532, + SIN = 533, + SQL_SIZE = 534, + SMALLINT = 535, + SOME = 536, + SPACE = 537, + SQL = 538, + SQL_TRUE = 539, + SQLCA = 540, + SQLCODE = 541, + SQLERROR = 542, + SQLSTATE = 543, + SQLWARNING = 544, + SQRT = 545, + STDEV = 546, + SUBSTRING = 547, + SUM = 548, + SYSDATE = 549, + SYSDATE_FORMAT = 550, + SYSTEM = 551, + TABLE = 552, + TAN = 553, + TEMPORARY = 554, + THEN = 555, + THREE_DIGITS = 556, + TIME = 557, + TIMESTAMP = 558, + TIMEZONE_HOUR = 559, + TIMEZONE_MINUTE = 560, + TINYINT = 561, + TO = 562, + TO_CHAR = 563, + TO_DATE = 564, + TRANSACTION = 565, + TRANSLATE = 566, + TRANSLATION = 567, + TRUNCATE = 568, + GENERAL_TITLE = 569, + TWO_DIGITS = 570, + UCASE = 571, + UNION = 572, + UNIQUE = 573, + SQL_UNKNOWN = 574, + UPDATE = 575, + UPPER = 576, + USAGE = 577, + USER = 578, + IDENTIFIER = 579, + IDENTIFIER_DOT_ASTERISK = 580, + QUERY_PARAMETER = 581, + USING = 582, + VALUE = 583, + VALUES = 584, + VARBINARY = 585, + VARCHAR = 586, + VARYING = 587, + VENDOR = 588, + VIEW = 589, + WEEK = 590, + WHEN = 591, + WHENEVER = 592, + WHERE = 593, + WHERE_CURRENT_OF = 594, + WITH = 595, + WORD_WRAPPED = 596, + WORK = 597, + WRAPPED = 598, + XOR = 599, + YEAR = 600, + YEARS_BETWEEN = 601, + SCAN_ERROR = 602, + __LAST_TOKEN = 603, + ILIKE = 604 + }; +#endif +/* Tokens. */ +#define UMINUS 258 +#define SQL_TYPE 259 +#define SQL_ABS 260 +#define ACOS 261 +#define AMPERSAND 262 +#define SQL_ABSOLUTE 263 +#define ADA 264 +#define ADD 265 +#define ADD_DAYS 266 +#define ADD_HOURS 267 +#define ADD_MINUTES 268 +#define ADD_MONTHS 269 +#define ADD_SECONDS 270 +#define ADD_YEARS 271 +#define ALL 272 +#define ALLOCATE 273 +#define ALTER 274 +#define AND 275 +#define ANY 276 +#define ARE 277 +#define AS 278 +#define ASIN 279 +#define ASC 280 +#define ASCII 281 +#define ASSERTION 282 +#define ATAN 283 +#define ATAN2 284 +#define AUTHORIZATION 285 +#define AUTO_INCREMENT 286 +#define AVG 287 +#define BEFORE 288 +#define SQL_BEGIN 289 +#define BETWEEN 290 +#define BIGINT 291 +#define BINARY 292 +#define BIT 293 +#define BIT_LENGTH 294 +#define BITWISE_SHIFT_LEFT 295 +#define BITWISE_SHIFT_RIGHT 296 +#define BREAK 297 +#define BY 298 +#define CASCADE 299 +#define CASCADED 300 +#define CASE 301 +#define CAST 302 +#define CATALOG 303 +#define CEILING 304 +#define CENTER 305 +#define SQL_CHAR 306 +#define CHAR_LENGTH 307 +#define CHARACTER_STRING_LITERAL 308 +#define CHECK 309 +#define CLOSE 310 +#define COALESCE 311 +#define COBOL 312 +#define COLLATE 313 +#define COLLATION 314 +#define COLUMN 315 +#define COMMIT 316 +#define COMPUTE 317 +#define CONCAT 318 +#define CONCATENATION 319 +#define CONNECT 320 +#define CONNECTION 321 +#define CONSTRAINT 322 +#define CONSTRAINTS 323 +#define CONTINUE 324 +#define CONVERT 325 +#define CORRESPONDING 326 +#define COS 327 +#define COT 328 +#define COUNT 329 +#define CREATE 330 +#define CURDATE 331 +#define CURRENT 332 +#define CURRENT_DATE 333 +#define CURRENT_TIME 334 +#define CURRENT_TIMESTAMP 335 +#define CURTIME 336 +#define CURSOR 337 +#define DATABASE 338 +#define SQL_DATE 339 +#define DATE_FORMAT 340 +#define DATE_REMAINDER 341 +#define DATE_VALUE 342 +#define DAY 343 +#define DAYOFMONTH 344 +#define DAYOFWEEK 345 +#define DAYOFYEAR 346 +#define DAYS_BETWEEN 347 +#define DEALLOCATE 348 +#define DEC 349 +#define DECLARE 350 +#define DEFAULT 351 +#define DEFERRABLE 352 +#define DEFERRED 353 +#define SQL_DELETE 354 +#define DESC 355 +#define DESCRIBE 356 +#define DESCRIPTOR 357 +#define DIAGNOSTICS 358 +#define DICTIONARY 359 +#define DIRECTORY 360 +#define DISCONNECT 361 +#define DISPLACEMENT 362 +#define DISTINCT 363 +#define DOMAIN_TOKEN 364 +#define SQL_DOUBLE 365 +#define DOUBLE_QUOTED_STRING 366 +#define DROP 367 +#define ELSE 368 +#define END 369 +#define END_EXEC 370 +#define EQUAL 371 +#define ESCAPE 372 +#define EXCEPT 373 +#define SQL_EXCEPTION 374 +#define EXEC 375 +#define EXECUTE 376 +#define EXISTS 377 +#define EXP 378 +#define EXPONENT 379 +#define EXTERNAL 380 +#define EXTRACT 381 +#define SQL_FALSE 382 +#define FETCH 383 +#define FIRST 384 +#define SQL_FLOAT 385 +#define FLOOR 386 +#define FN 387 +#define FOR 388 +#define FOREIGN 389 +#define FORTRAN 390 +#define FOUND 391 +#define FOUR_DIGITS 392 +#define FROM 393 +#define FULL 394 +#define GET 395 +#define GLOBAL 396 +#define GO 397 +#define GOTO 398 +#define GRANT 399 +#define GREATER_OR_EQUAL 400 +#define HAVING 401 +#define HOUR 402 +#define HOURS_BETWEEN 403 +#define IDENTITY 404 +#define IFNULL 405 +#define SQL_IGNORE 406 +#define IMMEDIATE 407 +#define SQL_IN 408 +#define INCLUDE 409 +#define INDEX 410 +#define INDICATOR 411 +#define INITIALLY 412 +#define INNER 413 +#define INPUT 414 +#define INSENSITIVE 415 +#define INSERT 416 +#define INTEGER 417 +#define INTERSECT 418 +#define INTERVAL 419 +#define INTO 420 +#define IS 421 +#define ISOLATION 422 +#define JOIN 423 +#define JUSTIFY 424 +#define KEY 425 +#define LANGUAGE 426 +#define LAST 427 +#define LCASE 428 +#define LEFT 429 +#define LENGTH 430 +#define LESS_OR_EQUAL 431 +#define LEVEL 432 +#define LIKE 433 +#define LINE_WIDTH 434 +#define LOCAL 435 +#define LOCATE 436 +#define LOG 437 +#define SQL_LONG 438 +#define LOWER 439 +#define LTRIM 440 +#define LTRIP 441 +#define MATCH 442 +#define SQL_MAX 443 +#define MICROSOFT 444 +#define SQL_MIN 445 +#define MINUS 446 +#define MINUTE 447 +#define MINUTES_BETWEEN 448 +#define MOD 449 +#define MODIFY 450 +#define MODULE 451 +#define MONTH 452 +#define MONTHS_BETWEEN 453 +#define MUMPS 454 +#define NAMES 455 +#define NATIONAL 456 +#define NCHAR 457 +#define NEXT 458 +#define NODUP 459 +#define NONE 460 +#define NOT 461 +#define NOT_EQUAL 462 +#define NOT_EQUAL2 463 +#define NOW 464 +#define SQL_NULL 465 +#define SQL_IS 466 +#define SQL_IS_NULL 467 +#define SQL_IS_NOT_NULL 468 +#define NULLIF 469 +#define NUMERIC 470 +#define OCTET_LENGTH 471 +#define ODBC 472 +#define OF 473 +#define SQL_OFF 474 +#define SQL_ON 475 +#define ONLY 476 +#define OPEN 477 +#define OPTION 478 +#define OR 479 +#define ORDER 480 +#define OUTER 481 +#define OUTPUT 482 +#define OVERLAPS 483 +#define PAGE 484 +#define PARTIAL 485 +#define SQL_PASCAL 486 +#define PERSISTENT 487 +#define CQL_PI 488 +#define PLI 489 +#define POSITION 490 +#define PRECISION 491 +#define PREPARE 492 +#define PRESERVE 493 +#define PRIMARY 494 +#define PRIOR 495 +#define PRIVILEGES 496 +#define PROCEDURE 497 +#define PRODUCT 498 +#define PUBLIC 499 +#define QUARTER 500 +#define QUIT 501 +#define RAND 502 +#define READ_ONLY 503 +#define REAL 504 +#define REFERENCES 505 +#define REPEAT 506 +#define REPLACE 507 +#define RESTRICT 508 +#define REVOKE 509 +#define RIGHT 510 +#define ROLLBACK 511 +#define ROWS 512 +#define RPAD 513 +#define RTRIM 514 +#define SCHEMA 515 +#define SCREEN_WIDTH 516 +#define SCROLL 517 +#define SECOND 518 +#define SECONDS_BETWEEN 519 +#define SELECT 520 +#define SEQUENCE 521 +#define SETOPT 522 +#define SET 523 +#define SHOWOPT 524 +#define SIGN 525 +#define SIMILAR_TO 526 +#define NOT_SIMILAR_TO 527 +#define INTEGER_CONST 528 +#define REAL_CONST 529 +#define DATE_CONST 530 +#define DATETIME_CONST 531 +#define TIME_CONST 532 +#define SIN 533 +#define SQL_SIZE 534 +#define SMALLINT 535 +#define SOME 536 +#define SPACE 537 +#define SQL 538 +#define SQL_TRUE 539 +#define SQLCA 540 +#define SQLCODE 541 +#define SQLERROR 542 +#define SQLSTATE 543 +#define SQLWARNING 544 +#define SQRT 545 +#define STDEV 546 +#define SUBSTRING 547 +#define SUM 548 +#define SYSDATE 549 +#define SYSDATE_FORMAT 550 +#define SYSTEM 551 +#define TABLE 552 +#define TAN 553 +#define TEMPORARY 554 +#define THEN 555 +#define THREE_DIGITS 556 +#define TIME 557 +#define TIMESTAMP 558 +#define TIMEZONE_HOUR 559 +#define TIMEZONE_MINUTE 560 +#define TINYINT 561 +#define TO 562 +#define TO_CHAR 563 +#define TO_DATE 564 +#define TRANSACTION 565 +#define TRANSLATE 566 +#define TRANSLATION 567 +#define TRUNCATE 568 +#define GENERAL_TITLE 569 +#define TWO_DIGITS 570 +#define UCASE 571 +#define UNION 572 +#define UNIQUE 573 +#define SQL_UNKNOWN 574 +#define UPDATE 575 +#define UPPER 576 +#define USAGE 577 +#define USER 578 +#define IDENTIFIER 579 +#define IDENTIFIER_DOT_ASTERISK 580 +#define QUERY_PARAMETER 581 +#define USING 582 +#define VALUE 583 +#define VALUES 584 +#define VARBINARY 585 +#define VARCHAR 586 +#define VARYING 587 +#define VENDOR 588 +#define VIEW 589 +#define WEEK 590 +#define WHEN 591 +#define WHENEVER 592 +#define WHERE 593 +#define WHERE_CURRENT_OF 594 +#define WITH 595 +#define WORD_WRAPPED 596 +#define WORK 597 +#define WRAPPED 598 +#define XOR 599 +#define YEAR 600 +#define YEARS_BETWEEN 601 +#define SCAN_ERROR 602 +#define __LAST_TOKEN 603 +#define ILIKE 604 + + + + +/* Copy the first part of user declarations. */ +#line 438 "sqlparser.y" + +#ifndef YYDEBUG /* compat. */ +# define YYDEBUG 0 +#endif +#include <stdio.h> +#include <string.h> +#include <string> +#include <iostream> +#include <assert.h> +#include <limits.h> +//TODO OK? +#ifdef Q_WS_WIN +//workaround for bug on msvc +# undef LLONG_MIN +#endif +#ifndef LLONG_MAX +# define LLONG_MAX 0x7fffffffffffffffLL +#endif +#ifndef LLONG_MIN +# define LLONG_MIN 0x8000000000000000LL +#endif +#ifndef LLONG_MAX +# define ULLONG_MAX 0xffffffffffffffffLL +#endif + +#ifdef _WIN32 +# include <malloc.h> +#endif + +#include <qobject.h> +#include <kdebug.h> +#include <klocale.h> +#include <qptrlist.h> +#include <qcstring.h> +#include <qvariant.h> + +#include <connection.h> +#include <queryschema.h> +#include <field.h> +#include <tableschema.h> + +#include "parser.h" +#include "parser_p.h" +#include "sqltypes.h" + +int yylex(); + +// using namespace std; +using namespace KexiDB; + +#define YY_NO_UNPUT +#define YYSTACK_USE_ALLOCA 1 +#define YYMAXDEPTH 255 + + extern "C" + { + int yywrap() + { + return 1; + } + } + +#if 0 + struct yyval + { + QString parserUserName; + int integerValue; + KexiDBField::ColumnType coltype; + } +#endif + + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +#line 511 "sqlparser.y" +{ + QString* stringValue; + Q_LLONG integerValue; + bool booleanValue; + struct realType realValue; + KexiDB::Field::Type colType; + KexiDB::Field *field; + KexiDB::BaseExpr *expr; + KexiDB::NArgExpr *exprList; + KexiDB::ConstExpr *constExpr; + KexiDB::QuerySchema *querySchema; + SelectOptionsInternal *selectOptions; + OrderByColumnInternal::List *orderByColumns; + QVariant *variantValue; +} +/* Line 193 of yacc.c. */ +#line 883 "sqlparser.tab.c" + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +/* Copy the second part of user declarations. */ + + +/* Line 216 of yacc.c. */ +#line 896 "sqlparser.tab.c" + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if YYENABLE_NLS +# if ENABLE_NLS +# include <libintl.h> /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int i) +#else +static int +YYID (i) + int i; +#endif +{ + return i; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include <alloca.h> /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include <malloc.h> /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss; + YYSTYPE yyvs; + }; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 10 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 335 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 373 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 37 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 108 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 176 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 604 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint16 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 357, 352, 370, 361, + 358, 359, 351, 350, 355, 349, 356, 362, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 354, + 364, 363, 365, 360, 353, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 368, 2, 369, 367, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 371, 2, 372, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, + 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, + 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, + 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, + 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, + 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, + 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, + 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, + 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, + 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, + 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, + 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, + 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, + 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, + 345, 346, 347, 348, 366 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint16 yyprhs[] = +{ + 0, 0, 3, 5, 9, 11, 14, 16, 18, 19, + 27, 31, 33, 36, 40, 43, 45, 48, 51, 53, + 55, 60, 65, 66, 69, 73, 76, 80, 85, 87, + 89, 93, 98, 103, 106, 108, 111, 115, 120, 122, + 126, 128, 130, 132, 134, 138, 142, 146, 148, 152, + 156, 160, 164, 168, 170, 174, 178, 182, 186, 190, + 194, 196, 199, 202, 204, 208, 212, 214, 218, 222, + 226, 230, 232, 236, 240, 244, 246, 249, 252, 255, + 258, 260, 262, 265, 269, 271, 273, 275, 277, 279, + 283, 287, 291, 295, 298, 302, 304, 306, 309, 313, + 317, 319, 321, 323, 327, 330, 332, 337, 339 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int16 yyrhs[] = +{ + 374, 0, -1, 375, -1, 376, 354, 375, -1, 376, + -1, 376, 354, -1, 377, -1, 384, -1, -1, 75, + 297, 324, 378, 358, 379, 359, -1, 379, 355, 380, + -1, 380, -1, 324, 383, -1, 324, 383, 381, -1, + 381, 382, -1, 382, -1, 239, 170, -1, 206, 210, + -1, 31, -1, 4, -1, 4, 358, 273, 359, -1, + 331, 358, 273, 359, -1, -1, 385, 406, -1, 385, + 406, 403, -1, 385, 403, -1, 385, 406, 386, -1, + 385, 406, 403, 386, -1, 265, -1, 387, -1, 225, + 43, 388, -1, 387, 225, 43, 388, -1, 225, 43, + 388, 387, -1, 338, 391, -1, 389, -1, 389, 390, + -1, 389, 355, 388, -1, 389, 390, 355, 388, -1, + 324, -1, 324, 356, 324, -1, 273, -1, 25, -1, + 100, -1, 392, -1, 393, 20, 392, -1, 393, 224, + 392, -1, 393, 344, 392, -1, 393, -1, 394, 365, + 393, -1, 394, 145, 393, -1, 394, 364, 393, -1, + 394, 176, 393, -1, 394, 363, 393, -1, 394, -1, + 395, 207, 394, -1, 395, 208, 394, -1, 395, 178, + 394, -1, 395, 153, 394, -1, 395, 271, 394, -1, + 395, 272, 394, -1, 395, -1, 395, 212, -1, 395, + 213, -1, 396, -1, 397, 40, 396, -1, 397, 41, + 396, -1, 397, -1, 398, 350, 397, -1, 398, 349, + 397, -1, 398, 370, 397, -1, 398, 371, 397, -1, + 398, -1, 399, 362, 398, -1, 399, 351, 398, -1, + 399, 352, 398, -1, 399, -1, 349, 399, -1, 350, + 399, -1, 372, 399, -1, 206, 399, -1, 324, -1, + 326, -1, 324, 401, -1, 324, 356, 324, -1, 210, + -1, 53, -1, 273, -1, 274, -1, 400, -1, 358, + 391, 359, -1, 358, 402, 359, -1, 391, 355, 402, + -1, 391, 355, 391, -1, 138, 404, -1, 404, 355, + 405, -1, 405, -1, 324, -1, 324, 324, -1, 324, + 23, 324, -1, 406, 355, 407, -1, 407, -1, 408, + -1, 409, -1, 408, 23, 324, -1, 408, 324, -1, + 391, -1, 108, 358, 408, 359, -1, 351, -1, 324, + 356, 351, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 580, 580, 590, 594, 595, 605, 609, 617, 616, + 626, 626, 632, 640, 656, 656, 662, 667, 672, 680, + 685, 692, 699, 707, 714, 719, 725, 731, 740, 750, + 756, 762, 769, 779, 788, 797, 807, 815, 827, 833, + 840, 847, 851, 858, 863, 868, 872, 877, 882, 886, + 890, 894, 898, 903, 908, 913, 917, 921, 925, 929, + 934, 939, 943, 948, 953, 957, 962, 967, 972, 976, + 980, 985, 990, 994, 998, 1003, 1009, 1013, 1017, 1021, + 1025, 1033, 1039, 1046, 1053, 1060, 1066, 1083, 1089, 1094, + 1102, 1112, 1117, 1126, 1171, 1176, 1184, 1212, 1223, 1239, + 1245, 1254, 1263, 1268, 1277, 1289, 1333, 1342, 1351 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "UMINUS", "SQL_TYPE", "SQL_ABS", "ACOS", + "AMPERSAND", "SQL_ABSOLUTE", "ADA", "ADD", "ADD_DAYS", "ADD_HOURS", + "ADD_MINUTES", "ADD_MONTHS", "ADD_SECONDS", "ADD_YEARS", "ALL", + "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "AS", "ASIN", "ASC", "ASCII", + "ASSERTION", "ATAN", "ATAN2", "AUTHORIZATION", "AUTO_INCREMENT", "AVG", + "BEFORE", "SQL_BEGIN", "BETWEEN", "BIGINT", "BINARY", "BIT", + "BIT_LENGTH", "BITWISE_SHIFT_LEFT", "BITWISE_SHIFT_RIGHT", "BREAK", "BY", + "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CEILING", "CENTER", + "SQL_CHAR", "CHAR_LENGTH", "CHARACTER_STRING_LITERAL", "CHECK", "CLOSE", + "COALESCE", "COBOL", "COLLATE", "COLLATION", "COLUMN", "COMMIT", + "COMPUTE", "CONCAT", "CONCATENATION", "CONNECT", "CONNECTION", + "CONSTRAINT", "CONSTRAINTS", "CONTINUE", "CONVERT", "CORRESPONDING", + "COS", "COT", "COUNT", "CREATE", "CURDATE", "CURRENT", "CURRENT_DATE", + "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURTIME", "CURSOR", "DATABASE", + "SQL_DATE", "DATE_FORMAT", "DATE_REMAINDER", "DATE_VALUE", "DAY", + "DAYOFMONTH", "DAYOFWEEK", "DAYOFYEAR", "DAYS_BETWEEN", "DEALLOCATE", + "DEC", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "SQL_DELETE", + "DESC", "DESCRIBE", "DESCRIPTOR", "DIAGNOSTICS", "DICTIONARY", + "DIRECTORY", "DISCONNECT", "DISPLACEMENT", "DISTINCT", "DOMAIN_TOKEN", + "SQL_DOUBLE", "DOUBLE_QUOTED_STRING", "DROP", "ELSE", "END", "END_EXEC", + "EQUAL", "ESCAPE", "EXCEPT", "SQL_EXCEPTION", "EXEC", "EXECUTE", + "EXISTS", "EXP", "EXPONENT", "EXTERNAL", "EXTRACT", "SQL_FALSE", "FETCH", + "FIRST", "SQL_FLOAT", "FLOOR", "FN", "FOR", "FOREIGN", "FORTRAN", + "FOUND", "FOUR_DIGITS", "FROM", "FULL", "GET", "GLOBAL", "GO", "GOTO", + "GRANT", "GREATER_OR_EQUAL", "HAVING", "HOUR", "HOURS_BETWEEN", + "IDENTITY", "IFNULL", "SQL_IGNORE", "IMMEDIATE", "SQL_IN", "INCLUDE", + "INDEX", "INDICATOR", "INITIALLY", "INNER", "INPUT", "INSENSITIVE", + "INSERT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "IS", "ISOLATION", + "JOIN", "JUSTIFY", "KEY", "LANGUAGE", "LAST", "LCASE", "LEFT", "LENGTH", + "LESS_OR_EQUAL", "LEVEL", "LIKE", "LINE_WIDTH", "LOCAL", "LOCATE", "LOG", + "SQL_LONG", "LOWER", "LTRIM", "LTRIP", "MATCH", "SQL_MAX", "MICROSOFT", + "SQL_MIN", "MINUS", "MINUTE", "MINUTES_BETWEEN", "MOD", "MODIFY", + "MODULE", "MONTH", "MONTHS_BETWEEN", "MUMPS", "NAMES", "NATIONAL", + "NCHAR", "NEXT", "NODUP", "NONE", "NOT", "NOT_EQUAL", "NOT_EQUAL2", + "NOW", "SQL_NULL", "SQL_IS", "SQL_IS_NULL", "SQL_IS_NOT_NULL", "NULLIF", + "NUMERIC", "OCTET_LENGTH", "ODBC", "OF", "SQL_OFF", "SQL_ON", "ONLY", + "OPEN", "OPTION", "OR", "ORDER", "OUTER", "OUTPUT", "OVERLAPS", "PAGE", + "PARTIAL", "SQL_PASCAL", "PERSISTENT", "CQL_PI", "PLI", "POSITION", + "PRECISION", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVILEGES", + "PROCEDURE", "PRODUCT", "PUBLIC", "QUARTER", "QUIT", "RAND", "READ_ONLY", + "REAL", "REFERENCES", "REPEAT", "REPLACE", "RESTRICT", "REVOKE", "RIGHT", + "ROLLBACK", "ROWS", "RPAD", "RTRIM", "SCHEMA", "SCREEN_WIDTH", "SCROLL", + "SECOND", "SECONDS_BETWEEN", "SELECT", "SEQUENCE", "SETOPT", "SET", + "SHOWOPT", "SIGN", "SIMILAR_TO", "NOT_SIMILAR_TO", "INTEGER_CONST", + "REAL_CONST", "DATE_CONST", "DATETIME_CONST", "TIME_CONST", "SIN", + "SQL_SIZE", "SMALLINT", "SOME", "SPACE", "SQL", "SQL_TRUE", "SQLCA", + "SQLCODE", "SQLERROR", "SQLSTATE", "SQLWARNING", "SQRT", "STDEV", + "SUBSTRING", "SUM", "SYSDATE", "SYSDATE_FORMAT", "SYSTEM", "TABLE", + "TAN", "TEMPORARY", "THEN", "THREE_DIGITS", "TIME", "TIMESTAMP", + "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TINYINT", "TO", "TO_CHAR", + "TO_DATE", "TRANSACTION", "TRANSLATE", "TRANSLATION", "TRUNCATE", + "GENERAL_TITLE", "TWO_DIGITS", "UCASE", "UNION", "UNIQUE", "SQL_UNKNOWN", + "UPDATE", "UPPER", "USAGE", "USER", "IDENTIFIER", + "IDENTIFIER_DOT_ASTERISK", "QUERY_PARAMETER", "USING", "VALUE", "VALUES", + "VARBINARY", "VARCHAR", "VARYING", "VENDOR", "VIEW", "WEEK", "WHEN", + "WHENEVER", "WHERE", "WHERE_CURRENT_OF", "WITH", "WORD_WRAPPED", "WORK", + "WRAPPED", "XOR", "YEAR", "YEARS_BETWEEN", "SCAN_ERROR", "__LAST_TOKEN", + "'-'", "'+'", "'*'", "'%'", "'@'", "';'", "','", "'.'", "'$'", "'('", + "')'", "'?'", "'''", "'/'", "'='", "'<'", "'>'", "ILIKE", "'^'", "'['", + "']'", "'&'", "'|'", "'~'", "$accept", "TopLevelStatement", + "StatementList", "Statement", "CreateTableStatement", "@1", "ColDefs", + "ColDef", "ColKeys", "ColKey", "ColType", "SelectStatement", "Select", + "SelectOptions", "WhereClause", "OrderByClause", "OrderByColumnId", + "OrderByOption", "aExpr", "aExpr2", "aExpr3", "aExpr4", "aExpr5", + "aExpr6", "aExpr7", "aExpr8", "aExpr9", "aExpr10", "aExprList", + "aExprList2", "Tables", "FlatTableList", "FlatTable", "ColViews", + "ColItem", "ColExpression", "ColWildCard", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, + 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, + 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, + 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, + 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, + 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, + 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, + 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, + 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, + 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, + 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, + 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, + 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, + 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, + 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, + 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, + 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, + 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, + 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, + 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, + 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, + 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, + 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, + 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, + 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, + 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, + 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, + 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, + 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, + 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, + 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, + 595, 596, 597, 598, 599, 600, 601, 602, 603, 45, + 43, 42, 37, 64, 59, 44, 46, 36, 40, 41, + 63, 39, 47, 61, 60, 62, 604, 94, 91, 93, + 38, 124, 126 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint16 yyr1[] = +{ + 0, 373, 374, 375, 375, 375, 376, 376, 378, 377, + 379, 379, 380, 380, 381, 381, 382, 382, 382, 383, + 383, 383, 383, 384, 384, 384, 384, 384, 385, 386, + 386, 386, 386, 387, 388, 388, 388, 388, 389, 389, + 389, 390, 390, 391, 392, 392, 392, 392, 393, 393, + 393, 393, 393, 393, 394, 394, 394, 394, 394, 394, + 394, 395, 395, 395, 396, 396, 396, 397, 397, 397, + 397, 397, 398, 398, 398, 398, 399, 399, 399, 399, + 399, 399, 399, 399, 399, 399, 399, 399, 399, 400, + 401, 402, 402, 403, 404, 404, 405, 405, 405, 406, + 406, 407, 407, 407, 407, 408, 408, 409, 409 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 1, 3, 1, 2, 1, 1, 0, 7, + 3, 1, 2, 3, 2, 1, 2, 2, 1, 1, + 4, 4, 0, 2, 3, 2, 3, 4, 1, 1, + 3, 4, 4, 2, 1, 2, 3, 4, 1, 3, + 1, 1, 1, 1, 3, 3, 3, 1, 3, 3, + 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, + 1, 2, 2, 1, 3, 3, 1, 3, 3, 3, + 3, 1, 3, 3, 3, 1, 2, 2, 2, 2, + 1, 1, 2, 3, 1, 1, 1, 1, 1, 3, + 3, 3, 3, 2, 3, 1, 1, 2, 3, 3, + 1, 1, 1, 3, 2, 1, 4, 1, 3 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 0, 0, 28, 0, 2, 4, 6, 7, 0, 0, + 1, 5, 85, 0, 0, 0, 84, 86, 87, 80, + 81, 0, 0, 107, 0, 0, 105, 43, 47, 53, + 60, 63, 66, 71, 75, 88, 25, 23, 100, 101, + 102, 8, 3, 0, 96, 93, 95, 80, 79, 0, + 0, 82, 76, 77, 0, 78, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 61, 62, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 26, 29, 24, 0, 104, 0, + 0, 0, 97, 0, 0, 83, 108, 0, 0, 89, + 44, 45, 46, 49, 51, 52, 50, 48, 57, 56, + 54, 55, 58, 59, 64, 65, 68, 67, 69, 70, + 73, 74, 72, 0, 33, 99, 0, 27, 103, 0, + 106, 98, 94, 0, 90, 40, 38, 30, 34, 0, + 22, 0, 11, 92, 91, 0, 32, 41, 42, 0, + 35, 31, 19, 0, 12, 0, 9, 39, 36, 0, + 0, 0, 18, 0, 0, 13, 15, 10, 37, 0, + 0, 17, 16, 14, 20, 21 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int16 yydefgoto[] = +{ + -1, 3, 4, 5, 6, 89, 141, 142, 165, 166, + 154, 7, 8, 84, 85, 137, 138, 150, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 51, 98, + 36, 45, 46, 37, 38, 39, 40 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -336 +static const yytype_int16 yypact[] = +{ + -67, -271, -336, 39, -336, -311, -336, -336, -50, -265, + -336, -67, -336, -298, -262, -37, -336, -336, -336, -335, + -336, -37, -37, -336, -37, -37, -336, -336, -18, -135, + -142, -336, -7, -325, -332, -336, -336, -133, -336, -19, + -336, -336, -336, -40, -9, -291, -336, -318, -336, -309, + -37, -336, -336, -336, -292, -336, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -336, -336, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, 26, -37, -47, -336, -153, -216, -251, -336, -284, + -273, -237, -336, -262, -231, -336, -336, -260, -263, -336, + -336, -336, -336, -336, -336, -336, -336, -336, -336, -336, + -336, -336, -336, -336, -336, -336, -336, -336, -336, -336, + -336, -336, -336, -261, -336, -336, 51, -336, -336, -227, + -336, -336, -336, -37, -336, -336, -258, -239, -25, -261, + -3, -324, -336, -260, -336, -224, -336, -336, -336, -261, + -254, -336, -256, -255, -24, -227, -336, -336, -336, -261, + -169, -168, -336, -104, -63, -24, -336, -336, -336, -250, + -249, -336, -336, -336, -336, -336 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int16 yypgoto[] = +{ + -336, -336, 97, -336, -336, -336, -336, -44, -336, -53, + -336, -336, -336, 27, -23, -122, -336, -336, -6, -1, + 18, -17, -336, -21, 8, 11, 7, -336, -336, -16, + 78, -336, 23, -336, 35, 76, -336 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 147, 152, 56, 12, 87, 14, 12, 162, 1, 81, + 59, 64, 135, 12, 91, 95, 12, 151, 54, 78, + 79, 49, 48, 50, 74, 75, 9, 158, 52, 53, + 80, 155, 55, 72, 73, 156, 65, 168, 94, 10, + 50, 60, 96, 11, 97, 76, 77, 108, 109, 110, + 111, 114, 115, 112, 113, 100, 101, 102, 13, 41, + 43, 13, 44, 136, 93, 66, 67, 99, 13, 123, + 68, 69, 126, 128, 129, 148, 124, 103, 104, 105, + 106, 107, 116, 117, 118, 119, 130, 131, 14, 120, + 121, 122, 81, 95, 139, 133, 134, 140, 145, 82, + 157, 159, 160, 161, 169, 170, 171, 172, 42, 174, + 175, 167, 173, 127, 146, 86, 132, 144, 125, 90, + 0, 0, 82, 0, 0, 0, 0, 143, 0, 70, + 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 15, 0, 0, 15, + 16, 0, 0, 16, 0, 0, 15, 0, 0, 15, + 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, + 0, 0, 163, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 82, 57, 0, 0, 0, + 0, 0, 0, 0, 0, 164, 0, 0, 0, 0, + 0, 0, 83, 17, 18, 0, 17, 18, 61, 62, + 63, 0, 0, 17, 18, 0, 17, 18, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 19, 0, 20, 19, 0, 20, + 0, 0, 0, 0, 47, 0, 20, 47, 0, 20, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, + 22, 23, 21, 22, 23, 88, 0, 0, 24, 21, + 22, 24, 21, 22, 0, 92, 0, 0, 24, 0, + 0, 24, 25, 0, 0, 25, 58, 0, 153, 0, + 149, 0, 25, 0, 0, 25 +}; + +static const yytype_int16 yycheck[] = +{ + 25, 4, 20, 53, 23, 138, 53, 31, 75, 225, + 145, 153, 273, 53, 23, 324, 53, 139, 24, 351, + 352, 356, 15, 358, 349, 350, 297, 149, 21, 22, + 362, 355, 25, 40, 41, 359, 178, 159, 356, 0, + 358, 176, 351, 354, 50, 370, 371, 64, 65, 66, + 67, 72, 73, 70, 71, 56, 57, 58, 108, 324, + 358, 108, 324, 324, 355, 207, 208, 359, 108, 43, + 212, 213, 225, 324, 358, 100, 82, 59, 60, 61, + 62, 63, 74, 75, 76, 77, 359, 324, 138, 78, + 79, 80, 225, 324, 43, 355, 359, 324, 356, 338, + 324, 355, 358, 358, 273, 273, 210, 170, 11, 359, + 359, 155, 165, 86, 137, 37, 93, 133, 83, 43, + -1, -1, 338, -1, -1, -1, -1, 133, -1, 271, + 272, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 206, -1, -1, 206, + 210, -1, -1, 210, -1, -1, 206, -1, -1, 206, + 210, -1, -1, 210, -1, -1, -1, -1, -1, -1, + -1, -1, 206, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 265, -1, + -1, -1, -1, -1, -1, 338, 224, -1, -1, -1, + -1, -1, -1, -1, -1, 239, -1, -1, -1, -1, + -1, -1, 355, 273, 274, -1, 273, 274, 363, 364, + 365, -1, -1, 273, 274, -1, 273, 274, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 324, -1, 326, 324, -1, 326, + -1, -1, -1, -1, 324, -1, 326, 324, -1, 326, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 349, + 350, 351, 349, 350, 351, 324, -1, -1, 358, 349, + 350, 358, 349, 350, -1, 324, -1, -1, 358, -1, + -1, 358, 372, -1, -1, 372, 344, -1, 331, -1, + 355, -1, 372, -1, -1, 372 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint16 yystos[] = +{ + 0, 75, 265, 374, 375, 376, 377, 384, 385, 297, + 0, 354, 53, 108, 138, 206, 210, 273, 274, 324, + 326, 349, 350, 351, 358, 372, 391, 392, 393, 394, + 395, 396, 397, 398, 399, 400, 403, 406, 407, 408, + 409, 324, 375, 358, 324, 404, 405, 324, 399, 356, + 358, 401, 399, 399, 391, 399, 20, 224, 344, 145, + 176, 363, 364, 365, 153, 178, 207, 208, 212, 213, + 271, 272, 40, 41, 349, 350, 370, 371, 351, 352, + 362, 225, 338, 355, 386, 387, 403, 23, 324, 378, + 408, 23, 324, 355, 356, 324, 351, 391, 402, 359, + 392, 392, 392, 393, 393, 393, 393, 393, 394, 394, + 394, 394, 394, 394, 396, 396, 397, 397, 397, 397, + 398, 398, 398, 43, 391, 407, 225, 386, 324, 358, + 359, 324, 405, 355, 359, 273, 324, 388, 389, 43, + 324, 379, 380, 391, 402, 356, 387, 25, 100, 355, + 390, 388, 4, 331, 383, 355, 359, 324, 388, 355, + 358, 358, 31, 206, 239, 381, 382, 380, 388, 273, + 273, 210, 170, 382, 359, 359 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (YYLEX_PARAM) +#else +# define YYLEX yylex () +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include <stdio.h> /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, const YYSTYPE * const yyvaluep) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + const YYSTYPE * const yyvaluep; +#endif +{ + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, const YYSTYPE * const yyvaluep) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + const YYSTYPE * const yyvaluep; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + yy_symbol_value_print (yyoutput, yytype, yyvaluep); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) +#else +static void +yy_stack_print (bottom, top) + yytype_int16 *bottom; + yytype_int16 *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, + int yyrule) +#else +static void +yy_reduce_print (yyvsp, yyrule + ) + YYSTYPE *yyvsp; + + int yyrule; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + fprintf (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + ); + fprintf (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, Rule); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) +#else +static void +yydestruct (yymsg, yytype, yyvaluep) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; +#endif +{ + YYUSE (yyvaluep); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (void); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + +/* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void) +#else +int +yyparse () + +#endif +#endif +{ + + int yystate; + int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss = yyssa; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp; + + + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + look-ahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to look-ahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: +#line 581 "sqlparser.y" + { +//todo: multiple statements +//todo: not only "select" statements + parser->setOperation(Parser::OP_Select); + parser->setQuerySchema((yyvsp[(1) - (1)].querySchema)); +;} + break; + + case 3: +#line 591 "sqlparser.y" + { +//todo: multiple statements +;} + break; + + case 5: +#line 596 "sqlparser.y" + { + (yyval.querySchema) = (yyvsp[(1) - (2)].querySchema); +;} + break; + + case 6: +#line 606 "sqlparser.y" + { +YYACCEPT; +;} + break; + + case 7: +#line 610 "sqlparser.y" + { + (yyval.querySchema) = (yyvsp[(1) - (1)].querySchema); +;} + break; + + case 8: +#line 617 "sqlparser.y" + { + parser->setOperation(Parser::OP_CreateTable); + parser->createTable((yyvsp[(3) - (3)].stringValue)->latin1()); + delete (yyvsp[(3) - (3)].stringValue); +;} + break; + + case 11: +#line 627 "sqlparser.y" + { +;} + break; + + case 12: +#line 633 "sqlparser.y" + { + KexiDBDbg << "adding field " << *(yyvsp[(1) - (2)].stringValue) << endl; + field->setName((yyvsp[(1) - (2)].stringValue)->latin1()); + parser->table()->addField(field); + field = 0; + delete (yyvsp[(1) - (2)].stringValue); +;} + break; + + case 13: +#line 641 "sqlparser.y" + { + KexiDBDbg << "adding field " << *(yyvsp[(1) - (3)].stringValue) << endl; + field->setName(*(yyvsp[(1) - (3)].stringValue)); + delete (yyvsp[(1) - (3)].stringValue); + parser->table()->addField(field); + +// if(field->isPrimaryKey()) +// parser->table()->addPrimaryKey(field->name()); + +// delete field; +// field = 0; +;} + break; + + case 15: +#line 657 "sqlparser.y" + { +;} + break; + + case 16: +#line 663 "sqlparser.y" + { + field->setPrimaryKey(true); + KexiDBDbg << "primary" << endl; +;} + break; + + case 17: +#line 668 "sqlparser.y" + { + field->setNotNull(true); + KexiDBDbg << "not_null" << endl; +;} + break; + + case 18: +#line 673 "sqlparser.y" + { + field->setAutoIncrement(true); + KexiDBDbg << "ainc" << endl; +;} + break; + + case 19: +#line 681 "sqlparser.y" + { + field = new Field(); + field->setType((yyvsp[(1) - (1)].colType)); +;} + break; + + case 20: +#line 686 "sqlparser.y" + { + KexiDBDbg << "sql + length" << endl; + field = new Field(); + field->setPrecision((yyvsp[(3) - (4)].integerValue)); + field->setType((yyvsp[(1) - (4)].colType)); +;} + break; + + case 21: +#line 693 "sqlparser.y" + { + field = new Field(); + field->setPrecision((yyvsp[(3) - (4)].integerValue)); + field->setType(Field::Text); +;} + break; + + case 22: +#line 699 "sqlparser.y" + { + // SQLITE compatibillity + field = new Field(); + field->setType(Field::InvalidType); +;} + break; + + case 23: +#line 708 "sqlparser.y" + { + KexiDBDbg << "Select ColViews=" << (yyvsp[(2) - (2)].exprList)->debugString() << endl; + + if (!((yyval.querySchema) = buildSelectQuery( (yyvsp[(1) - (2)].querySchema), (yyvsp[(2) - (2)].exprList) ))) + return 0; +;} + break; + + case 24: +#line 715 "sqlparser.y" + { + if (!((yyval.querySchema) = buildSelectQuery( (yyvsp[(1) - (3)].querySchema), (yyvsp[(2) - (3)].exprList), (yyvsp[(3) - (3)].exprList) ))) + return 0; +;} + break; + + case 25: +#line 720 "sqlparser.y" + { + KexiDBDbg << "Select ColViews Tables" << endl; + if (!((yyval.querySchema) = buildSelectQuery( (yyvsp[(1) - (2)].querySchema), 0, (yyvsp[(2) - (2)].exprList) ))) + return 0; +;} + break; + + case 26: +#line 726 "sqlparser.y" + { + KexiDBDbg << "Select ColViews Conditions" << endl; + if (!((yyval.querySchema) = buildSelectQuery( (yyvsp[(1) - (3)].querySchema), (yyvsp[(2) - (3)].exprList), 0, (yyvsp[(3) - (3)].selectOptions) ))) + return 0; +;} + break; + + case 27: +#line 732 "sqlparser.y" + { + KexiDBDbg << "Select ColViews Tables SelectOptions" << endl; + if (!((yyval.querySchema) = buildSelectQuery( (yyvsp[(1) - (4)].querySchema), (yyvsp[(2) - (4)].exprList), (yyvsp[(3) - (4)].exprList), (yyvsp[(4) - (4)].selectOptions) ))) + return 0; +;} + break; + + case 28: +#line 741 "sqlparser.y" + { + KexiDBDbg << "SELECT" << endl; +// parser->createSelect(); +// parser->setOperation(Parser::OP_Select); + (yyval.querySchema) = new QuerySchema(); +;} + break; + + case 29: +#line 751 "sqlparser.y" + { + KexiDBDbg << "WhereClause" << endl; + (yyval.selectOptions) = new SelectOptionsInternal; + (yyval.selectOptions)->whereExpr = (yyvsp[(1) - (1)].expr); +;} + break; + + case 30: +#line 757 "sqlparser.y" + { + KexiDBDbg << "OrderByClause" << endl; + (yyval.selectOptions) = new SelectOptionsInternal; + (yyval.selectOptions)->orderByColumns = (yyvsp[(3) - (3)].orderByColumns); +;} + break; + + case 31: +#line 763 "sqlparser.y" + { + KexiDBDbg << "WhereClause ORDER BY OrderByClause" << endl; + (yyval.selectOptions) = new SelectOptionsInternal; + (yyval.selectOptions)->whereExpr = (yyvsp[(1) - (4)].expr); + (yyval.selectOptions)->orderByColumns = (yyvsp[(4) - (4)].orderByColumns); +;} + break; + + case 32: +#line 770 "sqlparser.y" + { + KexiDBDbg << "OrderByClause WhereClause" << endl; + (yyval.selectOptions) = new SelectOptionsInternal; + (yyval.selectOptions)->whereExpr = (yyvsp[(4) - (4)].expr); + (yyval.selectOptions)->orderByColumns = (yyvsp[(3) - (4)].orderByColumns); +;} + break; + + case 33: +#line 780 "sqlparser.y" + { + (yyval.expr) = (yyvsp[(2) - (2)].expr); +;} + break; + + case 34: +#line 789 "sqlparser.y" + { + KexiDBDbg << "ORDER BY IDENTIFIER" << endl; + (yyval.orderByColumns) = new OrderByColumnInternal::List; + OrderByColumnInternal orderByColumn; + orderByColumn.setColumnByNameOrNumber( *(yyvsp[(1) - (1)].variantValue) ); + (yyval.orderByColumns)->append( orderByColumn ); + delete (yyvsp[(1) - (1)].variantValue); +;} + break; + + case 35: +#line 798 "sqlparser.y" + { + KexiDBDbg << "ORDER BY IDENTIFIER OrderByOption" << endl; + (yyval.orderByColumns) = new OrderByColumnInternal::List; + OrderByColumnInternal orderByColumn; + orderByColumn.setColumnByNameOrNumber( *(yyvsp[(1) - (2)].variantValue) ); + orderByColumn.ascending = (yyvsp[(2) - (2)].booleanValue); + (yyval.orderByColumns)->append( orderByColumn ); + delete (yyvsp[(1) - (2)].variantValue); +;} + break; + + case 36: +#line 808 "sqlparser.y" + { + (yyval.orderByColumns) = (yyvsp[(3) - (3)].orderByColumns); + OrderByColumnInternal orderByColumn; + orderByColumn.setColumnByNameOrNumber( *(yyvsp[(1) - (3)].variantValue) ); + (yyval.orderByColumns)->append( orderByColumn ); + delete (yyvsp[(1) - (3)].variantValue); +;} + break; + + case 37: +#line 816 "sqlparser.y" + { + (yyval.orderByColumns) = (yyvsp[(4) - (4)].orderByColumns); + OrderByColumnInternal orderByColumn; + orderByColumn.setColumnByNameOrNumber( *(yyvsp[(1) - (4)].variantValue) ); + orderByColumn.ascending = (yyvsp[(2) - (4)].booleanValue); + (yyval.orderByColumns)->append( orderByColumn ); + delete (yyvsp[(1) - (4)].variantValue); +;} + break; + + case 38: +#line 828 "sqlparser.y" + { + (yyval.variantValue) = new QVariant( *(yyvsp[(1) - (1)].stringValue) ); + KexiDBDbg << "OrderByColumnId: " << *(yyval.variantValue) << endl; + delete (yyvsp[(1) - (1)].stringValue); +;} + break; + + case 39: +#line 834 "sqlparser.y" + { + (yyval.variantValue) = new QVariant( *(yyvsp[(1) - (3)].stringValue) + "." + *(yyvsp[(3) - (3)].stringValue) ); + KexiDBDbg << "OrderByColumnId: " << *(yyval.variantValue) << endl; + delete (yyvsp[(1) - (3)].stringValue); + delete (yyvsp[(3) - (3)].stringValue); +;} + break; + + case 40: +#line 841 "sqlparser.y" + { + (yyval.variantValue) = new QVariant((yyvsp[(1) - (1)].integerValue)); + KexiDBDbg << "OrderByColumnId: " << *(yyval.variantValue) << endl; +;} + break; + + case 41: +#line 848 "sqlparser.y" + { + (yyval.booleanValue) = true; +;} + break; + + case 42: +#line 852 "sqlparser.y" + { + (yyval.booleanValue) = false; +;} + break; + + case 44: +#line 864 "sqlparser.y" + { +// KexiDBDbg << "AND " << $3.debugString() << endl; + (yyval.expr) = new BinaryExpr( KexiDBExpr_Logical, (yyvsp[(1) - (3)].expr), AND, (yyvsp[(3) - (3)].expr) ); +;} + break; + + case 45: +#line 869 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr( KexiDBExpr_Logical, (yyvsp[(1) - (3)].expr), OR, (yyvsp[(3) - (3)].expr) ); +;} + break; + + case 46: +#line 873 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr( KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), XOR, (yyvsp[(3) - (3)].expr) ); +;} + break; + + case 48: +#line 883 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), '>', (yyvsp[(3) - (3)].expr)); +;} + break; + + case 49: +#line 887 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), GREATER_OR_EQUAL, (yyvsp[(3) - (3)].expr)); +;} + break; + + case 50: +#line 891 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), '<', (yyvsp[(3) - (3)].expr)); +;} + break; + + case 51: +#line 895 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), LESS_OR_EQUAL, (yyvsp[(3) - (3)].expr)); +;} + break; + + case 52: +#line 899 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), '=', (yyvsp[(3) - (3)].expr)); +;} + break; + + case 54: +#line 909 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), NOT_EQUAL, (yyvsp[(3) - (3)].expr)); +;} + break; + + case 55: +#line 914 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), NOT_EQUAL2, (yyvsp[(3) - (3)].expr)); +;} + break; + + case 56: +#line 918 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), LIKE, (yyvsp[(3) - (3)].expr)); +;} + break; + + case 57: +#line 922 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), SQL_IN, (yyvsp[(3) - (3)].expr)); +;} + break; + + case 58: +#line 926 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), SIMILAR_TO, (yyvsp[(3) - (3)].expr)); +;} + break; + + case 59: +#line 930 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), NOT_SIMILAR_TO, (yyvsp[(3) - (3)].expr)); +;} + break; + + case 61: +#line 940 "sqlparser.y" + { + (yyval.expr) = new UnaryExpr( SQL_IS_NULL, (yyvsp[(1) - (2)].expr) ); +;} + break; + + case 62: +#line 944 "sqlparser.y" + { + (yyval.expr) = new UnaryExpr( SQL_IS_NOT_NULL, (yyvsp[(1) - (2)].expr) ); +;} + break; + + case 64: +#line 954 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), BITWISE_SHIFT_LEFT, (yyvsp[(3) - (3)].expr)); +;} + break; + + case 65: +#line 958 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), BITWISE_SHIFT_RIGHT, (yyvsp[(3) - (3)].expr)); +;} + break; + + case 67: +#line 968 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '+', (yyvsp[(3) - (3)].expr)); + (yyval.expr)->debug(); +;} + break; + + case 68: +#line 973 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '-', (yyvsp[(3) - (3)].expr)); +;} + break; + + case 69: +#line 977 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '&', (yyvsp[(3) - (3)].expr)); +;} + break; + + case 70: +#line 981 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '|', (yyvsp[(3) - (3)].expr)); +;} + break; + + case 72: +#line 991 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '/', (yyvsp[(3) - (3)].expr)); +;} + break; + + case 73: +#line 995 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '*', (yyvsp[(3) - (3)].expr)); +;} + break; + + case 74: +#line 999 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '%', (yyvsp[(3) - (3)].expr)); +;} + break; + + case 76: +#line 1010 "sqlparser.y" + { + (yyval.expr) = new UnaryExpr( '-', (yyvsp[(2) - (2)].expr) ); +;} + break; + + case 77: +#line 1014 "sqlparser.y" + { + (yyval.expr) = new UnaryExpr( '+', (yyvsp[(2) - (2)].expr) ); +;} + break; + + case 78: +#line 1018 "sqlparser.y" + { + (yyval.expr) = new UnaryExpr( '~', (yyvsp[(2) - (2)].expr) ); +;} + break; + + case 79: +#line 1022 "sqlparser.y" + { + (yyval.expr) = new UnaryExpr( NOT, (yyvsp[(2) - (2)].expr) ); +;} + break; + + case 80: +#line 1026 "sqlparser.y" + { + (yyval.expr) = new VariableExpr( *(yyvsp[(1) - (1)].stringValue) ); + +//TODO: simplify this later if that's 'only one field name' expression + KexiDBDbg << " + identifier: " << *(yyvsp[(1) - (1)].stringValue) << endl; + delete (yyvsp[(1) - (1)].stringValue); +;} + break; + + case 81: +#line 1034 "sqlparser.y" + { + (yyval.expr) = new QueryParameterExpr( *(yyvsp[(1) - (1)].stringValue) ); + KexiDBDbg << " + query parameter: " << (yyval.expr)->debugString() << endl; + delete (yyvsp[(1) - (1)].stringValue); +;} + break; + + case 82: +#line 1040 "sqlparser.y" + { + KexiDBDbg << " + function: " << *(yyvsp[(1) - (2)].stringValue) << "(" << (yyvsp[(2) - (2)].exprList)->debugString() << ")" << endl; + (yyval.expr) = new FunctionExpr(*(yyvsp[(1) - (2)].stringValue), (yyvsp[(2) - (2)].exprList)); + delete (yyvsp[(1) - (2)].stringValue); +;} + break; + + case 83: +#line 1047 "sqlparser.y" + { + (yyval.expr) = new VariableExpr( *(yyvsp[(1) - (3)].stringValue) + "." + *(yyvsp[(3) - (3)].stringValue) ); + KexiDBDbg << " + identifier.identifier: " << *(yyvsp[(1) - (3)].stringValue) << "." << *(yyvsp[(3) - (3)].stringValue) << endl; + delete (yyvsp[(1) - (3)].stringValue); + delete (yyvsp[(3) - (3)].stringValue); +;} + break; + + case 84: +#line 1054 "sqlparser.y" + { + (yyval.expr) = new ConstExpr( SQL_NULL, QVariant() ); + KexiDBDbg << " + NULL" << endl; +// $$ = new Field(); + //$$->setName(QString::null); +;} + break; + + case 85: +#line 1061 "sqlparser.y" + { + (yyval.expr) = new ConstExpr( CHARACTER_STRING_LITERAL, *(yyvsp[(1) - (1)].stringValue) ); + KexiDBDbg << " + constant " << (yyvsp[(1) - (1)].stringValue) << endl; + delete (yyvsp[(1) - (1)].stringValue); +;} + break; + + case 86: +#line 1067 "sqlparser.y" + { + QVariant val; + if ((yyvsp[(1) - (1)].integerValue) <= INT_MAX && (yyvsp[(1) - (1)].integerValue) >= INT_MIN) + val = (int)(yyvsp[(1) - (1)].integerValue); + else if ((yyvsp[(1) - (1)].integerValue) <= UINT_MAX && (yyvsp[(1) - (1)].integerValue) >= 0) + val = (uint)(yyvsp[(1) - (1)].integerValue); + else if ((yyvsp[(1) - (1)].integerValue) <= (Q_LLONG)LLONG_MAX && (yyvsp[(1) - (1)].integerValue) >= (Q_LLONG)LLONG_MIN) + val = (Q_LLONG)(yyvsp[(1) - (1)].integerValue); + +// if ($1 < ULLONG_MAX) +// val = (Q_ULLONG)$1; +//TODO ok? + + (yyval.expr) = new ConstExpr( INTEGER_CONST, val ); + KexiDBDbg << " + int constant: " << val.toString() << endl; +;} + break; + + case 87: +#line 1084 "sqlparser.y" + { + (yyval.expr) = new ConstExpr( REAL_CONST, QPoint( (yyvsp[(1) - (1)].realValue).integer, (yyvsp[(1) - (1)].realValue).fractional ) ); + KexiDBDbg << " + real constant: " << (yyvsp[(1) - (1)].realValue).integer << "." << (yyvsp[(1) - (1)].realValue).fractional << endl; +;} + break; + + case 89: +#line 1095 "sqlparser.y" + { + KexiDBDbg << "(expr)" << endl; + (yyval.expr) = new UnaryExpr('(', (yyvsp[(2) - (3)].expr)); +;} + break; + + case 90: +#line 1103 "sqlparser.y" + { +// $$ = new NArgExpr(0, 0); +// $$->add( $1 ); +// $$->add( $3 ); + (yyval.exprList) = (yyvsp[(2) - (3)].exprList); +;} + break; + + case 91: +#line 1113 "sqlparser.y" + { + (yyval.exprList) = (yyvsp[(3) - (3)].exprList); + (yyval.exprList)->prepend( (yyvsp[(1) - (3)].expr) ); +;} + break; + + case 92: +#line 1118 "sqlparser.y" + { + (yyval.exprList) = new NArgExpr(0, 0); + (yyval.exprList)->add( (yyvsp[(1) - (3)].expr) ); + (yyval.exprList)->add( (yyvsp[(3) - (3)].expr) ); +;} + break; + + case 93: +#line 1127 "sqlparser.y" + { + (yyval.exprList) = (yyvsp[(2) - (2)].exprList); +;} + break; + + case 94: +#line 1172 "sqlparser.y" + { + (yyval.exprList) = (yyvsp[(1) - (3)].exprList); + (yyval.exprList)->add((yyvsp[(3) - (3)].expr)); +;} + break; + + case 95: +#line 1177 "sqlparser.y" + { + (yyval.exprList) = new NArgExpr(KexiDBExpr_TableList, IDENTIFIER); //ok? + (yyval.exprList)->add((yyvsp[(1) - (1)].expr)); +;} + break; + + case 96: +#line 1185 "sqlparser.y" + { + KexiDBDbg << "FROM: '" << *(yyvsp[(1) - (1)].stringValue) << "'" << endl; + (yyval.expr) = new VariableExpr(*(yyvsp[(1) - (1)].stringValue)); + + /* +//TODO: this isn't ok for more tables: + Field::ListIterator it = parser->select()->fieldsIterator(); + for(Field *item; (item = it.current()); ++it) + { + if(item->table() == dummy) + { + item->setTable(schema); + } + + if(item->table() && !item->isQueryAsterisk()) + { + Field *f = item->table()->field(item->name()); + if(!f) + { + ParserError err(i18n("Field List Error"), i18n("Unknown column '%1' in table '%2'").arg(item->name()).arg(schema->name()), ctoken, current); + parser->setError(err); + yyerror("fieldlisterror"); + } + } + }*/ + delete (yyvsp[(1) - (1)].stringValue); +;} + break; + + case 97: +#line 1213 "sqlparser.y" + { + //table + alias + (yyval.expr) = new BinaryExpr( + KexiDBExpr_SpecialBinary, + new VariableExpr(*(yyvsp[(1) - (2)].stringValue)), 0, + new VariableExpr(*(yyvsp[(2) - (2)].stringValue)) + ); + delete (yyvsp[(1) - (2)].stringValue); + delete (yyvsp[(2) - (2)].stringValue); +;} + break; + + case 98: +#line 1224 "sqlparser.y" + { + //table + alias + (yyval.expr) = new BinaryExpr( + KexiDBExpr_SpecialBinary, + new VariableExpr(*(yyvsp[(1) - (3)].stringValue)), AS, + new VariableExpr(*(yyvsp[(3) - (3)].stringValue)) + ); + delete (yyvsp[(1) - (3)].stringValue); + delete (yyvsp[(3) - (3)].stringValue); +;} + break; + + case 99: +#line 1240 "sqlparser.y" + { + (yyval.exprList) = (yyvsp[(1) - (3)].exprList); + (yyval.exprList)->add( (yyvsp[(3) - (3)].expr) ); + KexiDBDbg << "ColViews: ColViews , ColItem" << endl; +;} + break; + + case 100: +#line 1246 "sqlparser.y" + { + (yyval.exprList) = new NArgExpr(0,0); + (yyval.exprList)->add( (yyvsp[(1) - (1)].expr) ); + KexiDBDbg << "ColViews: ColItem" << endl; +;} + break; + + case 101: +#line 1255 "sqlparser.y" + { +// $$ = new Field(); +// dummy->addField($$); +// $$->setExpression( $1 ); +// parser->select()->addField($$); + (yyval.expr) = (yyvsp[(1) - (1)].expr); + KexiDBDbg << " added column expr: '" << (yyvsp[(1) - (1)].expr)->debugString() << "'" << endl; +;} + break; + + case 102: +#line 1264 "sqlparser.y" + { + (yyval.expr) = (yyvsp[(1) - (1)].expr); + KexiDBDbg << " added column wildcard: '" << (yyvsp[(1) - (1)].expr)->debugString() << "'" << endl; +;} + break; + + case 103: +#line 1269 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr( + KexiDBExpr_SpecialBinary, (yyvsp[(1) - (3)].expr), AS, + new VariableExpr(*(yyvsp[(3) - (3)].stringValue)) + ); + KexiDBDbg << " added column expr: " << (yyval.expr)->debugString() << endl; + delete (yyvsp[(3) - (3)].stringValue); +;} + break; + + case 104: +#line 1278 "sqlparser.y" + { + (yyval.expr) = new BinaryExpr( + KexiDBExpr_SpecialBinary, (yyvsp[(1) - (2)].expr), 0, + new VariableExpr(*(yyvsp[(2) - (2)].stringValue)) + ); + KexiDBDbg << " added column expr: " << (yyval.expr)->debugString() << endl; + delete (yyvsp[(2) - (2)].stringValue); +;} + break; + + case 105: +#line 1290 "sqlparser.y" + { + (yyval.expr) = (yyvsp[(1) - (1)].expr); +;} + break; + + case 106: +#line 1334 "sqlparser.y" + { + (yyval.expr) = (yyvsp[(3) - (4)].expr); +//TODO +// $$->setName("DISTINCT(" + $3->name() + ")"); +;} + break; + + case 107: +#line 1343 "sqlparser.y" + { + (yyval.expr) = new VariableExpr("*"); + KexiDBDbg << "all columns" << endl; + +// QueryAsterisk *ast = new QueryAsterisk(parser->select(), dummy); +// parser->select()->addAsterisk(ast); +// requiresTable = true; +;} + break; + + case 108: +#line 1352 "sqlparser.y" + { + QString s( *(yyvsp[(1) - (3)].stringValue) ); + s += ".*"; + (yyval.expr) = new VariableExpr(s); + KexiDBDbg << " + all columns from " << s << endl; + delete (yyvsp[(1) - (3)].stringValue); +;} + break; + + +/* Line 1267 of yacc.c. */ +#line 3256 "sqlparser.tab.c" + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (yymsg); + } + else + { + yyerror (YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + yystos[yystate], yyvsp); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEOF && yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + return yyresult; +} + + +#line 1367 "sqlparser.y" + + + +const char * const tname(int offset) { return yytname[offset]; } diff --git a/kexi/kexidb/parser/sqlparser.h b/kexi/kexidb/parser/sqlparser.h new file mode 100644 index 00000000..7caf4b87 --- /dev/null +++ b/kexi/kexidb/parser/sqlparser.h @@ -0,0 +1,778 @@ +#ifndef _SQLPARSER_H_ +#define _SQLPARSER_H_ +#include "field.h" +#include "parser.h" +#include "sqltypes.h" + +bool parseData(KexiDB::Parser *p, const char *data); +/* A Bison parser, made by GNU Bison 2.2. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + UMINUS = 258, + SQL_TYPE = 259, + SQL_ABS = 260, + ACOS = 261, + AMPERSAND = 262, + SQL_ABSOLUTE = 263, + ADA = 264, + ADD = 265, + ADD_DAYS = 266, + ADD_HOURS = 267, + ADD_MINUTES = 268, + ADD_MONTHS = 269, + ADD_SECONDS = 270, + ADD_YEARS = 271, + ALL = 272, + ALLOCATE = 273, + ALTER = 274, + AND = 275, + ANY = 276, + ARE = 277, + AS = 278, + ASIN = 279, + ASC = 280, + ASCII = 281, + ASSERTION = 282, + ATAN = 283, + ATAN2 = 284, + AUTHORIZATION = 285, + AUTO_INCREMENT = 286, + AVG = 287, + BEFORE = 288, + SQL_BEGIN = 289, + BETWEEN = 290, + BIGINT = 291, + BINARY = 292, + BIT = 293, + BIT_LENGTH = 294, + BITWISE_SHIFT_LEFT = 295, + BITWISE_SHIFT_RIGHT = 296, + BREAK = 297, + BY = 298, + CASCADE = 299, + CASCADED = 300, + CASE = 301, + CAST = 302, + CATALOG = 303, + CEILING = 304, + CENTER = 305, + SQL_CHAR = 306, + CHAR_LENGTH = 307, + CHARACTER_STRING_LITERAL = 308, + CHECK = 309, + CLOSE = 310, + COALESCE = 311, + COBOL = 312, + COLLATE = 313, + COLLATION = 314, + COLUMN = 315, + COMMIT = 316, + COMPUTE = 317, + CONCAT = 318, + CONCATENATION = 319, + CONNECT = 320, + CONNECTION = 321, + CONSTRAINT = 322, + CONSTRAINTS = 323, + CONTINUE = 324, + CONVERT = 325, + CORRESPONDING = 326, + COS = 327, + COT = 328, + COUNT = 329, + CREATE = 330, + CURDATE = 331, + CURRENT = 332, + CURRENT_DATE = 333, + CURRENT_TIME = 334, + CURRENT_TIMESTAMP = 335, + CURTIME = 336, + CURSOR = 337, + DATABASE = 338, + SQL_DATE = 339, + DATE_FORMAT = 340, + DATE_REMAINDER = 341, + DATE_VALUE = 342, + DAY = 343, + DAYOFMONTH = 344, + DAYOFWEEK = 345, + DAYOFYEAR = 346, + DAYS_BETWEEN = 347, + DEALLOCATE = 348, + DEC = 349, + DECLARE = 350, + DEFAULT = 351, + DEFERRABLE = 352, + DEFERRED = 353, + SQL_DELETE = 354, + DESC = 355, + DESCRIBE = 356, + DESCRIPTOR = 357, + DIAGNOSTICS = 358, + DICTIONARY = 359, + DIRECTORY = 360, + DISCONNECT = 361, + DISPLACEMENT = 362, + DISTINCT = 363, + DOMAIN_TOKEN = 364, + SQL_DOUBLE = 365, + DOUBLE_QUOTED_STRING = 366, + DROP = 367, + ELSE = 368, + END = 369, + END_EXEC = 370, + EQUAL = 371, + ESCAPE = 372, + EXCEPT = 373, + SQL_EXCEPTION = 374, + EXEC = 375, + EXECUTE = 376, + EXISTS = 377, + EXP = 378, + EXPONENT = 379, + EXTERNAL = 380, + EXTRACT = 381, + SQL_FALSE = 382, + FETCH = 383, + FIRST = 384, + SQL_FLOAT = 385, + FLOOR = 386, + FN = 387, + FOR = 388, + FOREIGN = 389, + FORTRAN = 390, + FOUND = 391, + FOUR_DIGITS = 392, + FROM = 393, + FULL = 394, + GET = 395, + GLOBAL = 396, + GO = 397, + GOTO = 398, + GRANT = 399, + GREATER_OR_EQUAL = 400, + HAVING = 401, + HOUR = 402, + HOURS_BETWEEN = 403, + IDENTITY = 404, + IFNULL = 405, + SQL_IGNORE = 406, + IMMEDIATE = 407, + SQL_IN = 408, + INCLUDE = 409, + INDEX = 410, + INDICATOR = 411, + INITIALLY = 412, + INNER = 413, + INPUT = 414, + INSENSITIVE = 415, + INSERT = 416, + INTEGER = 417, + INTERSECT = 418, + INTERVAL = 419, + INTO = 420, + IS = 421, + ISOLATION = 422, + JOIN = 423, + JUSTIFY = 424, + KEY = 425, + LANGUAGE = 426, + LAST = 427, + LCASE = 428, + LEFT = 429, + LENGTH = 430, + LESS_OR_EQUAL = 431, + LEVEL = 432, + LIKE = 433, + LINE_WIDTH = 434, + LOCAL = 435, + LOCATE = 436, + LOG = 437, + SQL_LONG = 438, + LOWER = 439, + LTRIM = 440, + LTRIP = 441, + MATCH = 442, + SQL_MAX = 443, + MICROSOFT = 444, + SQL_MIN = 445, + MINUS = 446, + MINUTE = 447, + MINUTES_BETWEEN = 448, + MOD = 449, + MODIFY = 450, + MODULE = 451, + MONTH = 452, + MONTHS_BETWEEN = 453, + MUMPS = 454, + NAMES = 455, + NATIONAL = 456, + NCHAR = 457, + NEXT = 458, + NODUP = 459, + NONE = 460, + NOT = 461, + NOT_EQUAL = 462, + NOT_EQUAL2 = 463, + NOW = 464, + SQL_NULL = 465, + SQL_IS = 466, + SQL_IS_NULL = 467, + SQL_IS_NOT_NULL = 468, + NULLIF = 469, + NUMERIC = 470, + OCTET_LENGTH = 471, + ODBC = 472, + OF = 473, + SQL_OFF = 474, + SQL_ON = 475, + ONLY = 476, + OPEN = 477, + OPTION = 478, + OR = 479, + ORDER = 480, + OUTER = 481, + OUTPUT = 482, + OVERLAPS = 483, + PAGE = 484, + PARTIAL = 485, + SQL_PASCAL = 486, + PERSISTENT = 487, + CQL_PI = 488, + PLI = 489, + POSITION = 490, + PRECISION = 491, + PREPARE = 492, + PRESERVE = 493, + PRIMARY = 494, + PRIOR = 495, + PRIVILEGES = 496, + PROCEDURE = 497, + PRODUCT = 498, + PUBLIC = 499, + QUARTER = 500, + QUIT = 501, + RAND = 502, + READ_ONLY = 503, + REAL = 504, + REFERENCES = 505, + REPEAT = 506, + REPLACE = 507, + RESTRICT = 508, + REVOKE = 509, + RIGHT = 510, + ROLLBACK = 511, + ROWS = 512, + RPAD = 513, + RTRIM = 514, + SCHEMA = 515, + SCREEN_WIDTH = 516, + SCROLL = 517, + SECOND = 518, + SECONDS_BETWEEN = 519, + SELECT = 520, + SEQUENCE = 521, + SETOPT = 522, + SET = 523, + SHOWOPT = 524, + SIGN = 525, + SIMILAR_TO = 526, + NOT_SIMILAR_TO = 527, + INTEGER_CONST = 528, + REAL_CONST = 529, + DATE_CONST = 530, + DATETIME_CONST = 531, + TIME_CONST = 532, + SIN = 533, + SQL_SIZE = 534, + SMALLINT = 535, + SOME = 536, + SPACE = 537, + SQL = 538, + SQL_TRUE = 539, + SQLCA = 540, + SQLCODE = 541, + SQLERROR = 542, + SQLSTATE = 543, + SQLWARNING = 544, + SQRT = 545, + STDEV = 546, + SUBSTRING = 547, + SUM = 548, + SYSDATE = 549, + SYSDATE_FORMAT = 550, + SYSTEM = 551, + TABLE = 552, + TAN = 553, + TEMPORARY = 554, + THEN = 555, + THREE_DIGITS = 556, + TIME = 557, + TIMESTAMP = 558, + TIMEZONE_HOUR = 559, + TIMEZONE_MINUTE = 560, + TINYINT = 561, + TO = 562, + TO_CHAR = 563, + TO_DATE = 564, + TRANSACTION = 565, + TRANSLATE = 566, + TRANSLATION = 567, + TRUNCATE = 568, + GENERAL_TITLE = 569, + TWO_DIGITS = 570, + UCASE = 571, + UNION = 572, + UNIQUE = 573, + SQL_UNKNOWN = 574, + UPDATE = 575, + UPPER = 576, + USAGE = 577, + USER = 578, + IDENTIFIER = 579, + IDENTIFIER_DOT_ASTERISK = 580, + QUERY_PARAMETER = 581, + USING = 582, + VALUE = 583, + VALUES = 584, + VARBINARY = 585, + VARCHAR = 586, + VARYING = 587, + VENDOR = 588, + VIEW = 589, + WEEK = 590, + WHEN = 591, + WHENEVER = 592, + WHERE = 593, + WHERE_CURRENT_OF = 594, + WITH = 595, + WORD_WRAPPED = 596, + WORK = 597, + WRAPPED = 598, + XOR = 599, + YEAR = 600, + YEARS_BETWEEN = 601, + SCAN_ERROR = 602, + __LAST_TOKEN = 603, + ILIKE = 604 + }; +#endif +/* Tokens. */ +#define UMINUS 258 +#define SQL_TYPE 259 +#define SQL_ABS 260 +#define ACOS 261 +#define AMPERSAND 262 +#define SQL_ABSOLUTE 263 +#define ADA 264 +#define ADD 265 +#define ADD_DAYS 266 +#define ADD_HOURS 267 +#define ADD_MINUTES 268 +#define ADD_MONTHS 269 +#define ADD_SECONDS 270 +#define ADD_YEARS 271 +#define ALL 272 +#define ALLOCATE 273 +#define ALTER 274 +#define AND 275 +#define ANY 276 +#define ARE 277 +#define AS 278 +#define ASIN 279 +#define ASC 280 +#define ASCII 281 +#define ASSERTION 282 +#define ATAN 283 +#define ATAN2 284 +#define AUTHORIZATION 285 +#define AUTO_INCREMENT 286 +#define AVG 287 +#define BEFORE 288 +#define SQL_BEGIN 289 +#define BETWEEN 290 +#define BIGINT 291 +#define BINARY 292 +#define BIT 293 +#define BIT_LENGTH 294 +#define BITWISE_SHIFT_LEFT 295 +#define BITWISE_SHIFT_RIGHT 296 +#define BREAK 297 +#define BY 298 +#define CASCADE 299 +#define CASCADED 300 +#define CASE 301 +#define CAST 302 +#define CATALOG 303 +#define CEILING 304 +#define CENTER 305 +#define SQL_CHAR 306 +#define CHAR_LENGTH 307 +#define CHARACTER_STRING_LITERAL 308 +#define CHECK 309 +#define CLOSE 310 +#define COALESCE 311 +#define COBOL 312 +#define COLLATE 313 +#define COLLATION 314 +#define COLUMN 315 +#define COMMIT 316 +#define COMPUTE 317 +#define CONCAT 318 +#define CONCATENATION 319 +#define CONNECT 320 +#define CONNECTION 321 +#define CONSTRAINT 322 +#define CONSTRAINTS 323 +#define CONTINUE 324 +#define CONVERT 325 +#define CORRESPONDING 326 +#define COS 327 +#define COT 328 +#define COUNT 329 +#define CREATE 330 +#define CURDATE 331 +#define CURRENT 332 +#define CURRENT_DATE 333 +#define CURRENT_TIME 334 +#define CURRENT_TIMESTAMP 335 +#define CURTIME 336 +#define CURSOR 337 +#define DATABASE 338 +#define SQL_DATE 339 +#define DATE_FORMAT 340 +#define DATE_REMAINDER 341 +#define DATE_VALUE 342 +#define DAY 343 +#define DAYOFMONTH 344 +#define DAYOFWEEK 345 +#define DAYOFYEAR 346 +#define DAYS_BETWEEN 347 +#define DEALLOCATE 348 +#define DEC 349 +#define DECLARE 350 +#define DEFAULT 351 +#define DEFERRABLE 352 +#define DEFERRED 353 +#define SQL_DELETE 354 +#define DESC 355 +#define DESCRIBE 356 +#define DESCRIPTOR 357 +#define DIAGNOSTICS 358 +#define DICTIONARY 359 +#define DIRECTORY 360 +#define DISCONNECT 361 +#define DISPLACEMENT 362 +#define DISTINCT 363 +#define DOMAIN_TOKEN 364 +#define SQL_DOUBLE 365 +#define DOUBLE_QUOTED_STRING 366 +#define DROP 367 +#define ELSE 368 +#define END 369 +#define END_EXEC 370 +#define EQUAL 371 +#define ESCAPE 372 +#define EXCEPT 373 +#define SQL_EXCEPTION 374 +#define EXEC 375 +#define EXECUTE 376 +#define EXISTS 377 +#define EXP 378 +#define EXPONENT 379 +#define EXTERNAL 380 +#define EXTRACT 381 +#define SQL_FALSE 382 +#define FETCH 383 +#define FIRST 384 +#define SQL_FLOAT 385 +#define FLOOR 386 +#define FN 387 +#define FOR 388 +#define FOREIGN 389 +#define FORTRAN 390 +#define FOUND 391 +#define FOUR_DIGITS 392 +#define FROM 393 +#define FULL 394 +#define GET 395 +#define GLOBAL 396 +#define GO 397 +#define GOTO 398 +#define GRANT 399 +#define GREATER_OR_EQUAL 400 +#define HAVING 401 +#define HOUR 402 +#define HOURS_BETWEEN 403 +#define IDENTITY 404 +#define IFNULL 405 +#define SQL_IGNORE 406 +#define IMMEDIATE 407 +#define SQL_IN 408 +#define INCLUDE 409 +#define INDEX 410 +#define INDICATOR 411 +#define INITIALLY 412 +#define INNER 413 +#define INPUT 414 +#define INSENSITIVE 415 +#define INSERT 416 +#define INTEGER 417 +#define INTERSECT 418 +#define INTERVAL 419 +#define INTO 420 +#define IS 421 +#define ISOLATION 422 +#define JOIN 423 +#define JUSTIFY 424 +#define KEY 425 +#define LANGUAGE 426 +#define LAST 427 +#define LCASE 428 +#define LEFT 429 +#define LENGTH 430 +#define LESS_OR_EQUAL 431 +#define LEVEL 432 +#define LIKE 433 +#define LINE_WIDTH 434 +#define LOCAL 435 +#define LOCATE 436 +#define LOG 437 +#define SQL_LONG 438 +#define LOWER 439 +#define LTRIM 440 +#define LTRIP 441 +#define MATCH 442 +#define SQL_MAX 443 +#define MICROSOFT 444 +#define SQL_MIN 445 +#define MINUS 446 +#define MINUTE 447 +#define MINUTES_BETWEEN 448 +#define MOD 449 +#define MODIFY 450 +#define MODULE 451 +#define MONTH 452 +#define MONTHS_BETWEEN 453 +#define MUMPS 454 +#define NAMES 455 +#define NATIONAL 456 +#define NCHAR 457 +#define NEXT 458 +#define NODUP 459 +#define NONE 460 +#define NOT 461 +#define NOT_EQUAL 462 +#define NOT_EQUAL2 463 +#define NOW 464 +#define SQL_NULL 465 +#define SQL_IS 466 +#define SQL_IS_NULL 467 +#define SQL_IS_NOT_NULL 468 +#define NULLIF 469 +#define NUMERIC 470 +#define OCTET_LENGTH 471 +#define ODBC 472 +#define OF 473 +#define SQL_OFF 474 +#define SQL_ON 475 +#define ONLY 476 +#define OPEN 477 +#define OPTION 478 +#define OR 479 +#define ORDER 480 +#define OUTER 481 +#define OUTPUT 482 +#define OVERLAPS 483 +#define PAGE 484 +#define PARTIAL 485 +#define SQL_PASCAL 486 +#define PERSISTENT 487 +#define CQL_PI 488 +#define PLI 489 +#define POSITION 490 +#define PRECISION 491 +#define PREPARE 492 +#define PRESERVE 493 +#define PRIMARY 494 +#define PRIOR 495 +#define PRIVILEGES 496 +#define PROCEDURE 497 +#define PRODUCT 498 +#define PUBLIC 499 +#define QUARTER 500 +#define QUIT 501 +#define RAND 502 +#define READ_ONLY 503 +#define REAL 504 +#define REFERENCES 505 +#define REPEAT 506 +#define REPLACE 507 +#define RESTRICT 508 +#define REVOKE 509 +#define RIGHT 510 +#define ROLLBACK 511 +#define ROWS 512 +#define RPAD 513 +#define RTRIM 514 +#define SCHEMA 515 +#define SCREEN_WIDTH 516 +#define SCROLL 517 +#define SECOND 518 +#define SECONDS_BETWEEN 519 +#define SELECT 520 +#define SEQUENCE 521 +#define SETOPT 522 +#define SET 523 +#define SHOWOPT 524 +#define SIGN 525 +#define SIMILAR_TO 526 +#define NOT_SIMILAR_TO 527 +#define INTEGER_CONST 528 +#define REAL_CONST 529 +#define DATE_CONST 530 +#define DATETIME_CONST 531 +#define TIME_CONST 532 +#define SIN 533 +#define SQL_SIZE 534 +#define SMALLINT 535 +#define SOME 536 +#define SPACE 537 +#define SQL 538 +#define SQL_TRUE 539 +#define SQLCA 540 +#define SQLCODE 541 +#define SQLERROR 542 +#define SQLSTATE 543 +#define SQLWARNING 544 +#define SQRT 545 +#define STDEV 546 +#define SUBSTRING 547 +#define SUM 548 +#define SYSDATE 549 +#define SYSDATE_FORMAT 550 +#define SYSTEM 551 +#define TABLE 552 +#define TAN 553 +#define TEMPORARY 554 +#define THEN 555 +#define THREE_DIGITS 556 +#define TIME 557 +#define TIMESTAMP 558 +#define TIMEZONE_HOUR 559 +#define TIMEZONE_MINUTE 560 +#define TINYINT 561 +#define TO 562 +#define TO_CHAR 563 +#define TO_DATE 564 +#define TRANSACTION 565 +#define TRANSLATE 566 +#define TRANSLATION 567 +#define TRUNCATE 568 +#define GENERAL_TITLE 569 +#define TWO_DIGITS 570 +#define UCASE 571 +#define UNION 572 +#define UNIQUE 573 +#define SQL_UNKNOWN 574 +#define UPDATE 575 +#define UPPER 576 +#define USAGE 577 +#define USER 578 +#define IDENTIFIER 579 +#define IDENTIFIER_DOT_ASTERISK 580 +#define QUERY_PARAMETER 581 +#define USING 582 +#define VALUE 583 +#define VALUES 584 +#define VARBINARY 585 +#define VARCHAR 586 +#define VARYING 587 +#define VENDOR 588 +#define VIEW 589 +#define WEEK 590 +#define WHEN 591 +#define WHENEVER 592 +#define WHERE 593 +#define WHERE_CURRENT_OF 594 +#define WITH 595 +#define WORD_WRAPPED 596 +#define WORK 597 +#define WRAPPED 598 +#define XOR 599 +#define YEAR 600 +#define YEARS_BETWEEN 601 +#define SCAN_ERROR 602 +#define __LAST_TOKEN 603 +#define ILIKE 604 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +#line 511 "sqlparser.y" +{ + QString* stringValue; + Q_LLONG integerValue; + bool booleanValue; + struct realType realValue; + KexiDB::Field::Type colType; + KexiDB::Field *field; + KexiDB::BaseExpr *expr; + KexiDB::NArgExpr *exprList; + KexiDB::ConstExpr *constExpr; + KexiDB::QuerySchema *querySchema; + SelectOptionsInternal *selectOptions; + OrderByColumnInternal::List *orderByColumns; + QVariant *variantValue; +} +/* Line 1528 of yacc.c. */ +#line 763 "sqlparser.tab.h" + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +extern YYSTYPE yylval; + +#endif diff --git a/kexi/kexidb/parser/sqlparser.y b/kexi/kexidb/parser/sqlparser.y new file mode 100644 index 00000000..5a8357f2 --- /dev/null +++ b/kexi/kexidb/parser/sqlparser.y @@ -0,0 +1,1368 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <[email protected]> + Copyright (C) 2004, 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +%token UMINUS + +%token SQL_TYPE +%token SQL_ABS +%token ACOS +%token AMPERSAND +%token SQL_ABSOLUTE +%token ADA +%token ADD +%token ADD_DAYS +%token ADD_HOURS +%token ADD_MINUTES +%token ADD_MONTHS +%token ADD_SECONDS +%token ADD_YEARS +%token ALL +%token ALLOCATE +%token ALTER +%token AND +%token ANY +%token ARE +%token AS +%token ASIN +%token ASC +%token ASCII +%token ASSERTION +%token ATAN +%token ATAN2 +%token AUTHORIZATION +%token AUTO_INCREMENT +%token AVG +%token BEFORE +%token SQL_BEGIN +%token BETWEEN +%token BIGINT +%token BINARY +%token BIT +%token BIT_LENGTH +%token BITWISE_SHIFT_LEFT +%token BITWISE_SHIFT_RIGHT +%token BREAK +%token BY +%token CASCADE +%token CASCADED +%token CASE +%token CAST +%token CATALOG +%token CEILING +%token CENTER +%token SQL_CHAR +%token CHAR_LENGTH +%token CHARACTER_STRING_LITERAL +%token CHECK +%token CLOSE +%token COALESCE +%token COBOL +%token COLLATE +%token COLLATION +%token COLUMN +%token COMMIT +%token COMPUTE +%token CONCAT +%token CONCATENATION /* || */ +%token CONNECT +%token CONNECTION +%token CONSTRAINT +%token CONSTRAINTS +%token CONTINUE +%token CONVERT +%token CORRESPONDING +%token COS +%token COT +%token COUNT +%token CREATE +%token CURDATE +%token CURRENT +%token CURRENT_DATE +%token CURRENT_TIME +%token CURRENT_TIMESTAMP +%token CURTIME +%token CURSOR +%token DATABASE +%token SQL_DATE +%token DATE_FORMAT +%token DATE_REMAINDER +%token DATE_VALUE +%token DAY +%token DAYOFMONTH +%token DAYOFWEEK +%token DAYOFYEAR +%token DAYS_BETWEEN +%token DEALLOCATE +%token DEC +%token DECLARE +%token DEFAULT +%token DEFERRABLE +%token DEFERRED +%token SQL_DELETE +%token DESC +%token DESCRIBE +%token DESCRIPTOR +%token DIAGNOSTICS +%token DICTIONARY +%token DIRECTORY +%token DISCONNECT +%token DISPLACEMENT +%token DISTINCT +%token DOMAIN_TOKEN +%token SQL_DOUBLE +%token DOUBLE_QUOTED_STRING +%token DROP +%token ELSE +%token END +%token END_EXEC +%token EQUAL +%token ESCAPE +%token EXCEPT +%token SQL_EXCEPTION +%token EXEC +%token EXECUTE +%token EXISTS +%token EXP +%token EXPONENT +%token EXTERNAL +%token EXTRACT +%token SQL_FALSE +%token FETCH +%token FIRST +%token SQL_FLOAT +%token FLOOR +%token FN +%token FOR +%token FOREIGN +%token FORTRAN +%token FOUND +%token FOUR_DIGITS +%token FROM +%token FULL +%token GET +%token GLOBAL +%token GO +%token GOTO +%token GRANT +%token GREATER_OR_EQUAL +//%token GREATER_THAN +//conflict %token GROUP +%token HAVING +%token HOUR +%token HOURS_BETWEEN +%token IDENTITY +%token IFNULL +%token SQL_IGNORE +%token IMMEDIATE +%token SQL_IN +%token INCLUDE +%token INDEX +%token INDICATOR +%token INITIALLY +%token INNER +%token INPUT +%token INSENSITIVE +%token INSERT +%token INTEGER +%token INTERSECT +%token INTERVAL +%token INTO +%token IS +%token ISOLATION +%token JOIN +%token JUSTIFY +%token KEY +%token LANGUAGE +%token LAST +%token LCASE +%token LEFT +%token LENGTH +%token LESS_OR_EQUAL +//%token LESS_THAN +%token LEVEL +%token LIKE +%token LINE_WIDTH +%token LOCAL +%token LOCATE +%token LOG +%token SQL_LONG +%token LOWER +%token LTRIM +%token LTRIP +%token MATCH +%token SQL_MAX +%token MICROSOFT +%token SQL_MIN +%token MINUS +%token MINUTE +%token MINUTES_BETWEEN +%token MOD +%token MODIFY +%token MODULE +%token MONTH +%token MONTHS_BETWEEN +%token MUMPS +%token NAMES +%token NATIONAL +%token NCHAR +%token NEXT +%token NODUP +%token NONE +%token NOT +%token NOT_EQUAL //<> +%token NOT_EQUAL2 //!= +%token NOW +%token SQL_NULL +%token SQL_IS +%token SQL_IS_NULL /*helper */ +%token SQL_IS_NOT_NULL /*helper */ +%token NULLIF +%token NUMERIC +%token OCTET_LENGTH +%token ODBC +%token OF +%token SQL_OFF +%token SQL_ON +%token ONLY +%token OPEN +%token OPTION +%token OR +%token ORDER +%token OUTER +%token OUTPUT +%token OVERLAPS +%token PAGE +%token PARTIAL +%token SQL_PASCAL +%token PERSISTENT +%token CQL_PI +%token PLI +%token POSITION +%token PRECISION +%token PREPARE +%token PRESERVE +%token PRIMARY +%token PRIOR +%token PRIVILEGES +%token PROCEDURE +%token PRODUCT +%token PUBLIC +%token QUARTER +%token QUIT +%token RAND +%token READ_ONLY +%token REAL +%token REFERENCES +%token REPEAT +%token REPLACE +%token RESTRICT +%token REVOKE +%token RIGHT +%token ROLLBACK +%token ROWS +%token RPAD +%token RTRIM +%token SCHEMA +%token SCREEN_WIDTH +%token SCROLL +%token SECOND +%token SECONDS_BETWEEN +%token SELECT +%token SEQUENCE +%token SETOPT +%token SET +%token SHOWOPT +%token SIGN +//%token SIMILAR +%token SIMILAR_TO /* helper */ +%token NOT_SIMILAR_TO /* helper */ +%token INTEGER_CONST +%token REAL_CONST +%token DATE_CONST +%token DATETIME_CONST +%token TIME_CONST +%token SIN +%token SQL_SIZE +%token SMALLINT +%token SOME +%token SPACE +%token SQL +%token SQL_TRUE +%token SQLCA +%token SQLCODE +%token SQLERROR +%token SQLSTATE +%token SQLWARNING +%token SQRT +%token STDEV +%token SUBSTRING +%token SUM +%token SYSDATE +%token SYSDATE_FORMAT +%token SYSTEM +%token TABLE +%token TAN +%token TEMPORARY +%token THEN +%token THREE_DIGITS +%token TIME +%token TIMESTAMP +%token TIMEZONE_HOUR +%token TIMEZONE_MINUTE +%token TINYINT +%token TO +%token TO_CHAR +%token TO_DATE +%token TRANSACTION +%token TRANSLATE +%token TRANSLATION +%token TRUNCATE +%token GENERAL_TITLE +%token TWO_DIGITS +%token UCASE +%token UNION +%token UNIQUE +%token SQL_UNKNOWN +//%token UNSIGNED_INTEGER +%token UPDATE +%token UPPER +%token USAGE +%token USER +%token IDENTIFIER +%token IDENTIFIER_DOT_ASTERISK +%token QUERY_PARAMETER +//%token ERROR_DIGIT_BEFORE_IDENTIFIER +%token USING +%token VALUE +%token VALUES +%token VARBINARY +%token VARCHAR +%token VARYING +%token VENDOR +%token VIEW +%token WEEK +%token WHEN +%token WHENEVER +%token WHERE +%token WHERE_CURRENT_OF +%token WITH +%token WORD_WRAPPED +%token WORK +%token WRAPPED +%token XOR +%token YEAR +%token YEARS_BETWEEN + +%token SCAN_ERROR +%token __LAST_TOKEN /* sentinel */ + +%token '-' '+' +%token '*' +%token '%' +%token '@' +%token ';' +%token ',' +%token '.' +%token '$' +//%token '<' +//%token '>' +%token '(' ')' +%token '?' +%token '\'' +%token '/' + +%type <stringValue> IDENTIFIER +%type <stringValue> IDENTIFIER_DOT_ASTERISK +%type <stringValue> QUERY_PARAMETER +%type <stringValue> CHARACTER_STRING_LITERAL +%type <stringValue> DOUBLE_QUOTED_STRING + +/* +%type <field> ColExpression +%type <field> ColView +*/ +%type <expr> ColExpression +%type <expr> ColWildCard +//%type <expr> ColView +%type <expr> ColItem +%type <exprList> ColViews +%type <expr> aExpr +%type <expr> aExpr2 +%type <expr> aExpr3 +%type <expr> aExpr4 +%type <expr> aExpr5 +%type <expr> aExpr6 +%type <expr> aExpr7 +%type <expr> aExpr8 +%type <expr> aExpr9 +%type <expr> aExpr10 +%type <exprList> aExprList +%type <exprList> aExprList2 +%type <expr> WhereClause +%type <orderByColumns> OrderByClause +%type <booleanValue> OrderByOption +%type <variantValue> OrderByColumnId +%type <selectOptions> SelectOptions +%type <expr> FlatTable +%type <exprList> Tables +%type <exprList> FlatTableList +%type <querySchema> SelectStatement +%type <querySchema> Select +/*todo : list*/ +%type <querySchema> StatementList +/*todo: not onlu select*/ +%type <querySchema> Statement + +%type <colType> SQL_TYPE +%type <integerValue> INTEGER_CONST +%type <realValue> REAL_CONST +/*%type <integerValue> SIGNED_INTEGER */ + +%{ +#ifndef YYDEBUG /* compat. */ +# define YYDEBUG 0 +#endif +#include <stdio.h> +#include <string.h> +#include <string> +#include <iostream> +#include <assert.h> +#include <limits.h> +//TODO OK? +#ifdef Q_WS_WIN +//workaround for bug on msvc +# undef LLONG_MIN +#endif +#ifndef LLONG_MAX +# define LLONG_MAX 0x7fffffffffffffffLL +#endif +#ifndef LLONG_MIN +# define LLONG_MIN 0x8000000000000000LL +#endif +#ifndef LLONG_MAX +# define ULLONG_MAX 0xffffffffffffffffLL +#endif + +#ifdef _WIN32 +# include <malloc.h> +#endif + +#include <qobject.h> +#include <kdebug.h> +#include <klocale.h> +#include <qptrlist.h> +#include <qcstring.h> +#include <qvariant.h> + +#include <connection.h> +#include <queryschema.h> +#include <field.h> +#include <tableschema.h> + +#include "parser.h" +#include "parser_p.h" +#include "sqltypes.h" + +int yylex(); + +// using namespace std; +using namespace KexiDB; + +#define YY_NO_UNPUT +#define YYSTACK_USE_ALLOCA 1 +#define YYMAXDEPTH 255 + + extern "C" + { + int yywrap() + { + return 1; + } + } + +#if 0 + struct yyval + { + QString parserUserName; + int integerValue; + KexiDBField::ColumnType coltype; + } +#endif + +%} + +%union { + QString* stringValue; + Q_LLONG integerValue; + bool booleanValue; + struct realType realValue; + KexiDB::Field::Type colType; + KexiDB::Field *field; + KexiDB::BaseExpr *expr; + KexiDB::NArgExpr *exprList; + KexiDB::ConstExpr *constExpr; + KexiDB::QuerySchema *querySchema; + SelectOptionsInternal *selectOptions; + OrderByColumnInternal::List *orderByColumns; + QVariant *variantValue; +} + +//%left '=' NOT_EQUAL '>' GREATER_OR_EQUAL '<' LESS_OR_EQUAL LIKE '%' NOT +//%left '+' '-' +//%left ASTERISK SLASH + +/* precedence: lowest to highest */ +%left UNION EXCEPT +%left INTERSECT +%left OR +%left AND XOR +%right NOT +//%right '=' +//%nonassoc '<' '>' +//%nonassoc '=' '<' '>' "<=" ">=" "<>" ":=" LIKE ILIKE SIMILAR +//%nonassoc '=' LESS_THAN GREATER_THAN LESS_OR_EQUAL GREATER_OR_EQUAL NOT_EQUAL +%nonassoc '=' '<' '>' +//LESS_THAN GREATER_THAN +%nonassoc LESS_OR_EQUAL GREATER_OR_EQUAL +%nonassoc NOT_EQUAL NOT_EQUAL2 +%nonassoc SQL_IN LIKE ILIKE SIMILAR_TO NOT_SIMILAR_TO +//%nonassoc LIKE ILIKE SIMILAR +//%nonassoc ESCAPE +//%nonassoc OVERLAPS +%nonassoc BETWEEN +//%nonassoc IN_P +//%left POSTFIXOP // dummy for postfix Op rules +//%left Op OPERATOR // multi-character ops and user-defined operators +//%nonassoc NOTNULL +//%nonassoc ISNULL +//%nonassoc IS NULL_P TRUE_P FALSE_P UNKNOWN // sets precedence for IS NULL, etc +%left '+' '-' +%left '*' '/' '%' +%left '^' +%left UMINUS +// Unary Operators +//%left AT ZONE // sets precedence for AT TIME ZONE +//%right UMINUS +%left '[' ']' +%left '(' ')' +//%left TYPECAST +%left '.' + +/* + * These might seem to be low-precedence, but actually they are not part + * of the arithmetic hierarchy at all in their use as JOIN operators. + * We make them high-precedence to support their use as function names. + * They wouldn't be given a precedence at all, were it not that we need + * left-associativity among the JOIN rules themselves. + */ +/*%left JOIN UNIONJOIN CROSS LEFT FULL RIGHT INNER_P NATURAL +*/ +%% + +TopLevelStatement : +StatementList +{ +//todo: multiple statements +//todo: not only "select" statements + parser->setOperation(Parser::OP_Select); + parser->setQuerySchema($1); +} +; + +StatementList: +Statement ';' StatementList +{ +//todo: multiple statements +} +| Statement +| Statement ';' +{ + $$ = $1; +} +; + +/* Statement CreateTableStatement { YYACCEPT; } + | Statement SelectStatement { } +*/ +Statement : +CreateTableStatement +{ +YYACCEPT; +} +| SelectStatement +{ + $$ = $1; +} +; + +CreateTableStatement : +CREATE TABLE IDENTIFIER +{ + parser->setOperation(Parser::OP_CreateTable); + parser->createTable($3->latin1()); + delete $3; +} +'(' ColDefs ')' +; + +ColDefs: +ColDefs ',' ColDef|ColDef +{ +} +; + +ColDef: +IDENTIFIER ColType +{ + KexiDBDbg << "adding field " << *$1 << endl; + field->setName($1->latin1()); + parser->table()->addField(field); + field = 0; + delete $1; +} +| IDENTIFIER ColType ColKeys +{ + KexiDBDbg << "adding field " << *$1 << endl; + field->setName(*$1); + delete $1; + parser->table()->addField(field); + +// if(field->isPrimaryKey()) +// parser->table()->addPrimaryKey(field->name()); + +// delete field; +// field = 0; +} +; + +ColKeys: +ColKeys ColKey|ColKey +{ +} +; + +ColKey: +PRIMARY KEY +{ + field->setPrimaryKey(true); + KexiDBDbg << "primary" << endl; +} +| NOT SQL_NULL +{ + field->setNotNull(true); + KexiDBDbg << "not_null" << endl; +} +| AUTO_INCREMENT +{ + field->setAutoIncrement(true); + KexiDBDbg << "ainc" << endl; +} +; + +ColType: +SQL_TYPE +{ + field = new Field(); + field->setType($1); +} +| SQL_TYPE '(' INTEGER_CONST ')' +{ + KexiDBDbg << "sql + length" << endl; + field = new Field(); + field->setPrecision($3); + field->setType($1); +} +| VARCHAR '(' INTEGER_CONST ')' +{ + field = new Field(); + field->setPrecision($3); + field->setType(Field::Text); +} +| +{ + // SQLITE compatibillity + field = new Field(); + field->setType(Field::InvalidType); +} +; + +SelectStatement: +Select ColViews +{ + KexiDBDbg << "Select ColViews=" << $2->debugString() << endl; + + if (!($$ = buildSelectQuery( $1, $2 ))) + return 0; +} +| Select ColViews Tables +{ + if (!($$ = buildSelectQuery( $1, $2, $3 ))) + return 0; +} +| Select Tables +{ + KexiDBDbg << "Select ColViews Tables" << endl; + if (!($$ = buildSelectQuery( $1, 0, $2 ))) + return 0; +} +| Select ColViews SelectOptions +{ + KexiDBDbg << "Select ColViews Conditions" << endl; + if (!($$ = buildSelectQuery( $1, $2, 0, $3 ))) + return 0; +} +| Select ColViews Tables SelectOptions +{ + KexiDBDbg << "Select ColViews Tables SelectOptions" << endl; + if (!($$ = buildSelectQuery( $1, $2, $3, $4 ))) + return 0; +} +; + +Select: +SELECT +{ + KexiDBDbg << "SELECT" << endl; +// parser->createSelect(); +// parser->setOperation(Parser::OP_Select); + $$ = new QuerySchema(); +} +; + +SelectOptions: /* todo: more options (having, group by, limit...) */ +WhereClause +{ + KexiDBDbg << "WhereClause" << endl; + $$ = new SelectOptionsInternal; + $$->whereExpr = $1; +} +| ORDER BY OrderByClause +{ + KexiDBDbg << "OrderByClause" << endl; + $$ = new SelectOptionsInternal; + $$->orderByColumns = $3; +} +| WhereClause ORDER BY OrderByClause +{ + KexiDBDbg << "WhereClause ORDER BY OrderByClause" << endl; + $$ = new SelectOptionsInternal; + $$->whereExpr = $1; + $$->orderByColumns = $4; +} +| ORDER BY OrderByClause WhereClause +{ + KexiDBDbg << "OrderByClause WhereClause" << endl; + $$ = new SelectOptionsInternal; + $$->whereExpr = $4; + $$->orderByColumns = $3; +} +; + +WhereClause: +WHERE aExpr +{ + $$ = $2; +} +; + +/* todo: support "ORDER BY NULL" as described here http://dev.mysql.com/doc/refman/5.1/en/select.html */ +/* todo: accept expr and position as well */ +OrderByClause: +OrderByColumnId +{ + KexiDBDbg << "ORDER BY IDENTIFIER" << endl; + $$ = new OrderByColumnInternal::List; + OrderByColumnInternal orderByColumn; + orderByColumn.setColumnByNameOrNumber( *$1 ); + $$->append( orderByColumn ); + delete $1; +} +| OrderByColumnId OrderByOption +{ + KexiDBDbg << "ORDER BY IDENTIFIER OrderByOption" << endl; + $$ = new OrderByColumnInternal::List; + OrderByColumnInternal orderByColumn; + orderByColumn.setColumnByNameOrNumber( *$1 ); + orderByColumn.ascending = $2; + $$->append( orderByColumn ); + delete $1; +} +| OrderByColumnId ',' OrderByClause +{ + $$ = $3; + OrderByColumnInternal orderByColumn; + orderByColumn.setColumnByNameOrNumber( *$1 ); + $$->append( orderByColumn ); + delete $1; +} +| OrderByColumnId OrderByOption ',' OrderByClause +{ + $$ = $4; + OrderByColumnInternal orderByColumn; + orderByColumn.setColumnByNameOrNumber( *$1 ); + orderByColumn.ascending = $2; + $$->append( orderByColumn ); + delete $1; +} +; + +OrderByColumnId: +IDENTIFIER +{ + $$ = new QVariant( *$1 ); + KexiDBDbg << "OrderByColumnId: " << *$$ << endl; + delete $1; +} +| IDENTIFIER '.' IDENTIFIER +{ + $$ = new QVariant( *$1 + "." + *$3 ); + KexiDBDbg << "OrderByColumnId: " << *$$ << endl; + delete $1; + delete $3; +} +| INTEGER_CONST +{ + $$ = new QVariant($1); + KexiDBDbg << "OrderByColumnId: " << *$$ << endl; +} + +OrderByOption: +ASC +{ + $$ = true; +} +| DESC +{ + $$ = false; +} +; + +aExpr: +aExpr2 +; + +/* --- binary logical --- */ +aExpr2: +aExpr3 AND aExpr2 +{ +// KexiDBDbg << "AND " << $3.debugString() << endl; + $$ = new BinaryExpr( KexiDBExpr_Logical, $1, AND, $3 ); +} +| aExpr3 OR aExpr2 +{ + $$ = new BinaryExpr( KexiDBExpr_Logical, $1, OR, $3 ); +} +| aExpr3 XOR aExpr2 +{ + $$ = new BinaryExpr( KexiDBExpr_Arithm, $1, XOR, $3 ); +} +| +aExpr3 +; + +/* relational op precedence */ +aExpr3: +aExpr4 '>' %prec GREATER_OR_EQUAL aExpr3 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, '>', $3); +} +| aExpr4 GREATER_OR_EQUAL aExpr3 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, GREATER_OR_EQUAL, $3); +} +| aExpr4 '<' %prec LESS_OR_EQUAL aExpr3 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, '<', $3); +} +| aExpr4 LESS_OR_EQUAL aExpr3 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, LESS_OR_EQUAL, $3); +} +| aExpr4 '=' aExpr3 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, '=', $3); +} +| +aExpr4 +; + +/* relational (equality) op precedence */ +aExpr4: +aExpr5 NOT_EQUAL aExpr4 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, NOT_EQUAL, $3); +} +| +aExpr5 NOT_EQUAL2 aExpr4 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, NOT_EQUAL2, $3); +} +| aExpr5 LIKE aExpr4 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, LIKE, $3); +} +| aExpr5 SQL_IN aExpr4 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, SQL_IN, $3); +} +| aExpr5 SIMILAR_TO aExpr4 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, SIMILAR_TO, $3); +} +| aExpr5 NOT_SIMILAR_TO aExpr4 +{ + $$ = new BinaryExpr(KexiDBExpr_Relational, $1, NOT_SIMILAR_TO, $3); +} +| +aExpr5 +; + +/* --- unary logical right --- */ +aExpr5: +aExpr5 SQL_IS_NULL +{ + $$ = new UnaryExpr( SQL_IS_NULL, $1 ); +} +| aExpr5 SQL_IS_NOT_NULL +{ + $$ = new UnaryExpr( SQL_IS_NOT_NULL, $1 ); +} +| +aExpr6 +; + +/* arithm. lowest precedence */ +aExpr6: +aExpr7 BITWISE_SHIFT_LEFT aExpr6 +{ + $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, BITWISE_SHIFT_LEFT, $3); +} +| aExpr7 BITWISE_SHIFT_RIGHT aExpr6 +{ + $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, BITWISE_SHIFT_RIGHT, $3); +} +| +aExpr7 +; + +/* arithm. lower precedence */ +aExpr7: +aExpr8 '+' aExpr7 +{ + $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '+', $3); + $$->debug(); +} +| aExpr8 '-' %prec UMINUS aExpr7 +{ + $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '-', $3); +} +| aExpr8 '&' aExpr7 +{ + $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '&', $3); +} +| aExpr8 '|' aExpr7 +{ + $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '|', $3); +} +| +aExpr8 +; + +/* arithm. higher precedence */ +aExpr8: +aExpr9 '/' aExpr8 +{ + $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '/', $3); +} +| aExpr9 '*' aExpr8 +{ + $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '*', $3); +} +| aExpr9 '%' aExpr8 +{ + $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '%', $3); +} +| +aExpr9 +; + +/* parenthesis, unary operators, and terminals precedence */ +aExpr9: +/* --- unary logical left --- */ +'-' aExpr9 +{ + $$ = new UnaryExpr( '-', $2 ); +} +| '+' aExpr9 +{ + $$ = new UnaryExpr( '+', $2 ); +} +| '~' aExpr9 +{ + $$ = new UnaryExpr( '~', $2 ); +} +| NOT aExpr9 +{ + $$ = new UnaryExpr( NOT, $2 ); +} +| IDENTIFIER +{ + $$ = new VariableExpr( *$1 ); + +//TODO: simplify this later if that's 'only one field name' expression + KexiDBDbg << " + identifier: " << *$1 << endl; + delete $1; +} +| QUERY_PARAMETER +{ + $$ = new QueryParameterExpr( *$1 ); + KexiDBDbg << " + query parameter: " << $$->debugString() << endl; + delete $1; +} +| IDENTIFIER aExprList +{ + KexiDBDbg << " + function: " << *$1 << "(" << $2->debugString() << ")" << endl; + $$ = new FunctionExpr(*$1, $2); + delete $1; +} +/*TODO: shall we also support db name? */ +| IDENTIFIER '.' IDENTIFIER +{ + $$ = new VariableExpr( *$1 + "." + *$3 ); + KexiDBDbg << " + identifier.identifier: " << *$1 << "." << *$3 << endl; + delete $1; + delete $3; +} +| SQL_NULL +{ + $$ = new ConstExpr( SQL_NULL, QVariant() ); + KexiDBDbg << " + NULL" << endl; +// $$ = new Field(); + //$$->setName(QString::null); +} +| CHARACTER_STRING_LITERAL +{ + $$ = new ConstExpr( CHARACTER_STRING_LITERAL, *$1 ); + KexiDBDbg << " + constant " << $1 << endl; + delete $1; +} +| INTEGER_CONST +{ + QVariant val; + if ($1 <= INT_MAX && $1 >= INT_MIN) + val = (int)$1; + else if ($1 <= UINT_MAX && $1 >= 0) + val = (uint)$1; + else if ($1 <= (Q_LLONG)LLONG_MAX && $1 >= (Q_LLONG)LLONG_MIN) + val = (Q_LLONG)$1; + +// if ($1 < ULLONG_MAX) +// val = (Q_ULLONG)$1; +//TODO ok? + + $$ = new ConstExpr( INTEGER_CONST, val ); + KexiDBDbg << " + int constant: " << val.toString() << endl; +} +| REAL_CONST +{ + $$ = new ConstExpr( REAL_CONST, QPoint( $1.integer, $1.fractional ) ); + KexiDBDbg << " + real constant: " << $1.integer << "." << $1.fractional << endl; +} +| +aExpr10 +; + + +aExpr10: +'(' aExpr ')' +{ + KexiDBDbg << "(expr)" << endl; + $$ = new UnaryExpr('(', $2); +} +; + +aExprList: +'(' aExprList2 ')' +{ +// $$ = new NArgExpr(0, 0); +// $$->add( $1 ); +// $$->add( $3 ); + $$ = $2; +} +; + +aExprList2: +aExpr ',' aExprList2 +{ + $$ = $3; + $$->prepend( $1 ); +} +| aExpr ',' aExpr +{ + $$ = new NArgExpr(0, 0); + $$->add( $1 ); + $$->add( $3 ); +} +; + +Tables: +FROM FlatTableList +{ + $$ = $2; +} +/* +| Tables LEFT JOIN IDENTIFIER SQL_ON ColExpression +{ + KexiDBDbg << "LEFT JOIN: '" << *$4 << "' ON " << $6 << endl; + addTable($4->toQString()); + delete $4; +} +| Tables LEFT OUTER JOIN IDENTIFIER SQL_ON ColExpression +{ + KexiDBDbg << "LEFT OUTER JOIN: '" << $5 << "' ON " << $7 << endl; + addTable($5); +} +| Tables INNER JOIN IDENTIFIER SQL_ON ColExpression +{ + KexiDBDbg << "INNER JOIN: '" << *$4 << "' ON " << $6 << endl; + addTable($4->toQString()); + delete $4; +} +| Tables RIGHT JOIN IDENTIFIER SQL_ON ColExpression +{ + KexiDBDbg << "RIGHT JOIN: '" << *$4 << "' ON " << $6 << endl; + addTable(*$4); + delete $4; +} +| Tables RIGHT OUTER JOIN IDENTIFIER SQL_ON ColExpression +{ + KexiDBDbg << "RIGHT OUTER JOIN: '" << *$5 << "' ON " << $7 << endl; + addTable($5->toQString()); + delete $5; +}*/ +; + +/* +FlatTableList: +aFlatTableList +{ + $$ +} +;*/ + +FlatTableList: +FlatTableList ',' FlatTable +{ + $$ = $1; + $$->add($3); +} +|FlatTable +{ + $$ = new NArgExpr(KexiDBExpr_TableList, IDENTIFIER); //ok? + $$->add($1); +} +; + +FlatTable: +IDENTIFIER +{ + KexiDBDbg << "FROM: '" << *$1 << "'" << endl; + $$ = new VariableExpr(*$1); + + /* +//TODO: this isn't ok for more tables: + Field::ListIterator it = parser->select()->fieldsIterator(); + for(Field *item; (item = it.current()); ++it) + { + if(item->table() == dummy) + { + item->setTable(schema); + } + + if(item->table() && !item->isQueryAsterisk()) + { + Field *f = item->table()->field(item->name()); + if(!f) + { + ParserError err(i18n("Field List Error"), i18n("Unknown column '%1' in table '%2'").arg(item->name()).arg(schema->name()), ctoken, current); + parser->setError(err); + yyerror("fieldlisterror"); + } + } + }*/ + delete $1; +} +| IDENTIFIER IDENTIFIER +{ + //table + alias + $$ = new BinaryExpr( + KexiDBExpr_SpecialBinary, + new VariableExpr(*$1), 0, + new VariableExpr(*$2) + ); + delete $1; + delete $2; +} +| IDENTIFIER AS IDENTIFIER +{ + //table + alias + $$ = new BinaryExpr( + KexiDBExpr_SpecialBinary, + new VariableExpr(*$1), AS, + new VariableExpr(*$3) + ); + delete $1; + delete $3; +} +; + + + +ColViews: +ColViews ',' ColItem +{ + $$ = $1; + $$->add( $3 ); + KexiDBDbg << "ColViews: ColViews , ColItem" << endl; +} +|ColItem +{ + $$ = new NArgExpr(0,0); + $$->add( $1 ); + KexiDBDbg << "ColViews: ColItem" << endl; +} +; + +ColItem: +ColExpression +{ +// $$ = new Field(); +// dummy->addField($$); +// $$->setExpression( $1 ); +// parser->select()->addField($$); + $$ = $1; + KexiDBDbg << " added column expr: '" << $1->debugString() << "'" << endl; +} +| ColWildCard +{ + $$ = $1; + KexiDBDbg << " added column wildcard: '" << $1->debugString() << "'" << endl; +} +| ColExpression AS IDENTIFIER +{ + $$ = new BinaryExpr( + KexiDBExpr_SpecialBinary, $1, AS, + new VariableExpr(*$3) + ); + KexiDBDbg << " added column expr: " << $$->debugString() << endl; + delete $3; +} +| ColExpression IDENTIFIER +{ + $$ = new BinaryExpr( + KexiDBExpr_SpecialBinary, $1, 0, + new VariableExpr(*$2) + ); + KexiDBDbg << " added column expr: " << $$->debugString() << endl; + delete $2; +} +; + +ColExpression: +aExpr +{ + $$ = $1; +} +/* HANDLED BY 'IDENTIFIER aExprList' +| IDENTIFIER '(' ColViews ')' +{ + $$ = new FunctionExpr( $1, $3 ); +}*/ +/* +| SUM '(' ColExpression ')' +{ + FunctionExpr( +// $$ = new AggregationExpr( SUM, ); +//TODO +// $$->setName("SUM(" + $3->name() + ")"); +//wait $$->containsGroupingAggregate(true); +//wait parser->select()->grouped(true); +} +| SQL_MIN '(' ColExpression ')' +{ + $$ = $3; +//TODO +// $$->setName("MIN(" + $3->name() + ")"); +//wait $$->containsGroupingAggregate(true); +//wait parser->select()->grouped(true); +} +| SQL_MAX '(' ColExpression ')' +{ + $$ = $3; +//TODO +// $$->setName("MAX(" + $3->name() + ")"); +//wait $$->containsGroupingAggregate(true); +//wait parser->select()->grouped(true); +} +| AVG '(' ColExpression ')' +{ + $$ = $3; +//TODO +// $$->setName("AVG(" + $3->name() + ")"); +//wait $$->containsGroupingAggregate(true); +//wait parser->select()->grouped(true); +}*/ +//? +| DISTINCT '(' ColExpression ')' +{ + $$ = $3; +//TODO +// $$->setName("DISTINCT(" + $3->name() + ")"); +} +; + +ColWildCard: +'*' +{ + $$ = new VariableExpr("*"); + KexiDBDbg << "all columns" << endl; + +// QueryAsterisk *ast = new QueryAsterisk(parser->select(), dummy); +// parser->select()->addAsterisk(ast); +// requiresTable = true; +} +| IDENTIFIER '.' '*' +{ + QString s( *$1 ); + s += ".*"; + $$ = new VariableExpr(s); + KexiDBDbg << " + all columns from " << s << endl; + delete $1; +} +/*| ERROR_DIGIT_BEFORE_IDENTIFIER +{ + $$ = new VariableExpr($1); + KexiDBDbg << " Invalid identifier! " << $1 << endl; + setError(i18n("Invalid identifier \"%1\"").arg($1)); +}*/ +; + +%% + diff --git a/kexi/kexidb/parser/sqlscanner.cpp b/kexi/kexidb/parser/sqlscanner.cpp new file mode 100644 index 00000000..c3984a39 --- /dev/null +++ b/kexi/kexidb/parser/sqlscanner.cpp @@ -0,0 +1,2051 @@ +#line 2 "sqlscanner.cpp" +/* A lexical scanner generated by flex */ + +/* Scanner skeleton version: + * $Header: /home/daffy/u0/vern/flex/RCS/flex.skl,v 2.91 96/09/10 16:58:48 vern Exp $ + */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 + +#include <stdio.h> +#include <errno.h> + +/* cfront 1.2 defines "c_plusplus" instead of "__cplusplus" */ +#ifdef c_plusplus +#ifndef __cplusplus +#define __cplusplus +#endif +#endif + + +#ifdef __cplusplus + +#include <stdlib.h> +#ifndef _WIN32 +#include <unistd.h> +#endif + +/* Use prototypes in function declarations. */ +#define YY_USE_PROTOS + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_PROTOS +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef __TURBOC__ + #pragma warn -rch + #pragma warn -use +#include <io.h> +#include <stdlib.h> +#define YY_USE_CONST +#define YY_USE_PROTOS +#endif + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + + +#ifdef YY_USE_PROTOS +#define YY_PROTO(proto) proto +#else +#define YY_PROTO(proto) () +#endif + + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yy_start = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yy_start - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#define YY_BUF_SIZE 16384 + +typedef struct yy_buffer_state *YY_BUFFER_STATE; + +extern int yyleng; +extern FILE *yyin, *yyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + +/* The funky do-while in the following #define is used to turn the definition + * int a single C statement (which needs a semi-colon terminator). This + * avoids problems with code like: + * + * if ( condition_holds ) + * yyless( 5 ); + * else + * do_something_else(); + * + * Prior to using the do-while the compiler would get upset at the + * "else" because it interpreted the "if" statement as being all + * done when it reached the ';' after the yyless() call. + */ + +/* Return all but the first 'n' matched characters back to the input stream. */ + +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + *yy_cp = yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yy_c_buf_p = yy_cp = yy_bp + n - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, yytext_ptr ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ +typedef unsigned int yy_size_t; + + +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + }; + +static YY_BUFFER_STATE yy_current_buffer = 0; + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + */ +#define YY_CURRENT_BUFFER yy_current_buffer + + +/* yy_hold_char holds the character lost when yytext is formed. */ +static char yy_hold_char; + +static int yy_n_chars; /* number of characters read into yy_ch_buf */ + + +int yyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 1; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void yyrestart YY_PROTO(( FILE *input_file )); + +void yy_switch_to_buffer YY_PROTO(( YY_BUFFER_STATE new_buffer )); +void yy_load_buffer_state YY_PROTO(( void )); +YY_BUFFER_STATE yy_create_buffer YY_PROTO(( FILE *file, int size )); +void yy_delete_buffer YY_PROTO(( YY_BUFFER_STATE b )); +void yy_init_buffer YY_PROTO(( YY_BUFFER_STATE b, FILE *file )); +void yy_flush_buffer YY_PROTO(( YY_BUFFER_STATE b )); +#define YY_FLUSH_BUFFER yy_flush_buffer( yy_current_buffer ) + +YY_BUFFER_STATE yy_scan_buffer YY_PROTO(( char *base, yy_size_t size )); +YY_BUFFER_STATE yy_scan_string YY_PROTO(( yyconst char *yy_str )); +YY_BUFFER_STATE yy_scan_bytes YY_PROTO(( yyconst char *bytes, int len )); + +static void *yy_flex_alloc YY_PROTO(( yy_size_t )); +static void *yy_flex_realloc YY_PROTO(( void *, yy_size_t )); +static void yy_flex_free YY_PROTO(( void * )); + +#define yy_new_buffer yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! yy_current_buffer ) \ + yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \ + yy_current_buffer->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! yy_current_buffer ) \ + yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \ + yy_current_buffer->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (yy_current_buffer->yy_at_bol) + + +#define yywrap() 1 +#define YY_SKIP_YYWRAP +typedef unsigned char YY_CHAR; +FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0; +typedef int yy_state_type; +extern char *yytext; +#define yytext_ptr yytext + +static yy_state_type yy_get_previous_state YY_PROTO(( void )); +static yy_state_type yy_try_NUL_trans YY_PROTO(( yy_state_type current_state )); +static int yy_get_next_buffer YY_PROTO(( void )); +static void yy_fatal_error YY_PROTO(( yyconst char msg[] )); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yytext_ptr = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yy_c_buf_p = yy_cp; + +#define YY_NUM_RULES 43 +#define YY_END_OF_BUFFER 44 +static yyconst short int yy_accept[148] = + { 0, + 0, 0, 44, 43, 41, 42, 43, 42, 42, 43, + 42, 7, 42, 42, 42, 42, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 42, 41, 2, 0, 38, 9, 0, 8, + 8, 7, 39, 27, 4, 1, 3, 5, 28, 0, + 39, 10, 35, 39, 39, 39, 6, 22, 39, 39, + 39, 39, 39, 24, 25, 39, 39, 39, 39, 39, + 39, 26, 8, 40, 9, 36, 39, 39, 39, 39, + 0, 39, 39, 39, 21, 39, 39, 39, 39, 39, + 39, 39, 29, 39, 37, 12, 39, 0, 14, 15, + + 16, 0, 23, 39, 39, 39, 39, 39, 39, 39, + 39, 0, 0, 0, 34, 30, 39, 39, 32, 33, + 11, 39, 0, 0, 0, 31, 39, 13, 0, 20, + 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, + 18, 19, 0, 0, 0, 17, 0 + } ; + +static yyconst int yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 3, 4, 5, 1, 5, 6, 7, 5, + 5, 5, 5, 5, 5, 8, 5, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 5, 5, 10, + 11, 12, 5, 5, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 15, + 15, 31, 32, 33, 34, 15, 35, 36, 37, 15, + 13, 1, 14, 5, 15, 5, 16, 17, 18, 19, + + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 15, 15, 31, 32, 33, 34, 15, 35, 36, + 37, 15, 1, 38, 1, 5, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static yyconst int yy_meta[39] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, + 1, 1, 3, 3, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1 + } ; + +static yyconst short int yy_base[152] = + { 0, + 0, 0, 254, 255, 251, 241, 247, 255, 244, 242, + 239, 31, 31, 236, 33, 0, 238, 38, 39, 40, + 41, 42, 43, 44, 45, 47, 49, 55, 74, 46, + 60, 56, 207, 242, 255, 239, 255, 255, 235, 232, + 231, 50, 231, 255, 255, 255, 255, 255, 255, 224, + 76, 48, 229, 77, 79, 80, 81, 88, 83, 84, + 91, 85, 92, 228, 93, 94, 95, 96, 98, 100, + 101, 255, 226, 255, 226, 225, 105, 115, 117, 118, + 125, 120, 122, 121, 129, 126, 127, 128, 132, 134, + 135, 136, 224, 131, 223, 222, 138, 127, 221, 220, + + 219, 146, 218, 142, 148, 151, 155, 157, 160, 163, + 164, 192, 188, 190, 205, 204, 158, 171, 203, 202, + 201, 162, 206, 179, 177, 196, 166, 195, 173, 255, + 177, 184, 166, 172, 174, 171, 180, 165, 167, 159, + 255, 255, 187, 183, 158, 255, 255, 216, 219, 58, + 222 + } ; + +static yyconst short int yy_def[152] = + { 0, + 147, 1, 147, 147, 147, 147, 148, 147, 147, 149, + 147, 150, 147, 147, 147, 151, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 147, 147, 147, 148, 147, 147, 149, 147, + 147, 150, 150, 147, 147, 147, 147, 147, 147, 151, + 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, + 150, 147, 147, 147, 150, 150, 150, 150, 150, 150, + 147, 150, 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 147, 150, 150, + + 150, 147, 150, 150, 150, 150, 150, 150, 150, 150, + 150, 147, 147, 147, 150, 150, 150, 150, 150, 150, + 150, 150, 147, 147, 147, 150, 150, 150, 147, 147, + 147, 150, 147, 147, 147, 147, 147, 147, 147, 147, + 147, 147, 147, 147, 147, 147, 0, 147, 147, 147, + 147 + } ; + +static yyconst short int yy_nxt[294] = + { 0, + 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 8, 17, 18, 19, 20, 21, 17, + 22, 17, 17, 23, 24, 17, 25, 17, 26, 27, + 28, 29, 30, 17, 31, 32, 17, 33, 41, 42, + 44, 45, 46, 48, 49, 147, 147, 147, 147, 147, + 147, 147, 147, 147, 147, 147, 147, 41, 42, 43, + 55, 69, 147, 147, 60, 76, 51, 147, 61, 52, + 54, 57, 56, 59, 58, 53, 62, 64, 66, 65, + 63, 147, 70, 147, 147, 71, 147, 147, 147, 81, + 147, 147, 147, 67, 75, 147, 77, 68, 147, 147, + + 147, 147, 147, 147, 83, 147, 82, 147, 147, 79, + 78, 87, 147, 80, 91, 88, 84, 85, 86, 92, + 94, 89, 147, 90, 147, 147, 81, 147, 147, 147, + 102, 93, 95, 147, 147, 147, 147, 97, 147, 147, + 101, 147, 147, 147, 96, 147, 104, 102, 99, 147, + 105, 106, 103, 98, 100, 147, 112, 107, 147, 111, + 113, 108, 147, 110, 147, 147, 109, 147, 117, 147, + 147, 147, 115, 147, 129, 135, 119, 114, 147, 120, + 116, 118, 121, 122, 144, 135, 127, 146, 144, 143, + 126, 147, 128, 142, 141, 140, 132, 139, 137, 136, + + 134, 133, 147, 147, 131, 130, 138, 129, 147, 147, + 147, 147, 147, 125, 124, 145, 36, 36, 36, 39, + 39, 39, 50, 50, 123, 147, 147, 147, 147, 147, + 147, 147, 147, 147, 73, 147, 147, 74, 147, 73, + 40, 37, 37, 34, 72, 147, 47, 40, 37, 38, + 37, 35, 34, 147, 3, 147, 147, 147, 147, 147, + 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, + 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, + 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, + 147, 147, 147 + + } ; + +static yyconst short int yy_chk[294] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, + 13, 13, 13, 15, 15, 18, 19, 20, 21, 22, + 23, 24, 25, 30, 26, 52, 27, 42, 42, 150, + 21, 30, 28, 32, 25, 52, 18, 31, 25, 18, + 20, 23, 22, 24, 23, 19, 26, 27, 28, 27, + 26, 29, 31, 51, 54, 32, 55, 56, 57, 58, + 59, 60, 62, 29, 51, 58, 54, 29, 61, 63, + + 65, 66, 67, 68, 60, 69, 59, 70, 71, 56, + 55, 65, 77, 57, 69, 66, 61, 62, 63, 70, + 77, 67, 78, 68, 79, 80, 81, 82, 84, 83, + 85, 71, 78, 86, 87, 88, 85, 80, 94, 89, + 84, 90, 91, 92, 79, 97, 87, 102, 82, 104, + 88, 89, 86, 81, 83, 105, 98, 90, 106, 97, + 98, 91, 107, 94, 108, 117, 92, 109, 106, 122, + 110, 111, 104, 127, 129, 135, 108, 102, 118, 109, + 105, 107, 110, 111, 144, 132, 118, 145, 143, 140, + 117, 132, 122, 139, 138, 137, 127, 136, 134, 133, + + 131, 129, 128, 126, 125, 124, 135, 123, 121, 120, + 119, 116, 115, 114, 113, 144, 148, 148, 148, 149, + 149, 149, 151, 151, 112, 103, 101, 100, 99, 96, + 95, 93, 76, 75, 73, 64, 53, 50, 43, 41, + 40, 39, 36, 34, 33, 17, 14, 11, 10, 9, + 7, 6, 5, 3, 147, 147, 147, 147, 147, 147, + 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, + 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, + 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, + 147, 147, 147 + + } ; + +static yy_state_type yy_last_accepting_state; +static char *yy_last_accepting_cpos; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +char *yytext; +#line 1 "sqlscanner.l" +#define INITIAL 0 +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <[email protected]> + Copyright (C) 2004-2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#line 22 "sqlscanner.l" +#include <field.h> +#include <expression.h> + +#include "sqlparser.h" +#include "sqltypes.h" +#include <iostream> +#include <kdebug.h> +#include <klocale.h> + +#define YY_NO_UNPUT +#define ECOUNT current += yyleng; ctoken = yytext + +extern void setError(const QString& errDesc); +extern void setError(const QString& errName, const QString& errDesc); + +/* *** Please reflect changes to this file in ../driver_p.cpp *** */ +#define YY_NEVER_INTERACTIVE 1 +/*identifier [a-zA-Z_][a-zA-Z_0-9]* */ +/* quoted_identifier (\"[a-zA-Z_0-9]+\") */ +/* todo: support for real numbers */ +#line 524 "sqlscanner.cpp" + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap YY_PROTO(( void )); +#else +extern int yywrap YY_PROTO(( void )); +#endif +#endif + +#ifndef YY_NO_UNPUT +static void yyunput YY_PROTO(( int c, char *buf_ptr )); +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy YY_PROTO(( char *, yyconst char *, int )); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen YY_PROTO(( yyconst char * )); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput YY_PROTO(( void )); +#else +static int input YY_PROTO(( void )); +#endif +#endif + +#if YY_STACK_USED +static int yy_start_stack_ptr = 0; +static int yy_start_stack_depth = 0; +static int *yy_start_stack = 0; +#ifndef YY_NO_PUSH_STATE +static void yy_push_state YY_PROTO(( int new_state )); +#endif +#ifndef YY_NO_POP_STATE +static void yy_pop_state YY_PROTO(( void )); +#endif +#ifndef YY_NO_TOP_STATE +static int yy_top_state YY_PROTO(( void )); +#endif + +#else +#define YY_NO_PUSH_STATE 1 +#define YY_NO_POP_STATE 1 +#define YY_NO_TOP_STATE 1 +#endif + +#ifdef YY_MALLOC_DECL +YY_MALLOC_DECL +#else +#if __STDC__ +#ifndef __cplusplus +#include <stdlib.h> +#endif +#else +/* Just try to get by without declaring the routines. This will fail + * miserably on non-ANSI systems for which sizeof(size_t) != sizeof(int) + * or sizeof(void*) != sizeof(int). + */ +#endif +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ + +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO (void) fwrite( yytext, yyleng, 1, yyout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( yy_current_buffer->yy_is_interactive ) \ + { \ + int c = '*', n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + } +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL int yylex YY_PROTO(( void )) +#endif + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +YY_DECL + { + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + +#line 58 "sqlscanner.l" + + + +#line 690 "sqlscanner.cpp" + + if ( yy_init ) + { + yy_init = 0; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yy_start ) + yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! yy_current_buffer ) + yy_current_buffer = + yy_create_buffer( yyin, YY_BUF_SIZE ); + + yy_load_buffer_state(); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yy_start; +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + if ( yy_accept[yy_current_state] ) + { + yy_last_accepting_state = yy_current_state; + yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 148 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 255 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = yy_last_accepting_cpos; + yy_current_state = yy_last_accepting_state; + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + + +do_action: /* This label is used only to access EOF actions. */ + + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yy_hold_char; + yy_cp = yy_last_accepting_cpos; + yy_current_state = yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 61 "sqlscanner.l" +{ + ECOUNT; + return NOT_EQUAL; +} + YY_BREAK +case 2: +YY_RULE_SETUP +#line 66 "sqlscanner.l" +{ + ECOUNT; + return NOT_EQUAL2; +} + YY_BREAK +case 3: +YY_RULE_SETUP +#line 71 "sqlscanner.l" +{ + ECOUNT; + return '='; +} + YY_BREAK +case 4: +YY_RULE_SETUP +#line 76 "sqlscanner.l" +{ + ECOUNT; + return LESS_OR_EQUAL; +} + YY_BREAK +case 5: +YY_RULE_SETUP +#line 81 "sqlscanner.l" +{ + ECOUNT; + return GREATER_OR_EQUAL; +} + YY_BREAK +case 6: +YY_RULE_SETUP +#line 86 "sqlscanner.l" +{ + ECOUNT; + return SQL_IN; +} + YY_BREAK +case 7: +YY_RULE_SETUP +#line 91 "sqlscanner.l" +{ +//TODO: what about hex or octal values? + //we're using QString:toLongLong() here because atoll() is not so portable: + ECOUNT; + bool ok; + yylval.integerValue = QString(yytext).toLongLong( &ok ); + if (!ok) { + setError(i18n("Invalid integer number"),i18n("This integer number may be too large.")); + return SCAN_ERROR; + } +// yylval.integerValue = atol(yytext); + return INTEGER_CONST; +} + YY_BREAK +case 8: +YY_RULE_SETUP +#line 105 "sqlscanner.l" +{ + char *p = yytext; + if (yytext[0]=='.') { /* no integer part */ + yylval.realValue.integer = 0; + } + else { + yylval.realValue.integer = atoi(p); + int i=0; + while (p && i < yyleng && *p != '.') { + i++; + p++; + } + if (i==0 || !p || *p!='.') { + yylval.realValue.fractional = 0; + return REAL_CONST; + } + } + /* fractional part */ + p++; + yylval.realValue.fractional = atoi(p); + return REAL_CONST; +} + YY_BREAK +case 9: +YY_RULE_SETUP +#line 128 "sqlscanner.l" +{ + ECOUNT; + return AND; +} + YY_BREAK +case 10: +YY_RULE_SETUP +#line 133 "sqlscanner.l" +{ + ECOUNT; + return AS; +} + YY_BREAK +case 11: +YY_RULE_SETUP +#line 138 "sqlscanner.l" +{ + ECOUNT; + return CREATE; +} + YY_BREAK +case 12: +YY_RULE_SETUP +#line 143 "sqlscanner.l" +{ + ECOUNT; + return FROM; +} + YY_BREAK +case 13: +YY_RULE_SETUP +#line 148 "sqlscanner.l" +{ + ECOUNT; + return SQL_TYPE; +} + YY_BREAK +case 14: +YY_RULE_SETUP +#line 153 "sqlscanner.l" +{ + ECOUNT; + return JOIN; +} + YY_BREAK +case 15: +YY_RULE_SETUP +#line 158 "sqlscanner.l" +{ + ECOUNT; + return LEFT; +} + YY_BREAK +case 16: +YY_RULE_SETUP +#line 163 "sqlscanner.l" +{ + ECOUNT; + return LIKE; +} + YY_BREAK +case 17: +YY_RULE_SETUP +#line 168 "sqlscanner.l" +{ + ECOUNT; + return NOT_SIMILAR_TO; +} + YY_BREAK +case 18: +YY_RULE_SETUP +#line 173 "sqlscanner.l" +{ + ECOUNT; + return SIMILAR_TO; +} + YY_BREAK +case 19: +YY_RULE_SETUP +#line 178 "sqlscanner.l" +{ + ECOUNT; + return SQL_IS_NOT_NULL; +} + YY_BREAK +case 20: +YY_RULE_SETUP +#line 183 "sqlscanner.l" +{ + ECOUNT; + return SQL_IS_NULL; +} + YY_BREAK +case 21: +YY_RULE_SETUP +#line 188 "sqlscanner.l" +{ + ECOUNT; + return NOT; +} + YY_BREAK +case 22: +YY_RULE_SETUP +#line 193 "sqlscanner.l" +{ + ECOUNT; + return SQL_IS; +} + YY_BREAK +case 23: +YY_RULE_SETUP +#line 198 "sqlscanner.l" +{ + ECOUNT; + return SQL_NULL; +} + YY_BREAK +case 24: +YY_RULE_SETUP +#line 203 "sqlscanner.l" +{ + ECOUNT; + return SQL_ON; +} + YY_BREAK +case 25: +YY_RULE_SETUP +#line 208 "sqlscanner.l" +{ + ECOUNT; + return OR; +} + YY_BREAK +case 26: +YY_RULE_SETUP +#line 213 "sqlscanner.l" +{ /* also means OR for numbers (mysql) */ + ECOUNT; + return CONCATENATION; +} + YY_BREAK +case 27: +YY_RULE_SETUP +#line 218 "sqlscanner.l" +{ + ECOUNT; + return BITWISE_SHIFT_LEFT; +} + YY_BREAK +case 28: +YY_RULE_SETUP +#line 223 "sqlscanner.l" +{ + ECOUNT; + return BITWISE_SHIFT_RIGHT; +} + YY_BREAK +case 29: +YY_RULE_SETUP +#line 228 "sqlscanner.l" +{ + ECOUNT; + return XOR; +} + YY_BREAK +case 30: +YY_RULE_SETUP +#line 233 "sqlscanner.l" +{ + ECOUNT; + return RIGHT; +} + YY_BREAK +case 31: +YY_RULE_SETUP +#line 238 "sqlscanner.l" +{ + ECOUNT; + return SELECT; +} + YY_BREAK +case 32: +YY_RULE_SETUP +#line 243 "sqlscanner.l" +{ + ECOUNT; + return TABLE; +} + YY_BREAK +case 33: +YY_RULE_SETUP +#line 248 "sqlscanner.l" +{ + ECOUNT; + return WHERE; +} + YY_BREAK +case 34: +YY_RULE_SETUP +#line 253 "sqlscanner.l" +{ + ECOUNT; + return ORDER; +} + YY_BREAK +case 35: +YY_RULE_SETUP +#line 258 "sqlscanner.l" +{ + ECOUNT; + return BY; +} + YY_BREAK +case 36: +YY_RULE_SETUP +#line 263 "sqlscanner.l" +{ + ECOUNT; + return ASC; +} + YY_BREAK +case 37: +YY_RULE_SETUP +#line 268 "sqlscanner.l" +{ + ECOUNT; + return DESC; +} + YY_BREAK +case 38: +YY_RULE_SETUP +#line 273 "sqlscanner.l" +{ + ECOUNT; + yylval.stringValue = new QString(QString::fromUtf8(yytext+1, yyleng-2)); + return CHARACTER_STRING_LITERAL; + +/* "ZZZ" sentinel for script */ +} + YY_BREAK +case 39: +YY_RULE_SETUP +#line 281 "sqlscanner.l" +{ + KexiDBDbg << "yytext: '" << yytext << "' (" << yyleng << ")" << endl; + ECOUNT; + yylval.stringValue = new QString(QString::fromUtf8(yytext, yyleng)); + if (yytext[0]>='0' && yytext[0]<='9') { + setError(i18n("Invalid identifier"), + i18n("Identifiers should start with a letter or '_' character")); + return SCAN_ERROR; + } + return IDENTIFIER; +} + YY_BREAK +case 40: +YY_RULE_SETUP +#line 293 "sqlscanner.l" +{ + KexiDBDbg << "yytext: '" << yytext << "' (" << yyleng << ")" << endl; + ECOUNT; + yylval.stringValue = new QString(QString::fromUtf8(yytext+1, yyleng-2)); + return QUERY_PARAMETER; +} + YY_BREAK +case 41: +YY_RULE_SETUP +#line 300 "sqlscanner.l" +{ + ECOUNT; +} + YY_BREAK +case 42: +YY_RULE_SETUP +#line 304 "sqlscanner.l" +{ + KexiDBDbg << "char: '" << yytext[0] << "'" << endl; + ECOUNT; + return yytext[0]; +} + YY_BREAK +case 43: +YY_RULE_SETUP +#line 310 "sqlscanner.l" +ECHO; + YY_BREAK +#line 1153 "sqlscanner.cpp" +case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( yy_current_buffer->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between yy_current_buffer and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yy_n_chars = yy_current_buffer->yy_n_chars; + yy_current_buffer->yy_input_file = yyin; + yy_current_buffer->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yy_c_buf_p <= &yy_current_buffer->yy_ch_buf[yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yy_c_buf_p = yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state(); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer() ) + { + case EOB_ACT_END_OF_FILE: + { + yy_did_buffer_switch_on_eof = 0; + + if ( yywrap() ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yy_c_buf_p = yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yy_c_buf_p = + yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state(); + + yy_cp = yy_c_buf_p; + yy_bp = yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yy_c_buf_p = + &yy_current_buffer->yy_ch_buf[yy_n_chars]; + + yy_current_state = yy_get_previous_state(); + + yy_cp = yy_c_buf_p; + yy_bp = yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of yylex */ + + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ + +static int yy_get_next_buffer() + { + register char *dest = yy_current_buffer->yy_ch_buf; + register char *source = yytext_ptr; + register int number_to_move, i; + int ret_val; + + if ( yy_c_buf_p > &yy_current_buffer->yy_ch_buf[yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( yy_current_buffer->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yy_c_buf_p - yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yy_c_buf_p - yytext_ptr) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( yy_current_buffer->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + yy_current_buffer->yy_n_chars = yy_n_chars = 0; + + else + { + int num_to_read = + yy_current_buffer->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ +#ifdef YY_USES_REJECT + YY_FATAL_ERROR( +"input buffer overflow, can't enlarge buffer because scanner uses REJECT" ); +#else + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = yy_current_buffer; + + int yy_c_buf_p_offset = + (int) (yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yy_flex_realloc( (void *) b->yy_ch_buf, + b->yy_buf_size + 2 ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = yy_current_buffer->yy_buf_size - + number_to_move - 1; +#endif + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&yy_current_buffer->yy_ch_buf[number_to_move]), + yy_n_chars, num_to_read ); + + yy_current_buffer->yy_n_chars = yy_n_chars; + } + + if ( yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + yy_current_buffer->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + yy_n_chars += number_to_move; + yy_current_buffer->yy_ch_buf[yy_n_chars] = YY_END_OF_BUFFER_CHAR; + yy_current_buffer->yy_ch_buf[yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yytext_ptr = &yy_current_buffer->yy_ch_buf[0]; + + return ret_val; + } + + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + +static yy_state_type yy_get_previous_state() + { + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = yy_start; + + for ( yy_cp = yytext_ptr + YY_MORE_ADJ; yy_cp < yy_c_buf_p; ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + yy_last_accepting_state = yy_current_state; + yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 148 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + } + + return yy_current_state; + } + + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + +#ifdef YY_USE_PROTOS +static yy_state_type yy_try_NUL_trans( yy_state_type yy_current_state ) +#else +static yy_state_type yy_try_NUL_trans( yy_current_state ) +yy_state_type yy_current_state; +#endif + { + register int yy_is_jam; + register char *yy_cp = yy_c_buf_p; + + register YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + yy_last_accepting_state = yy_current_state; + yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 148 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 147); + + return yy_is_jam ? 0 : yy_current_state; + } + + +#ifndef YY_NO_UNPUT +#ifdef YY_USE_PROTOS +static void yyunput( int c, register char *yy_bp ) +#else +static void yyunput( c, yy_bp ) +int c; +register char *yy_bp; +#endif + { + register char *yy_cp = yy_c_buf_p; + + /* undo effects of setting up yytext */ + *yy_cp = yy_hold_char; + + if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = yy_n_chars + 2; + register char *dest = &yy_current_buffer->yy_ch_buf[ + yy_current_buffer->yy_buf_size + 2]; + register char *source = + &yy_current_buffer->yy_ch_buf[number_to_move]; + + while ( source > yy_current_buffer->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + yy_current_buffer->yy_n_chars = + yy_n_chars = yy_current_buffer->yy_buf_size; + + if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + + yytext_ptr = yy_bp; + yy_hold_char = *yy_cp; + yy_c_buf_p = yy_cp; + } +#endif /* ifndef YY_NO_UNPUT */ + + +#ifdef __cplusplus +static int yyinput() +#else +static int input() +#endif + { + int c; + + *yy_c_buf_p = yy_hold_char; + + if ( *yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yy_c_buf_p < &yy_current_buffer->yy_ch_buf[yy_n_chars] ) + /* This was really a NUL. */ + *yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = yy_c_buf_p - yytext_ptr; + ++yy_c_buf_p; + + switch ( yy_get_next_buffer() ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin ); + + /* fall through */ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap() ) + return EOF; + + if ( ! yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yy_c_buf_p = yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yy_c_buf_p; /* cast for 8-bit char's */ + *yy_c_buf_p = '\0'; /* preserve yytext */ + yy_hold_char = *++yy_c_buf_p; + + + return c; + } + + +#ifdef YY_USE_PROTOS +void yyrestart( FILE *input_file ) +#else +void yyrestart( input_file ) +FILE *input_file; +#endif + { + if ( ! yy_current_buffer ) + yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); + + yy_init_buffer( yy_current_buffer, input_file ); + yy_load_buffer_state(); + } + + +#ifdef YY_USE_PROTOS +void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer ) +#else +void yy_switch_to_buffer( new_buffer ) +YY_BUFFER_STATE new_buffer; +#endif + { + if ( yy_current_buffer == new_buffer ) + return; + + if ( yy_current_buffer ) + { + /* Flush out information for old buffer. */ + *yy_c_buf_p = yy_hold_char; + yy_current_buffer->yy_buf_pos = yy_c_buf_p; + yy_current_buffer->yy_n_chars = yy_n_chars; + } + + yy_current_buffer = new_buffer; + yy_load_buffer_state(); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yy_did_buffer_switch_on_eof = 1; + } + + +#ifdef YY_USE_PROTOS +void yy_load_buffer_state( void ) +#else +void yy_load_buffer_state() +#endif + { + yy_n_chars = yy_current_buffer->yy_n_chars; + yytext_ptr = yy_c_buf_p = yy_current_buffer->yy_buf_pos; + yyin = yy_current_buffer->yy_input_file; + yy_hold_char = *yy_c_buf_p; + } + + +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_create_buffer( FILE *file, int size ) +#else +YY_BUFFER_STATE yy_create_buffer( file, size ) +FILE *file; +int size; +#endif + { + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yy_flex_alloc( b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file ); + + return b; + } + + +#ifdef YY_USE_PROTOS +void yy_delete_buffer( YY_BUFFER_STATE b ) +#else +void yy_delete_buffer( b ) +YY_BUFFER_STATE b; +#endif + { + if ( ! b ) + return; + + if ( b == yy_current_buffer ) + yy_current_buffer = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yy_flex_free( (void *) b->yy_ch_buf ); + + yy_flex_free( (void *) b ); + } + + +#ifndef _WIN32 +#include <unistd.h> +#else +#ifndef YY_ALWAYS_INTERACTIVE +#ifndef YY_NEVER_INTERACTIVE +extern int isatty YY_PROTO(( int )); +#endif +#endif +#endif + +#ifdef YY_USE_PROTOS +void yy_init_buffer( YY_BUFFER_STATE b, FILE *file ) +#else +void yy_init_buffer( b, file ) +YY_BUFFER_STATE b; +FILE *file; +#endif + + + { + yy_flush_buffer( b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + +#if YY_ALWAYS_INTERACTIVE + b->yy_is_interactive = 1; +#else +#if YY_NEVER_INTERACTIVE + b->yy_is_interactive = 0; +#else + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; +#endif +#endif + } + + +#ifdef YY_USE_PROTOS +void yy_flush_buffer( YY_BUFFER_STATE b ) +#else +void yy_flush_buffer( b ) +YY_BUFFER_STATE b; +#endif + + { + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == yy_current_buffer ) + yy_load_buffer_state(); + } + + +#ifndef YY_NO_SCAN_BUFFER +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_scan_buffer( char *base, yy_size_t size ) +#else +YY_BUFFER_STATE yy_scan_buffer( base, size ) +char *base; +yy_size_t size; +#endif + { + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b ); + + return b; + } +#endif + + +#ifndef YY_NO_SCAN_STRING +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_scan_string( yyconst char *yy_str ) +#else +YY_BUFFER_STATE yy_scan_string( yy_str ) +yyconst char *yy_str; +#endif + { + int len; + for ( len = 0; yy_str[len]; ++len ) + ; + + return yy_scan_bytes( yy_str, len ); + } +#endif + + +#ifndef YY_NO_SCAN_BYTES +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_scan_bytes( yyconst char *bytes, int len ) +#else +YY_BUFFER_STATE yy_scan_bytes( bytes, len ) +yyconst char *bytes; +int len; +#endif + { + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = len + 2; + buf = (char *) yy_flex_alloc( n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < len; ++i ) + buf[i] = bytes[i]; + + buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; + } +#endif + + +#ifndef YY_NO_PUSH_STATE +#ifdef YY_USE_PROTOS +static void yy_push_state( int new_state ) +#else +static void yy_push_state( new_state ) +int new_state; +#endif + { + if ( yy_start_stack_ptr >= yy_start_stack_depth ) + { + yy_size_t new_size; + + yy_start_stack_depth += YY_START_STACK_INCR; + new_size = yy_start_stack_depth * sizeof( int ); + + if ( ! yy_start_stack ) + yy_start_stack = (int *) yy_flex_alloc( new_size ); + + else + yy_start_stack = (int *) yy_flex_realloc( + (void *) yy_start_stack, new_size ); + + if ( ! yy_start_stack ) + YY_FATAL_ERROR( + "out of memory expanding start-condition stack" ); + } + + yy_start_stack[yy_start_stack_ptr++] = YY_START; + + BEGIN(new_state); + } +#endif + + +#ifndef YY_NO_POP_STATE +static void yy_pop_state() + { + if ( --yy_start_stack_ptr < 0 ) + YY_FATAL_ERROR( "start-condition stack underflow" ); + + BEGIN(yy_start_stack[yy_start_stack_ptr]); + } +#endif + + +#ifndef YY_NO_TOP_STATE +static int yy_top_state() + { + return yy_start_stack[yy_start_stack_ptr - 1]; + } +#endif + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +#ifdef YY_USE_PROTOS +static void yy_fatal_error( yyconst char msg[] ) +#else +static void yy_fatal_error( msg ) +char msg[]; +#endif + { + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); + } + + + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + yytext[yyleng] = yy_hold_char; \ + yy_c_buf_p = yytext + n; \ + yy_hold_char = *yy_c_buf_p; \ + *yy_c_buf_p = '\0'; \ + yyleng = n; \ + } \ + while ( 0 ) + + +/* Internal utility routines. */ + +#ifndef yytext_ptr +#ifdef YY_USE_PROTOS +static void yy_flex_strncpy( char *s1, yyconst char *s2, int n ) +#else +static void yy_flex_strncpy( s1, s2, n ) +char *s1; +yyconst char *s2; +int n; +#endif + { + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; + } +#endif + +#ifdef YY_NEED_STRLEN +#ifdef YY_USE_PROTOS +static int yy_flex_strlen( yyconst char *s ) +#else +static int yy_flex_strlen( s ) +yyconst char *s; +#endif + { + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; + } +#endif + + +#ifdef YY_USE_PROTOS +static void *yy_flex_alloc( yy_size_t size ) +#else +static void *yy_flex_alloc( size ) +yy_size_t size; +#endif + { + return (void *) malloc( size ); + } + +#ifdef YY_USE_PROTOS +static void *yy_flex_realloc( void *ptr, yy_size_t size ) +#else +static void *yy_flex_realloc( ptr, size ) +void *ptr; +yy_size_t size; +#endif + { + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); + } + +#ifdef YY_USE_PROTOS +static void yy_flex_free( void *ptr ) +#else +static void yy_flex_free( ptr ) +void *ptr; +#endif + { + free( ptr ); + } + +#if YY_MAIN +int main() + { + yylex(); + return 0; + } +#endif +#line 310 "sqlscanner.l" + + +void tokenize(const char *data) +{ + yy_switch_to_buffer(yy_scan_string(data)); + ctoken = ""; + current = 0; +} + diff --git a/kexi/kexidb/parser/sqlscanner.l b/kexi/kexidb/parser/sqlscanner.l new file mode 100644 index 00000000..5f74a0ca --- /dev/null +++ b/kexi/kexidb/parser/sqlscanner.l @@ -0,0 +1,318 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <[email protected]> + Copyright (C) 2004-2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +%{ +#include <field.h> +#include <expression.h> + +#include "sqlparser.h" +#include "sqltypes.h" +#include <iostream> +#include <kdebug.h> +#include <klocale.h> + +#define YY_NO_UNPUT +#define ECOUNT current += yyleng; ctoken = yytext + +extern void setError(const QString& errDesc); +extern void setError(const QString& errName, const QString& errDesc); + +%} + +/* *** Please reflect changes to this file in ../driver_p.cpp *** */ + +%option case-insensitive +%option noyywrap +%option never-interactive + +whitespace [ \t\n] +digit [0-9] +/*identifier [a-zA-Z_][a-zA-Z_0-9]* */ +identifier [a-zA-Z_0-9]+ +/* quoted_identifier (\"[a-zA-Z_0-9]+\") */ +query_parameter \[[^\[\]]+\] + +integer {digit}+ +decimal (({digit}*\.{digit}+)|({digit}+\.{digit}*)) +real ((({digit}*\.{digit}+)|({digit}+\.{digit}*)|({digit}+))([Ee][-+]?{digit}+)) +/* todo: support for real numbers */ + + +%% + + +"<>" { + ECOUNT; + return NOT_EQUAL; +} + +"!=" { + ECOUNT; + return NOT_EQUAL2; +} + +"==" { + ECOUNT; + return '='; +} + +"<=" { + ECOUNT; + return LESS_OR_EQUAL; +} + +">=" { + ECOUNT; + return GREATER_OR_EQUAL; +} + +"IN" { + ECOUNT; + return SQL_IN; +} + +{integer} { +//TODO: what about hex or octal values? + //we're using QString:toLongLong() here because atoll() is not so portable: + ECOUNT; + bool ok; + yylval.integerValue = QString(yytext).toLongLong( &ok ); + if (!ok) { + setError(i18n("Invalid integer number"),i18n("This integer number may be too large.")); + return SCAN_ERROR; + } +// yylval.integerValue = atol(yytext); + return INTEGER_CONST; +} + +{decimal} { + char *p = yytext; + if (yytext[0]=='.') { /* no integer part */ + yylval.realValue.integer = 0; + } + else { + yylval.realValue.integer = atoi(p); + int i=0; + while (p && i < yyleng && *p != '.') { + i++; + p++; + } + if (i==0 || !p || *p!='.') { + yylval.realValue.fractional = 0; + return REAL_CONST; + } + } + /* fractional part */ + p++; + yylval.realValue.fractional = atoi(p); + return REAL_CONST; +} + +("AND"|"&&") { + ECOUNT; + return AND; +} + +"AS" { + ECOUNT; + return AS; +} + +"CREATE" { + ECOUNT; + return CREATE; +} + +"FROM" { + ECOUNT; + return FROM; +} + +"INTEGER" { + ECOUNT; + return SQL_TYPE; +} + +"JOIN" { + ECOUNT; + return JOIN; +} + +"LEFT" { + ECOUNT; + return LEFT; +} + +"LIKE" { + ECOUNT; + return LIKE; +} + +"NOT"{whitespace}+"SIMILAR"{whitespace}+"TO" { + ECOUNT; + return NOT_SIMILAR_TO; +} + +"SIMILAR"{whitespace}+"TO" { + ECOUNT; + return SIMILAR_TO; +} + +"IS"{whitespace}+"NOT"{whitespace}+"NULL" { + ECOUNT; + return SQL_IS_NOT_NULL; +} + +"IS"{whitespace}+"NULL" { + ECOUNT; + return SQL_IS_NULL; +} + +"NOT" { + ECOUNT; + return NOT; +} + +"IS" { + ECOUNT; + return SQL_IS; +} + +"NULL" { + ECOUNT; + return SQL_NULL; +} + +"ON" { + ECOUNT; + return SQL_ON; +} + +"OR" { + ECOUNT; + return OR; +} + +"||" { /* also means OR for numbers (mysql) */ + ECOUNT; + return CONCATENATION; +} + +"<<" { + ECOUNT; + return BITWISE_SHIFT_LEFT; +} + +">>" { + ECOUNT; + return BITWISE_SHIFT_RIGHT; +} + +"XOR" { + ECOUNT; + return XOR; +} + +"RIGHT" { + ECOUNT; + return RIGHT; +} + +"SELECT" { + ECOUNT; + return SELECT; +} + +"TABLE" { + ECOUNT; + return TABLE; +} + +"WHERE" { + ECOUNT; + return WHERE; +} + +"ORDER" { + ECOUNT; + return ORDER; +} + +"BY" { + ECOUNT; + return BY; +} + +"ASC" { + ECOUNT; + return ASC; +} + +"DESC" { + ECOUNT; + return DESC; +} + +(['][^']*[']|["][^\"]*["]) { + ECOUNT; + yylval.stringValue = new QString(QString::fromUtf8(yytext+1, yyleng-2)); + return CHARACTER_STRING_LITERAL; + +/* "ZZZ" sentinel for script */ +} + +{identifier} { + KexiDBDbg << "yytext: '" << yytext << "' (" << yyleng << ")" << endl; + ECOUNT; + yylval.stringValue = new QString(QString::fromUtf8(yytext, yyleng)); + if (yytext[0]>='0' && yytext[0]<='9') { + setError(i18n("Invalid identifier"), + i18n("Identifiers should start with a letter or '_' character")); + return SCAN_ERROR; + } + return IDENTIFIER; +} + +{query_parameter} { + KexiDBDbg << "yytext: '" << yytext << "' (" << yyleng << ")" << endl; + ECOUNT; + yylval.stringValue = new QString(QString::fromUtf8(yytext+1, yyleng-2)); + return QUERY_PARAMETER; +} + +{whitespace}+ { + ECOUNT; +} + +[\~\!\@\#\^\&\|\`\?,()\[\]\.;\:\+\-\*\/\%\^\<\>\=] { + KexiDBDbg << "char: '" << yytext[0] << "'" << endl; + ECOUNT; + return yytext[0]; +} + +%% + +void tokenize(const char *data) +{ + yy_switch_to_buffer(yy_scan_string(data)); + ctoken = ""; + current = 0; +} + diff --git a/kexi/kexidb/parser/sqltypes.h b/kexi/kexidb/parser/sqltypes.h new file mode 100644 index 00000000..c0879a3c --- /dev/null +++ b/kexi/kexidb/parser/sqltypes.h @@ -0,0 +1,80 @@ +/* This file is part of the KDE project + Copyright (C) 2003, 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef SQLTYPES_H +#define SQLTYPES_H + +#include <qvariant.h> + +extern int current; +extern QString ctoken; + +struct dateType +{ + int year; + int month; + int day; +}; + +struct realType +{ + int integer; + int fractional; +}; + +//! @internal +struct OrderByColumnInternal +{ + typedef QValueList<OrderByColumnInternal> List; + typedef QValueListConstIterator<OrderByColumnInternal> ListConstIterator; + OrderByColumnInternal() + : columnNumber(-1) + , ascending(true) + { + } + + void setColumnByNameOrNumber(const QVariant& nameOrNumber) { + if (nameOrNumber.type()==QVariant::String) { + aliasOrName = nameOrNumber.toString(); + columnNumber = -1; + } + else { + columnNumber = nameOrNumber.toInt(); + aliasOrName = QString::null; + } + } + + QString aliasOrName; //!< Can include a "tablename." prefix + int columnNumber; //!< Optional, used instead of aliasOrName to refer to column + //!< by its number rather than name. + bool ascending : 1; +}; + +//! @internal +struct SelectOptionsInternal +{ + SelectOptionsInternal() : whereExpr(0), orderByColumns(0) {} + ~SelectOptionsInternal() { + delete orderByColumns; // delete because this is internal temp. structure + } + KexiDB::BaseExpr* whereExpr; + OrderByColumnInternal::List* orderByColumns; +}; + +#endif diff --git a/kexi/kexidb/parser/tokens.cpp b/kexi/kexidb/parser/tokens.cpp new file mode 100644 index 00000000..9d196c52 --- /dev/null +++ b/kexi/kexidb/parser/tokens.cpp @@ -0,0 +1,25 @@ +/* WARNING! All changes made in this file will be lost! Run 'make parser' instead. */ +INS("AND"); +INS("AS"); +INS("ASC"); +INS("BY"); +INS("CREATE"); +INS("DESC"); +INS("FROM"); +INS("IN"); +INS("INTEGER"); +INS("IS"); +INS("JOIN"); +INS("LEFT"); +INS("LIKE"); +INS("NOT"); +INS("NULL"); +INS("ON"); +INS("OR"); +INS("ORDER"); +INS("RIGHT"); +INS("SELECT"); +INS("SIMILAR"); +INS("TABLE"); +INS("WHERE"); +INS("XOR"); diff --git a/kexi/kexidb/preparedstatement.cpp b/kexi/kexidb/preparedstatement.cpp new file mode 100644 index 00000000..24947dff --- /dev/null +++ b/kexi/kexidb/preparedstatement.cpp @@ -0,0 +1,136 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "preparedstatement.h" + +#include <kexidb/connection.h> +#include <kexidb/connection_p.h> +#include <kdebug.h> + +using namespace KexiDB; + +PreparedStatement::PreparedStatement(StatementType type, ConnectionInternal& conn, + FieldList& fields, const QStringList& where) + : KShared() + , m_type(type) + , m_fields(&fields) + , m_where(where.isEmpty() ? new QStringList(where) : 0) + , m_whereFields(0) +{ + Q_UNUSED(conn); +} + +PreparedStatement::~PreparedStatement() +{ + delete m_where; + delete m_whereFields; +} + +QCString PreparedStatement::generateStatementString() +{ + QCString s(1024); + if (m_type == SelectStatement) { +//! @todo only tables and trivial queries supported for select... + s = "SELECT "; + bool first = true; +// for (uint i=0; i<m_fields->fieldCount(); i++) { + for (Field::ListIterator it(m_fields->fieldsIterator()); it.current(); ++it) { + if (first) + first = false; + else + s.append(", "); + s.append(it.current()->name().latin1()); + } + first = true; + s.append(" WHERE "); +// for (uint i=0; i<m_fields->fieldCount(); i++) { + + m_whereFields = new Field::List(); + for (QStringList::ConstIterator it=m_where->constBegin(); it!=m_where->constEnd(); ++it) { +// for (Field::ListIterator it(m_fields->fieldsIterator()); it.current(); ++it) { + if (first) + first = false; + else + s.append(" AND "); + Field *f = m_fields->field(*it); + if (!f) { + KexiDBWarn << "PreparedStatement::generateStatementString(): no '" + << *it << "' field found" << endl; + continue; + } + m_whereFields->append(f); + s.append((*it).latin1()); + s.append("=?"); + } + } + else if (m_type == InsertStatement /*&& dynamic_cast<TableSchema*>(m_fields)*/) { +//! @todo only tables supported for insert; what about views? + + TableSchema *table = m_fields->fieldCount()>0 ? m_fields->field(0)->table() : 0; + if (!table) + return ""; //err + + QCString namesList; + bool first = true; + const bool allTableFieldsUsed = dynamic_cast<TableSchema*>(m_fields); //we are using a selection of fields only + Field::ListIterator it = m_fields->fieldsIterator(); + for (uint i=0; i<m_fields->fieldCount(); i++, ++it) { + if (first) { + s.append( "?" ); + if (!allTableFieldsUsed) + namesList = it.current()->name().latin1(); + first = false; + } else { + s.append( ",?" ); + if (!allTableFieldsUsed) + namesList.append(QCString(", ")+it.current()->name().latin1()); + } + } + s.append(")"); + s.prepend(QCString("INSERT INTO ") + table->name().latin1() + + (allTableFieldsUsed ? QCString() : (" (" + namesList + ")")) + + " VALUES ("); + } + return s; +} + +PreparedStatement& PreparedStatement::operator<< ( const QVariant& value ) +{ + m_args.append(value); + return *this; +} + +/*bool PreparedStatement::insert() +{ + const bool res = m_conn->drv_prepareStatement(this); + const bool res = m_conn->drv_insertRecord(this); + clearArguments(); + return res; +} + +bool PreparedStatement::select() +{ + const bool res = m_conn->drv_bindArgumentForPreparedStatement(this, m_args.count()-1); +}*/ + +void PreparedStatement::clearArguments() +{ + m_args.clear(); +} + diff --git a/kexi/kexidb/preparedstatement.h b/kexi/kexidb/preparedstatement.h new file mode 100644 index 00000000..368140e5 --- /dev/null +++ b/kexi/kexidb/preparedstatement.h @@ -0,0 +1,117 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_PREPAREDSTATEMENT_H +#define KEXIDB_PREPAREDSTATEMENT_H + +#include <qvariant.h> +#include <ksharedptr.h> + +#include "field.h" + +namespace KexiDB { + +class ConnectionInternal; +class TableSchema; +class FieldList; + +/*! @short Prepared database command for optimizing sequences of multiple database actions + + Currently INSERT and SELECT statements are supported. + For example, wher using PreparedStatement for INSERTs, + you can gain about 30% speedup compared to using multiple + connection.insertRecord(*tabelSchema, dbRowBuffer). + + To use PreparedStatement, create is using KexiDB::Connection:prepareStatement(), + providing table schema; set up arguments using operator << ( const QVariant& value ); + and call execute() when ready. PreparedStatement objects are accessed + using KDE shared pointers, i.e KexiDB::PreparedStatement::Ptr, so you do not need + to remember about destroying them. However, when underlying Connection object + is destroyed, PreparedStatement should not be used. + + Let's assume tableSchema contains two columns NUMBER integer and TEXT text. + Following code inserts 10000 records with random numbers and text strings + obtained elsewhere using getText(i). + \code + bool insertMultiple(KexiDB::Connection &conn, KexiDB::TableSchema& tableSchema) + { + KexiDB::PreparedStatement::Ptr prepared = conn.prepareStatement( + KexiDB::PreparedStatement::InsertStatement, tableSchema); + for (i=0; i<10000; i++) { + prepared << rand() << getText(i); + if (!prepared.execute()) + return false; + prepared.clearArguments(); + } + return true; + } + \endcode + + If you do not call clearArguments() after every insert, you can insert + the same value multiple times using execute() what increases efficiency even more. + + Another use case is inserting large objects (BLOBs or CLOBs). + Depending on database backend, you can avoid escaping BLOBs. + See KexiFormView::storeData() for example use. +*/ +class KEXI_DB_EXPORT PreparedStatement : public KShared +{ + public: + typedef KSharedPtr<PreparedStatement> Ptr; + + //! Defines type of the prepared statement. + enum StatementType { + SelectStatement, //!< SELECT statement will be prepared end executed + InsertStatement //!< INSERT statement will be prepared end executed + }; + + //! Creates Prepared statement. In your code use KexiDB::Connection:prepareStatement() instead. + PreparedStatement(StatementType type, ConnectionInternal& conn, FieldList& fields, + const QStringList& where = QStringList()); + + virtual ~PreparedStatement(); + + //! Appends argument \a value to the statement. + PreparedStatement& operator<< ( const QVariant& value ); + + //! Clears arguments of the prepared statement. Usually used after execute() + void clearArguments(); + + /*! Executes the prepared statement. In most cases you will need to clear + arguments after executing, using clearArguments(). + A number arguments set up for the statement must be the same as a number of fields + defined in the underlying database table. + \return false on failure. Detailed error status can be obtained + from KexiDB::Connection object used to create this statement. */ + virtual bool execute() = 0; + + protected: +//! @todo is this portable across backends? + QCString generateStatementString(); + + StatementType m_type; + FieldList *m_fields; + QValueList<QVariant> m_args; + QStringList* m_where; + Field::List* m_whereFields; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/queryschema.cpp b/kexi/kexidb/queryschema.cpp new file mode 100644 index 00000000..4da4d2b8 --- /dev/null +++ b/kexi/kexidb/queryschema.cpp @@ -0,0 +1,1859 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2007 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidb/queryschema.h" +#include "kexidb/driver.h" +#include "kexidb/connection.h" +#include "kexidb/expression.h" +#include "kexidb/parser/sqlparser.h" +#include "utils.h" +#include "lookupfieldschema.h" + +#include <assert.h> + +#include <qvaluelist.h> +#include <qasciidict.h> +#include <qptrdict.h> +#include <qintdict.h> +#include <qbitarray.h> + +#include <kdebug.h> +#include <klocale.h> + +using namespace KexiDB; + +QueryColumnInfo::QueryColumnInfo(Field *f, const QCString& _alias, bool _visible, + QueryColumnInfo *foreignColumn) + : field(f), alias(_alias), visible(_visible), m_indexForVisibleLookupValue(-1) + , m_foreignColumn(foreignColumn) +{ +} + +QueryColumnInfo::~QueryColumnInfo() +{ +} + +QString QueryColumnInfo::debugString() const +{ + return field->name() + + ( alias.isEmpty() ? QString::null + : (QString::fromLatin1(" AS ") + QString(alias)) ); +} + +//======================================= +namespace KexiDB { +//! @internal +class QuerySchemaPrivate +{ + public: + QuerySchemaPrivate(QuerySchema* q, QuerySchemaPrivate* copy = 0) + : query(q) + , masterTable(0) + , fakeRowIDField(0) + , fakeRowIDCol(0) + , maxIndexWithAlias(-1) + , visibility(64) + , fieldsExpanded(0) + , internalFields(0) + , fieldsExpandedWithInternalAndRowID(0) + , fieldsExpandedWithInternal(0) + , autoincFields(0) + , columnsOrder(0) + , columnsOrderWithoutAsterisks(0) + , columnsOrderExpanded(0) + , pkeyFieldsOrder(0) + , pkeyFieldsCount(0) + , tablesBoundToColumns(64, -1) + , tablePositionsForAliases(67, false) + , columnPositionsForAliases(67, false) + , whereExpr(0) + , ownedVisibleColumns(0) + , regenerateExprAliases(false) + { + columnAliases.setAutoDelete(true); + tableAliases.setAutoDelete(true); + asterisks.setAutoDelete(true); + relations.setAutoDelete(true); + tablePositionsForAliases.setAutoDelete(true); + columnPositionsForAliases.setAutoDelete(true); + visibility.fill(false); + if (copy) { + // deep copy + *this = *copy; + if (copy->fieldsExpanded) + fieldsExpanded = new QueryColumnInfo::Vector(*copy->fieldsExpanded); + if (copy->internalFields) + internalFields = new QueryColumnInfo::Vector(*copy->internalFields); + if (copy->fieldsExpandedWithInternalAndRowID) + fieldsExpandedWithInternalAndRowID = new QueryColumnInfo::Vector( + *copy->fieldsExpandedWithInternalAndRowID); + if (copy->fieldsExpandedWithInternal) + fieldsExpandedWithInternal = new QueryColumnInfo::Vector( + *copy->fieldsExpandedWithInternal); + if (copy->autoincFields) + autoincFields = new QueryColumnInfo::List(*copy->autoincFields); + if (copy->columnsOrder) + columnsOrder = new QMap<QueryColumnInfo*,int>(*copy->columnsOrder); + if (copy->columnsOrderWithoutAsterisks) + columnsOrderWithoutAsterisks = new QMap<QueryColumnInfo*,int>( + *copy->columnsOrderWithoutAsterisks); + if (copy->columnsOrderExpanded) + columnsOrderExpanded = new QMap<QueryColumnInfo*,int>(*copy->columnsOrderExpanded); + if (copy->pkeyFieldsOrder) + pkeyFieldsOrder = new QValueVector<int>(*copy->pkeyFieldsOrder); + if (copy->whereExpr) + whereExpr = copy->whereExpr->copy(); + if (copy->fakeRowIDCol) + fakeRowIDCol = new QueryColumnInfo(*copy->fakeRowIDCol); + if (copy->fakeRowIDField) + fakeRowIDField = new Field(*copy->fakeRowIDField); + if (copy->ownedVisibleColumns) + ownedVisibleColumns = new Field::List(*copy->ownedVisibleColumns); + } + } + ~QuerySchemaPrivate() + { + delete fieldsExpanded; + delete internalFields; + delete fieldsExpandedWithInternalAndRowID; + delete fieldsExpandedWithInternal; + delete autoincFields; + delete columnsOrder; + delete columnsOrderWithoutAsterisks; + delete columnsOrderExpanded; + delete pkeyFieldsOrder; + delete whereExpr; + delete fakeRowIDCol; + delete fakeRowIDField; + delete ownedVisibleColumns; + } + + void clear() + { + columnAliases.clear(); + tableAliases.clear(); + asterisks.clear(); + relations.clear(); + masterTable = 0; + tables.clear(); + clearCachedData(); + delete pkeyFieldsOrder; + pkeyFieldsOrder=0; + visibility.fill(false); + tablesBoundToColumns = QValueVector<int>(64,-1); + tablePositionsForAliases.clear(); + columnPositionsForAliases.clear(); + } + + void clearCachedData() + { + orderByColumnList.clear(); + if (fieldsExpanded) { + delete fieldsExpanded; + fieldsExpanded = 0; + delete internalFields; + internalFields = 0; + delete columnsOrder; + columnsOrder = 0; + delete columnsOrderWithoutAsterisks; + columnsOrderWithoutAsterisks = 0; + delete columnsOrderExpanded; + columnsOrderExpanded = 0; + delete autoincFields; + autoincFields = 0; + autoIncrementSQLFieldsList = QString::null; + columnInfosByNameExpanded.clear(); + columnInfosByName.clear(); + delete ownedVisibleColumns; + ownedVisibleColumns = 0; + } + } + + void setColumnAliasInternal(uint position, const QCString& alias) + { + columnAliases.replace(position, new QCString(alias)); + columnPositionsForAliases.replace(alias, new int(position)); + maxIndexWithAlias = QMAX( maxIndexWithAlias, (int)position ); + } + + void setColumnAlias(uint position, const QCString& alias) + { + QCString *oldAlias = columnAliases.take(position); + if (oldAlias) { + tablePositionsForAliases.remove(*oldAlias); + delete oldAlias; + } + if (alias.isEmpty()) { + maxIndexWithAlias = -1; + } + else { + setColumnAliasInternal(position, alias); + } + } + + bool hasColumnAliases() + { + tryRegenerateExprAliases(); + return !columnAliases.isEmpty(); + } + + QCString* columnAlias(uint position) + { + tryRegenerateExprAliases(); + return columnAliases[position]; + } + + QuerySchema *query; + + /*! Master table of the query. (may be NULL) + Any data modifications can be performed if we know master table. + If null, query's records cannot be modified. */ + TableSchema *masterTable; + + /*! List of tables used in this query */ + TableSchema::List tables; + + Field *fakeRowIDField; //! used to mark a place for ROWID + QueryColumnInfo *fakeRowIDCol; //! used to mark a place for ROWID + + protected: + void tryRegenerateExprAliases() + { + if (!regenerateExprAliases) + return; + //regenerate missing aliases for experessions + Field *f; + uint p=0; + uint colNum=0; //used to generate a name + QCString columnAlias; + for (Field::ListIterator it(query->fieldsIterator()); (f = it.current()); ++it, p++) { + if (f->isExpression() && !columnAliases[p]) { + //missing + for (;;) { //find 1st unused + colNum++; + columnAlias = (i18n("short for 'expression' word (only latin letters, please)", "expr") + + QString::number(colNum)).latin1(); + if (!tablePositionsForAliases[columnAlias]) + break; + } + setColumnAliasInternal(p, columnAlias); + } + } + regenerateExprAliases = false; + } + + /*! Used to mapping columns to its aliases for this query */ + QIntDict<QCString> columnAliases; + + public: + /*! Used to mapping tables to its aliases for this query */ + QIntDict<QCString> tableAliases; + + /*! Helper used with aliases */ + int maxIndexWithAlias; + + /*! Helper used with tableAliases */ + int maxIndexWithTableAlias; + + /*! Used to store visibility flag for every field */ + QBitArray visibility; + + /*! List of asterisks defined for this query */ + Field::List asterisks; + + /*! Temporary field vector for using in fieldsExpanded() */ +// Field::Vector *fieldsExpanded; + QueryColumnInfo::Vector *fieldsExpanded; + + /*! Temporary field vector containing internal fields used for lookup columns. */ + QueryColumnInfo::Vector *internalFields; + + /*! Temporary, used to cache sum of expanded fields and internal fields (+rowid) used for lookup columns. + Contains not auto-deleted items.*/ + QueryColumnInfo::Vector *fieldsExpandedWithInternalAndRowID; + + /*! Temporary, used to cache sum of expanded fields and internal fields used for lookup columns. + Contains not auto-deleted items.*/ + QueryColumnInfo::Vector *fieldsExpandedWithInternal; + + /*! A list of fields for ORDER BY section. @see QuerySchema::orderByColumnList(). */ + OrderByColumnList orderByColumnList; + + /*! A cache for autoIncrementFields(). */ + QueryColumnInfo::List *autoincFields; + + /*! A cache for autoIncrementSQLFieldsList(). */ + QString autoIncrementSQLFieldsList; + QGuardedPtr<Driver> lastUsedDriverForAutoIncrementSQLFieldsList; + + /*! A map for fast lookup of query columns' order (unexpanded version). */ + QMap<QueryColumnInfo*,int> *columnsOrder; + + /*! A map for fast lookup of query columns' order (unexpanded version without asterisks). */ + QMap<QueryColumnInfo*,int> *columnsOrderWithoutAsterisks; + + /*! A map for fast lookup of query columns' order. + This is exactly opposite information compared to vector returned + by fieldsExpanded() */ + QMap<QueryColumnInfo*,int> *columnsOrderExpanded; + +// QValueList<bool> detailedVisibility; + + /*! order of PKEY fields (e.g. for updateRow() ) */ + QValueVector<int> *pkeyFieldsOrder; + + /*! number of PKEY fields within the query */ + uint pkeyFieldsCount; + + /*! forced (predefined) statement */ + QString statement; + + /*! Relationships defined for this query. */ + Relationship::List relations; + + /*! Information about columns bound to tables. + Used a table is used in FROM section more than once + (using table aliases). + + This list is updated by insertField(uint position, Field *field, + int bindToTable, bool visible), using bindToTable parameter. + + Example: for this statement: + SELECT t1.a, othertable.x, t2.b FROM table t1, table t2, othertable; + tablesBoundToColumns list looks like this: + [ 0, -1, 1 ] + - first column is bound to table 0 "t1" + - second coulmn is not specially bound (othertable.x isn't ambiguous) + - third column is bound to table 1 "t2" + */ + QValueVector<int> tablesBoundToColumns; + + /*! Collects table positions for aliases: used in tablePositionForAlias(). */ + QAsciiDict<int> tablePositionsForAliases; + + /*! Collects column positions for aliases: used in columnPositionForAlias(). */ + QAsciiDict<int> columnPositionsForAliases; + + /*! WHERE expression */ + BaseExpr *whereExpr; + + QDict<QueryColumnInfo> columnInfosByNameExpanded; + + QDict<QueryColumnInfo> columnInfosByName; //!< Same as columnInfosByNameExpanded but asterisks are skipped + + //! field schemas created for multiple joined columns like a||' '||b||' '||c + Field::List *ownedVisibleColumns; + + /*! Set by insertField(): true, if aliases for expression columns should + be generated on next columnAlias() call. */ + bool regenerateExprAliases : 1; +}; +} + +//======================================= + +OrderByColumn::OrderByColumn() + : m_column(0) + , m_pos(-1) + , m_field(0) + , m_ascending(true) +{ +} + +OrderByColumn::OrderByColumn(QueryColumnInfo& column, bool ascending, int pos) + : m_column(&column) + , m_pos(pos) + , m_field(0) + , m_ascending(ascending) +{ +} + +OrderByColumn::OrderByColumn(Field& field, bool ascending) + : m_column(0) + , m_pos(-1) + , m_field(&field) + , m_ascending(ascending) +{ +} + +OrderByColumn::~OrderByColumn() +{ +} + +QString OrderByColumn::debugString() const +{ + QString orderString( m_ascending ? "ascending" : "descending" ); + if (m_column) { + if (m_pos>-1) + return QString("COLUMN_AT_POSITION_%1(%2, %3)") + .arg(m_pos+1).arg(m_column->debugString()).arg(orderString); + else + return QString("COLUMN(%1, %2)").arg(m_column->debugString()).arg(orderString); + } + return m_field ? QString("FIELD(%1, %2)").arg(m_field->debugString()).arg(orderString) + : QString("NONE"); +} + +QString OrderByColumn::toSQLString(bool includeTableName, Driver *drv, int identifierEscaping) const +{ + const QString orderString( m_ascending ? "" : " DESC" ); + QString fieldName, tableName; + if (m_column) { + if (m_pos>-1) + return QString::number(m_pos+1) + orderString; + else { + if (includeTableName && m_column->alias.isEmpty()) { + tableName = m_column->field->table()->name(); + if (drv) + tableName = drv->escapeIdentifier(tableName, identifierEscaping); + tableName += "."; + } + fieldName = m_column->aliasOrName(); + if (drv) + fieldName = drv->escapeIdentifier(fieldName, identifierEscaping); + } + } + else { + if (includeTableName) { + tableName = m_field->table()->name(); + if (drv) + tableName = drv->escapeIdentifier(tableName, identifierEscaping); + tableName += "."; + } + fieldName = m_field ? m_field->name() : "??"/*error*/; + if (drv) + fieldName = drv->escapeIdentifier(fieldName, identifierEscaping); + } + return tableName + fieldName + orderString; +} + +//======================================= + +OrderByColumnList::OrderByColumnList() + : OrderByColumnListBase() +{ +} + +bool OrderByColumnList::appendFields(QuerySchema& querySchema, + const QString& field1, bool ascending1, + const QString& field2, bool ascending2, + const QString& field3, bool ascending3, + const QString& field4, bool ascending4, + const QString& field5, bool ascending5) +{ + uint numAdded = 0; +#define ADD_COL(fieldName, ascending) \ + if (ok && !fieldName.isEmpty()) { \ + if (!appendField( querySchema, fieldName, ascending )) \ + ok = false; \ + else \ + numAdded++; \ + } + bool ok = true; + ADD_COL(field1, ascending1); + ADD_COL(field2, ascending2); + ADD_COL(field3, ascending3); + ADD_COL(field4, ascending4); + ADD_COL(field5, ascending5); +#undef ADD_COL + if (ok) + return true; + for (uint i=0; i<numAdded; i++) + pop_back(); + return false; +} + +OrderByColumnList::~OrderByColumnList() +{ +} + +void OrderByColumnList::appendColumn(QueryColumnInfo& columnInfo, bool ascending) +{ + appendColumn( OrderByColumn(columnInfo, ascending) ); +} + +bool OrderByColumnList::appendColumn(QuerySchema& querySchema, bool ascending, int pos) +{ + QueryColumnInfo::Vector fieldsExpanded( querySchema.fieldsExpanded() ); + QueryColumnInfo* ci = (pos >= (int)fieldsExpanded.size()) ? 0 : fieldsExpanded[pos]; + if (!ci) + return false; + appendColumn( OrderByColumn(*ci, ascending, pos) ); + return true; +} + +void OrderByColumnList::appendField(Field& field, bool ascending) +{ + appendColumn( OrderByColumn(field, ascending) ); +} + +bool OrderByColumnList::appendField(QuerySchema& querySchema, + const QString& fieldName, bool ascending) +{ + QueryColumnInfo *columnInfo = querySchema.columnInfo( fieldName ); + if (columnInfo) { + appendColumn( OrderByColumn(*columnInfo, ascending) ); + return true; + } + Field *field = querySchema.findTableField(fieldName); + if (field) { + appendColumn( OrderByColumn(*field, ascending) ); + return true; + } + KexiDBWarn << "OrderByColumnList::addColumn(QuerySchema& querySchema, " + "const QString& column, bool ascending): no such field \"" << fieldName << "\"" << endl; + return false; +} + +void OrderByColumnList::appendColumn(const OrderByColumn& column) +{ + append( column ); +} + +QString OrderByColumnList::debugString() const +{ + if (isEmpty()) + return "NONE"; + QString dbg; + for (OrderByColumn::ListConstIterator it=constBegin(); it!=constEnd(); ++it) { + if (!dbg.isEmpty()) + dbg += "\n"; + dbg += (*it).debugString(); + } + return dbg; +} + +QString OrderByColumnList::toSQLString(bool includeTableNames, Driver *drv, int identifierEscaping) const +{ + QString string; + for (OrderByColumn::ListConstIterator it=constBegin(); it!=constEnd(); ++it) { + if (!string.isEmpty()) + string += ", "; + string += (*it).toSQLString(includeTableNames, drv, identifierEscaping); + } + return string; +} + +//======================================= + +QuerySchema::QuerySchema() + : FieldList(false)//fields are not owned by QuerySchema object + , SchemaData(KexiDB::QueryObjectType) + , d( new QuerySchemaPrivate(this) ) +{ + init(); +} + +QuerySchema::QuerySchema(TableSchema& tableSchema) + : FieldList(false) + , SchemaData(KexiDB::QueryObjectType) + , d( new QuerySchemaPrivate(this) ) +{ + d->masterTable = &tableSchema; + init(); +/* if (!d->masterTable) { + KexiDBWarn << "QuerySchema(TableSchema*): !d->masterTable" << endl; + m_name = QString::null; + return; + }*/ + addTable(d->masterTable); + //defaults: + //inherit name from a table + m_name = d->masterTable->name(); + //inherit caption from a table + m_caption = d->masterTable->caption(); + +//replaced by explicit field list: //add all fields of the table as asterisk: +//replaced by explicit field list: addField( new QueryAsterisk(this) ); + + // add explicit field list to avoid problems (e.g. with fields added outside of Kexi): + for (Field::ListIterator it( d->masterTable->fieldsIterator() ); it.current(); ++it) { + addField( it.current() ); + } +} + +QuerySchema::QuerySchema(const QuerySchema& querySchema) + : FieldList(querySchema, false /* !deepCopyFields */) + , SchemaData(querySchema) + , d( new QuerySchemaPrivate(this, querySchema.d) ) +{ + //only deep copy query asterisks + for (Field::ListIterator f_it(querySchema.m_fields); f_it.current(); ++f_it) { + Field *f; + if (dynamic_cast<QueryAsterisk*>( f_it.current() )) { + f = f_it.current()->copy(); + if (static_cast<const KexiDB::FieldList *>(f_it.current()->m_parent) == &querySchema) + f->m_parent = this; + } + else + f = f_it.current(); + addField( f ); + } +} + +QuerySchema::~QuerySchema() +{ + delete d; +} + +void QuerySchema::init() +{ + m_type = KexiDB::QueryObjectType; +//m_fields_by_name.setAutoDelete( true ); //because we're using QueryColumnInfoEntry objects +} + +void QuerySchema::clear() +{ + FieldList::clear(); + SchemaData::clear(); + d->clear(); +} + +FieldList& QuerySchema::insertField(uint position, Field *field, bool visible) +{ + return insertField(position, field, -1/*don't bind*/, visible); +} + +/*virtual*/ +FieldList& QuerySchema::insertField(uint position, Field *field) +{ + return insertField( position, field, -1/*don't bind*/, true ); +} + +FieldList& QuerySchema::insertField(uint position, Field *field, + int bindToTable, bool visible) +{ + if (!field) { + KexiDBWarn << "QuerySchema::insertField(): !field" << endl; + return *this; + } + + if (position>m_fields.count()) { + KexiDBWarn << "QuerySchema::insertField(): position (" << position << ") out of range" << endl; + return *this; + } + if (!field->isQueryAsterisk() && !field->isExpression() && !field->table()) { + KexiDBWarn << "QuerySchema::insertField(): WARNING: field '"<<field->name() + <<"' must contain table information!" <<endl; + return *this; + } + if (fieldCount()>=d->visibility.size()) { + d->visibility.resize(d->visibility.size()*2); + d->tablesBoundToColumns.resize(d->tablesBoundToColumns.size()*2); + } + d->clearCachedData(); + FieldList::insertField(position, field); + if (field->isQueryAsterisk()) { + d->asterisks.append(field); + //if this is single-table asterisk, + //add a table to list if doesn't exist there: + if (field->table() && (d->tables.findRef(field->table())==-1)) + d->tables.append(field->table()); + } + else if (field->table()) { + //add a table to list if doesn't exist there: + if (d->tables.findRef(field->table())==-1) + d->tables.append(field->table()); + } +// //visible by default +// setFieldVisible(field, true); +// d->visibility.setBit(fieldCount()-1, visible); + //update visibility + //--move bits to make a place for a new one + for (uint i=fieldCount()-1; i>position; i--) + d->visibility.setBit(i, d->visibility.testBit(i-1)); + d->visibility.setBit(position, visible); + + //bind to table + if (bindToTable < -1 && bindToTable>(int)d->tables.count()) { + KexiDBWarn << "QuerySchema::insertField(): bindToTable (" << bindToTable + << ") out of range" << endl; + bindToTable = -1; + } + //--move items to make a place for a new one + for (uint i=fieldCount()-1; i>position; i--) + d->tablesBoundToColumns[i] = d->tablesBoundToColumns[i-1]; + d->tablesBoundToColumns[ position ] = bindToTable; + + KexiDBDbg << "QuerySchema::insertField(): bound to table (" << bindToTable << "): " <<endl; + if (bindToTable==-1) + KexiDBDbg << " <NOT SPECIFIED>" << endl; + else + KexiDBDbg << " name=" << d->tables.at(bindToTable)->name() + << " alias=" << tableAlias(bindToTable) << endl; + QString s; + for (uint i=0; i<fieldCount();i++) + s+= (QString::number(d->tablesBoundToColumns[i]) + " "); + KexiDBDbg << "tablesBoundToColumns == [" << s << "]" <<endl; + + if (field->isExpression()) + d->regenerateExprAliases = true; + + return *this; +} + +int QuerySchema::tableBoundToColumn(uint columnPosition) const +{ + if (columnPosition > d->tablesBoundToColumns.count()) { + KexiDBWarn << "QuerySchema::tableBoundToColumn(): columnPosition (" << columnPosition + << ") out of range" << endl; + return -1; + } + return d->tablesBoundToColumns[columnPosition]; +} + +KexiDB::FieldList& QuerySchema::addField(KexiDB::Field* field, bool visible) +{ + return insertField(m_fields.count(), field, visible); +} + +KexiDB::FieldList& QuerySchema::addField(KexiDB::Field* field, int bindToTable, + bool visible) +{ + return insertField(m_fields.count(), field, bindToTable, visible); +} + +void QuerySchema::removeField(KexiDB::Field *field) +{ + if (!field) + return; + d->clearCachedData(); + if (field->isQueryAsterisk()) { + d->asterisks.remove(field); //this will destroy this asterisk + } +//TODO: should we also remove table for this field or asterisk? + FieldList::removeField(field); +} + +FieldList& QuerySchema::addExpression(BaseExpr* expr, bool visible) +{ + return addField( new Field(this, expr), visible ); +} + +bool QuerySchema::isColumnVisible(uint position) const +{ + return (position < fieldCount()) ? d->visibility.testBit(position) : false; +} + +void QuerySchema::setColumnVisible(uint position, bool v) +{ + if (position < fieldCount()) + d->visibility.setBit(position, v); +} + +FieldList& QuerySchema::addAsterisk(QueryAsterisk *asterisk, bool visible) +{ + if (!asterisk) + return *this; + //make unique name + asterisk->m_name = (asterisk->table() ? asterisk->table()->name() + ".*" : "*") + + QString::number(asterisks()->count()); + return addField(asterisk, visible); +} + +Connection* QuerySchema::connection() const +{ + TableSchema *mt = masterTable(); + return mt ? mt->connection() : 0; +} + +QString QuerySchema::debugString() +{ + QString dbg; + dbg.reserve(1024); + //fields + TableSchema *mt = masterTable(); + dbg = QString("QUERY ") + schemaDataDebugString() + "\n" + + "-masterTable=" + (mt ? mt->name() :"<NULL>") + + "\n-COLUMNS:\n" + + ((fieldCount()>0) ? FieldList::debugString() : "<NONE>") + "\n" + + "-FIELDS EXPANDED "; + + QString dbg1; + uint fieldsExpandedCount = 0; + if (fieldCount()>0) { + QueryColumnInfo::Vector fe( fieldsExpanded() ); + fieldsExpandedCount = fe.size(); + for ( uint i=0; i < fieldsExpandedCount; i++ ) { + QueryColumnInfo *ci = fe[i]; + if (!dbg1.isEmpty()) + dbg1 += ",\n"; + dbg1 += ci->debugString(); + } + dbg1 += "\n"; + } + else { + dbg1 = "<NONE>\n"; + } + dbg1.prepend( QString("(%1):\n").arg(fieldsExpandedCount) ); + dbg += dbg1; + + //it's safer to delete fieldsExpanded for now + // (debugString() could be called before all fields are added) +//causes a crash d->clearCachedData(); + + //bindings + QString dbg2; + dbg2.reserve(512); + for (uint i = 0; i<fieldCount(); i++) { + int tablePos = tableBoundToColumn(i); + if (tablePos>=0) { + QCString tAlias = tableAlias(tablePos); + if (!tAlias.isEmpty()) { + dbg2 += (QString::fromLatin1(" field \"") + FieldList::field(i)->name() + + "\" uses alias \"" + QString(tAlias) + "\" of table \"" + + d->tables.at(tablePos)->name() + "\"\n"); + } + } + } + if (!dbg2.isEmpty()) { + dbg += "\n-BINDINGS:\n"; + dbg += dbg2; + } + + //tables + TableSchema *table; + QString table_names; + table_names.reserve(512); + for ( table = d->tables.first(); table; table = d->tables.next() ) { + if (!table_names.isEmpty()) + table_names += ", "; + table_names += (QString("'") + table->name() + "'"); + } + if (d->tables.isEmpty()) + table_names = "<NONE>"; + dbg += (QString("-TABLES:\n") + table_names); + QString aliases; + if (!d->hasColumnAliases()) + aliases = "<NONE>\n"; + else { + Field::ListIterator it( m_fields ); + for (int i=0; it.current(); ++it, i++) { + QCString *alias = d->columnAlias(i); + if (alias) + aliases += (QString("field #%1: ").arg(i) + + (it.current()->name().isEmpty() ? "<noname>" : it.current()->name()) + + " -> " + (const char*)*alias + "\n"); + } + } + //aliases + dbg += QString("\n-COLUMN ALIASES:\n" + aliases); + if (d->tableAliases.isEmpty()) + aliases = "<NONE>"; + else { + aliases = ""; + TableSchema::ListIterator t_it(d->tables); + for (int i=0; t_it.current(); ++t_it, i++) { + QCString *alias = d->tableAliases[i]; + if (alias) + aliases += (QString("table #%1: ").arg(i) + + (t_it.current()->name().isEmpty() ? "<noname>" : t_it.current()->name()) + + " -> " + (const char*)*alias + "\n"); + } + } + dbg += QString("-TABLE ALIASES:\n" + aliases); + QString where = d->whereExpr ? d->whereExpr->debugString() : QString::null; + if (!where.isEmpty()) + dbg += (QString("\n-WHERE EXPRESSION:\n") + where); + if (!orderByColumnList().isEmpty()) + dbg += (QString("\n-ORDER BY (%1):\n").arg(orderByColumnList().count()) + + orderByColumnList().debugString()); + return dbg; +} + +TableSchema* QuerySchema::masterTable() const +{ + if (d->masterTable) + return d->masterTable; + if (d->tables.isEmpty()) + return 0; + + //try to find master table if there's only one table (with possible aliasses) + int num = 0; + QString tableNameLower; + for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) { + if (!tableNameLower.isEmpty() && it.current()->name().lower()!=tableNameLower) { + //two or more different tables + return 0; + } + tableNameLower = tableAlias(num); + } + return d->tables.first(); +} + +void QuerySchema::setMasterTable(TableSchema *table) +{ + if (table) + d->masterTable=table; +} + +TableSchema::List* QuerySchema::tables() const +{ + return &d->tables; +} + +void QuerySchema::addTable(TableSchema *table, const QCString& alias) +{ + KexiDBDbg << "QuerySchema::addTable() " << (void *)table + << " alias=" << alias << endl; + if (!table) + return; + + //only append table if: + //-it has alias + //-it has no alias but there is no such table on the list + if (alias.isEmpty() && d->tables.findRef(table)!=-1) { + const QString& tableNameLower = table->name().lower(); + const QString& aliasLower = QString(alias.lower()); + int num = 0; + for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) { + if (it.current()->name().lower()==tableNameLower) { + const QString& tAlias = tableAlias(num); + if (tAlias == aliasLower) { + KexiDBWarn << "QuerySchema::addTable(): table with \"" + << tAlias << "\" alias already added!" << endl; + return; + } + } + } + } + + d->tables.append(table); + + if (!alias.isEmpty()) + setTableAlias(d->tables.count()-1, alias); +} + +void QuerySchema::removeTable(TableSchema *table) +{ + if (!table) + return; + if (d->masterTable == table) + d->masterTable = 0; + d->tables.remove(table); + //todo: remove fields! +} + +TableSchema* QuerySchema::table(const QString& tableName) const +{ +//TODO: maybe use tables_byname? + for (TableSchema::ListIterator it(d->tables); it.current(); ++it) { + if (it.current()->name().lower()==tableName.lower()) + return it.current(); + } + return 0; +} + +bool QuerySchema::contains(TableSchema *table) const +{ + return d->tables.findRef(table)!=-1; +} + +Field* QuerySchema::findTableField(const QString &tableOrTableAndFieldName) const +{ + QString tableName, fieldName; + if (!KexiDB::splitToTableAndFieldParts(tableOrTableAndFieldName, + tableName, fieldName, KexiDB::SetFieldNameIfNoTableName)) { + return 0; + } + if (tableName.isEmpty()) { + for (TableSchema::ListIterator it(d->tables); it.current(); ++it) { + if (it.current()->field(fieldName)) + return it.current()->field(fieldName); + } + return 0; + } + TableSchema *tableSchema = table(tableName); + if (!tableSchema) + return 0; + return tableSchema->field(fieldName); +} + +QCString QuerySchema::columnAlias(uint position) const +{ + QCString *a = d->columnAlias(position); + return a ? *a : QCString(); +} + +bool QuerySchema::hasColumnAlias(uint position) const +{ + return d->columnAlias(position)!=0; +} + +void QuerySchema::setColumnAlias(uint position, const QCString& alias) +{ + if (position >= m_fields.count()) { + KexiDBWarn << "QuerySchema::setColumnAlias(): position (" << position + << ") out of range!" << endl; + return; + } + QCString fixedAlias = alias.stripWhiteSpace(); + Field *f = FieldList::field(position); + if (f->captionOrName().isEmpty() && fixedAlias.isEmpty()) { + KexiDBWarn << "QuerySchema::setColumnAlias(): position (" << position + << ") could not remove alias when no name is specified for expression column!" << endl; + return; + } + d->setColumnAlias(position, fixedAlias); +} + +QCString QuerySchema::tableAlias(uint position) const +{ + QCString *a = d->tableAliases[position]; + return a ? *a : QCString(); +} + +int QuerySchema::tablePositionForAlias(const QCString& name) const +{ + int *num = d->tablePositionsForAliases[name]; + if (!num) + return -1; + return *num; +} + +int QuerySchema::tablePosition(const QString& tableName) const +{ + int num = 0; + for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) { + if (it.current()->name().lower()==tableName.lower()) + return num; + } + return -1; +} + +QValueList<int> QuerySchema::tablePositions(const QString& tableName) const +{ + int num = 0; + QValueList<int> result; + const QString& tableNameLower = tableName.lower(); + for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) { + if (it.current()->name().lower()==tableNameLower) { + result += num; + } + } + return result; +} + +bool QuerySchema::hasTableAlias(uint position) const +{ + return d->tableAliases[position]!=0; +} + +int QuerySchema::columnPositionForAlias(const QCString& name) const +{ + int *num = d->columnPositionsForAliases[name]; + if (!num) + return -1; + return *num; +} + +void QuerySchema::setTableAlias(uint position, const QCString& alias) +{ + if (position >= d->tables.count()) { + KexiDBWarn << "QuerySchema::setTableAlias(): position (" << position + << ") out of range!" << endl; + return; + } + QCString fixedAlias = alias.stripWhiteSpace(); + if (fixedAlias.isEmpty()) { + QCString *oldAlias = d->tableAliases.take(position); + if (oldAlias) { + d->tablePositionsForAliases.remove(*oldAlias); + delete oldAlias; + } +// d->maxIndexWithTableAlias = -1; + } + else { + d->tableAliases.replace(position, new QCString(fixedAlias)); + d->tablePositionsForAliases.replace(fixedAlias, new int(position)); +// d->maxIndexWithTableAlias = QMAX( d->maxIndexWithTableAlias, (int)index ); + } +} + +Relationship::List* QuerySchema::relationships() const +{ + return &d->relations; +} + +Field::List* QuerySchema::asterisks() const +{ + return &d->asterisks; +} + +QString QuerySchema::statement() const +{ + return d->statement; +} + +void QuerySchema::setStatement(const QString &s) +{ + d->statement = s; +} + +Field* QuerySchema::field(const QString& identifier, bool expanded) +{ + QueryColumnInfo *ci = columnInfo(identifier, expanded); + return ci ? ci->field : 0; +} + +QueryColumnInfo* QuerySchema::columnInfo(const QString& identifier, bool expanded) +{ + computeFieldsExpanded(); + return expanded ? d->columnInfosByNameExpanded[identifier] : d->columnInfosByName[identifier]; +} + +QueryColumnInfo::Vector QuerySchema::fieldsExpanded(FieldsExpandedOptions options) +{ + computeFieldsExpanded(); + if (options == WithInternalFields || options == WithInternalFieldsAndRowID) { + //a ref to a proper pointer (as we cache the vector for two cases) + QueryColumnInfo::Vector*& tmpFieldsExpandedWithInternal = + (options == WithInternalFields) ? d->fieldsExpandedWithInternal : d->fieldsExpandedWithInternalAndRowID; + //special case + if (!tmpFieldsExpandedWithInternal) { + //glue expanded and internal fields and cache it + const uint size = d->fieldsExpanded->count() + + (d->internalFields ? d->internalFields->count() : 0) + + ((options == WithInternalFieldsAndRowID) ? 1 : 0) /*ROWID*/; + tmpFieldsExpandedWithInternal = new QueryColumnInfo::Vector( size ); + const uint fieldsExpandedVectorSize = d->fieldsExpanded->size(); + for (uint i=0; i<fieldsExpandedVectorSize; i++) + tmpFieldsExpandedWithInternal->insert(i, d->fieldsExpanded->at(i)); + const uint internalFieldsCount = d->internalFields ? d->internalFields->size() : 0; + if (internalFieldsCount > 0) { + for (uint i=0; i < internalFieldsCount; i++) + tmpFieldsExpandedWithInternal->insert( + fieldsExpandedVectorSize + i, d->internalFields->at(i)); + } + if (options == WithInternalFieldsAndRowID) { + if (!d->fakeRowIDField) { + d->fakeRowIDField = new Field("rowID", Field::BigInteger); + d->fakeRowIDCol = new QueryColumnInfo(d->fakeRowIDField, QCString(), true); + } + tmpFieldsExpandedWithInternal->insert( + fieldsExpandedVectorSize + internalFieldsCount, d->fakeRowIDCol ); + } + } + return *tmpFieldsExpandedWithInternal; + } + + if (options == Default) + return *d->fieldsExpanded; + + //options == Unique: + QDict<char> columnsAlreadyFound; + QueryColumnInfo::Vector result( d->fieldsExpanded->count() ); //initial size is set +// QMapConstIterator<QueryColumnInfo*, bool> columnsAlreadyFoundIt; + //compute unique list + uint uniqueListCount = 0; + for (uint i=0; i<d->fieldsExpanded->count(); i++) { + QueryColumnInfo *ci = (*d->fieldsExpanded)[i]; +// columnsAlreadyFoundIt = columnsAlreadyFound.find(ci); +// uint foundColumnIndex = -1; + if (!columnsAlreadyFound[ci->aliasOrName()]) {// columnsAlreadyFoundIt==columnsAlreadyFound.constEnd()) + columnsAlreadyFound.insert(ci->aliasOrName(), (char*)1); + result.insert(uniqueListCount++, ci); + } + } + result.resize(uniqueListCount); //update result size + return result; +} + +QueryColumnInfo::Vector QuerySchema::internalFields() +{ + computeFieldsExpanded(); + return d->internalFields ? *d->internalFields : QueryColumnInfo::Vector(); +} + +QueryColumnInfo* QuerySchema::expandedOrInternalField(uint index) +{ + QueryColumnInfo::Vector vector = fieldsExpanded(WithInternalFields); + return (index < vector.size()) ? vector[index] : 0; +} + +inline QString lookupColumnKey(Field *foreignField, Field* field) +{ + QString res; + if (field->table()) // can be 0 for anonymous fields built as joined multiple visible columns + res = field->table()->name() + "."; + return res + field->name() + "_" + foreignField->table()->name() + "." + foreignField->name(); +} + +void QuerySchema::computeFieldsExpanded() +{ + if (d->fieldsExpanded) + return; + + if (!d->columnsOrder) { + d->columnsOrder = new QMap<QueryColumnInfo*,int>(); + d->columnsOrderWithoutAsterisks = new QMap<QueryColumnInfo*,int>(); + } + else { + d->columnsOrder->clear(); + d->columnsOrderWithoutAsterisks->clear(); + } + if (d->ownedVisibleColumns) + d->ownedVisibleColumns->clear(); + + //collect all fields in a list (not a vector yet, because we do not know its size) + QueryColumnInfo::List list; //temporary + QueryColumnInfo::List lookup_list; //temporary, for collecting additional fields related to lookup fields + QMap<QueryColumnInfo*, bool> columnInfosOutsideAsterisks; //helper for filling d->columnInfosByName + uint i = 0; + uint fieldPosition = 0; + uint numberOfColumnsWithMultipleVisibleFields = 0; //used to find an unique name for anonymous field + Field *f; + for (Field::ListIterator it = fieldsIterator(); (f = it.current()); ++it, fieldPosition++) { + if (f->isQueryAsterisk()) { + if (static_cast<QueryAsterisk*>(f)->isSingleTableAsterisk()) { + Field::List *ast_fields = static_cast<QueryAsterisk*>(f)->table()->fields(); + for (Field *ast_f = ast_fields->first(); ast_f; ast_f=ast_fields->next()) { +// d->detailedVisibility += isFieldVisible(fieldPosition); + QueryColumnInfo *ci = new QueryColumnInfo(ast_f, QCString()/*no field for asterisk!*/, + isColumnVisible(fieldPosition)); + list.append( ci ); + KexiDBDbg << "QuerySchema::computeFieldsExpanded(): caching (unexpanded) columns order: " + << ci->debugString() << " at position " << fieldPosition << endl; + d->columnsOrder->insert(ci, fieldPosition); +// list.append(ast_f); + } + } + else {//all-tables asterisk: iterate through table list + for (TableSchema *table = d->tables.first(); table; table = d->tables.next()) { + //add all fields from this table + Field::List *tab_fields = table->fields(); + for (Field *tab_f = tab_fields->first(); tab_f; tab_f = tab_fields->next()) { +//! \todo (js): perhaps not all fields should be appended here +// d->detailedVisibility += isFieldVisible(fieldPosition); +// list.append(tab_f); + QueryColumnInfo *ci = new QueryColumnInfo(tab_f, QCString()/*no field for asterisk!*/, + isColumnVisible(fieldPosition)); + list.append( ci ); + KexiDBDbg << "QuerySchema::computeFieldsExpanded(): caching (unexpanded) columns order: " + << ci->debugString() << " at position " << fieldPosition << endl; + d->columnsOrder->insert(ci, fieldPosition); + } + } + } + } + else { + //a single field +// d->detailedVisibility += isFieldVisible(fieldPosition); + QueryColumnInfo *ci = new QueryColumnInfo(f, columnAlias(fieldPosition), isColumnVisible(fieldPosition)); + list.append( ci ); + columnInfosOutsideAsterisks.insert( ci, true ); + KexiDBDbg << "QuerySchema::computeFieldsExpanded(): caching (unexpanded) column's order: " + << ci->debugString() << " at position " << fieldPosition << endl; + d->columnsOrder->insert(ci, fieldPosition); + d->columnsOrderWithoutAsterisks->insert(ci, fieldPosition); + + //handle lookup field schema + LookupFieldSchema *lookupFieldSchema = f->table() ? f->table()->lookupFieldSchema( *f ) : 0; + if (!lookupFieldSchema || lookupFieldSchema->boundColumn()<0) + continue; + // Lookup field schema found: + // Now we also need to fetch "visible" value from the lookup table, not only the value of binding. + // -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken) + // "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField" + LookupFieldSchema::RowSource& rowSource = lookupFieldSchema->rowSource(); + if (rowSource.type() == LookupFieldSchema::RowSource::Table) { + TableSchema *lookupTable = connection()->tableSchema( rowSource.name() ); + FieldList* visibleColumns = 0; + Field *boundField = 0; + if (lookupTable + && (uint)lookupFieldSchema->boundColumn() < lookupTable->fieldCount() + && (visibleColumns = lookupTable->subList( lookupFieldSchema->visibleColumns() )) + && (boundField = lookupTable->field( lookupFieldSchema->boundColumn() ))) + { + Field *visibleColumn = 0; + // for single visible column, just add it as-is + if (visibleColumns->fieldCount() == 1) { + visibleColumn = visibleColumns->fields()->first(); + } + else { + // for multiple visible columns, build an expression column + // (the expression object will be owned by column info) + visibleColumn = new Field(); + visibleColumn->setName( + QString::fromLatin1("[multiple_visible_fields_%1]") + .arg( ++numberOfColumnsWithMultipleVisibleFields )); + visibleColumn->setExpression( + new ConstExpr(CHARACTER_STRING_LITERAL, QVariant()/*not important*/)); + if (!d->ownedVisibleColumns) { + d->ownedVisibleColumns = new Field::List(); + d->ownedVisibleColumns->setAutoDelete(true); + } + d->ownedVisibleColumns->append( visibleColumn ); // remember to delete later + } + + lookup_list.append( + new QueryColumnInfo(visibleColumn, QCString(), true/*visible*/, ci/*foreign*/) ); +/* + //add visibleField to the list of SELECTed fields if it is not yes present there + if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) { + if (!table( visibleField->table()->name() )) { + } + if (!sql.isEmpty()) + sql += QString::fromLatin1(", "); + sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "." + + escapeIdentifier(visibleField->name(), drvEscaping)); + }*/ + } + delete visibleColumns; + } + else if (rowSource.type() == LookupFieldSchema::RowSource::Query) { + QuerySchema *lookupQuery = connection()->querySchema( rowSource.name() ); + if (!lookupQuery) + continue; + const QueryColumnInfo::Vector lookupQueryFieldsExpanded( lookupQuery->fieldsExpanded() ); + if ((uint)lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count()) + continue; + QueryColumnInfo *boundColumnInfo = 0; + if (!(boundColumnInfo = lookupQueryFieldsExpanded[ lookupFieldSchema->boundColumn() ])) + continue; + Field *boundField = boundColumnInfo->field; + if (!boundField) + continue; + const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() ); + bool ok = true; + // all indices in visibleColumns should be in [0..lookupQueryFieldsExpanded.size()-1] + foreach (QValueList<uint>::ConstIterator, visibleColumnsIt, visibleColumns) { + if ((*visibleColumnsIt) >= lookupQueryFieldsExpanded.count()) { + ok = false; + break; + } + } + if (!ok) + continue; + Field *visibleColumn = 0; + // for single visible column, just add it as-is + if (visibleColumns.count() == 1) { + visibleColumn = lookupQueryFieldsExpanded[ visibleColumns.first() ]->field; + } + else { + // for multiple visible columns, build an expression column + // (the expression object will be owned by column info) + visibleColumn = new Field(); + visibleColumn->setName( + QString::fromLatin1("[multiple_visible_fields_%1]") + .arg( ++numberOfColumnsWithMultipleVisibleFields )); + visibleColumn->setExpression( + new ConstExpr(CHARACTER_STRING_LITERAL, QVariant()/*not important*/)); + if (!d->ownedVisibleColumns) { + d->ownedVisibleColumns = new Field::List(); + d->ownedVisibleColumns->setAutoDelete(true); + } + d->ownedVisibleColumns->append( visibleColumn ); // remember to delete later + } + + lookup_list.append( + new QueryColumnInfo(visibleColumn, QCString(), true/*visible*/, ci/*foreign*/) ); +/* + //add visibleField to the list of SELECTed fields if it is not yes present there + if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) { + if (!table( visibleField->table()->name() )) { + } + if (!sql.isEmpty()) + sql += QString::fromLatin1(", "); + sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "." + + escapeIdentifier(visibleField->name(), drvEscaping)); + }*/ + } + } + } + //prepare clean vector for expanded list, and a map for order information + if (!d->fieldsExpanded) { + d->fieldsExpanded = new QueryColumnInfo::Vector( list.count() );// Field::Vector( list.count() ); + d->fieldsExpanded->setAutoDelete(true); + d->columnsOrderExpanded = new QMap<QueryColumnInfo*,int>(); + } + else {//for future: + d->fieldsExpanded->clear(); + d->fieldsExpanded->resize( list.count() ); + d->columnsOrderExpanded->clear(); + } + + /*fill (based on prepared 'list' and 'lookup_list'): + -the vector + -the map + -"fields by name" dictionary + */ + d->columnInfosByName.clear(); + d->columnInfosByNameExpanded.clear(); + i=0; + QueryColumnInfo *ci; + for (QueryColumnInfo::ListIterator it(list); (ci = it.current()); ++it, i++) { + d->fieldsExpanded->insert(i, ci); + d->columnsOrderExpanded->insert(ci, i); + //remember field by name/alias/table.name if there's no such string yet in d->columnInfosByNameExpanded + if (!ci->alias.isEmpty()) { + //store alias and table.alias + if (!d->columnInfosByNameExpanded[ ci->alias ]) + d->columnInfosByNameExpanded.insert( ci->alias, ci ); + QString tableAndAlias( ci->alias ); + if (ci->field->table()) + tableAndAlias.prepend(ci->field->table()->name() + "."); + if (!d->columnInfosByNameExpanded[ tableAndAlias ]) + d->columnInfosByNameExpanded.insert( tableAndAlias, ci ); + //the same for "unexpanded" list + if (columnInfosOutsideAsterisks.contains(ci)) { + if (!d->columnInfosByName[ ci->alias ]) + d->columnInfosByName.insert( ci->alias, ci ); + if (!d->columnInfosByName[ tableAndAlias ]) + d->columnInfosByName.insert( tableAndAlias, ci ); + } + } + else { + //no alias: store name and table.name + if (!d->columnInfosByNameExpanded[ ci->field->name() ]) + d->columnInfosByNameExpanded.insert( ci->field->name(), ci ); + QString tableAndName( ci->field->name() ); + if (ci->field->table()) + tableAndName.prepend(ci->field->table()->name() + "."); + if (!d->columnInfosByNameExpanded[ tableAndName ]) + d->columnInfosByNameExpanded.insert( tableAndName, ci ); + //the same for "unexpanded" list + if (columnInfosOutsideAsterisks.contains(ci)) { + if (!d->columnInfosByName[ ci->field->name() ]) + d->columnInfosByName.insert( ci->field->name(), ci ); + if (!d->columnInfosByName[ tableAndName ]) + d->columnInfosByName.insert( tableAndName, ci ); + } + } + } + + //remove duplicates for lookup fields + QDict<uint> lookup_dict(101); //used to fight duplicates and to update QueryColumnInfo::indexForVisibleLookupValue() + // (a mapping from table.name string to uint* lookupFieldIndex + lookup_dict.setAutoDelete(true); + i=0; + for (QueryColumnInfo::ListIterator it(lookup_list); (ci = it.current());) + { + const QString key( lookupColumnKey(ci->foreignColumn()->field, ci->field) ); + if ( /* not needed columnInfo( tableAndFieldName ) || */ + lookup_dict[ key ]) { + // this table.field is already fetched by this query + ++it; + lookup_list.removeRef( ci ); + } + else { + lookup_dict.replace( key, new uint( i ) ); + ++it; + i++; + } + } + + //create internal expanded list with lookup fields + if (d->internalFields) { + d->internalFields->clear(); + d->internalFields->resize( lookup_list.count() ); + } + delete d->fieldsExpandedWithInternal; //clear cache + delete d->fieldsExpandedWithInternalAndRowID; //clear cache + d->fieldsExpandedWithInternal = 0; + d->fieldsExpandedWithInternalAndRowID = 0; + if (!lookup_list.isEmpty() && !d->internalFields) {//create on demand + d->internalFields = new QueryColumnInfo::Vector( lookup_list.count() ); + d->internalFields->setAutoDelete(true); + } + i=0; + for (QueryColumnInfo::ListIterator it(lookup_list); it.current();i++, ++it) + { + //add it to the internal list + d->internalFields->insert(i, it.current()); + d->columnsOrderExpanded->insert(it.current(), list.count()+i); + } + + //update QueryColumnInfo::indexForVisibleLookupValue() cache for columns + numberOfColumnsWithMultipleVisibleFields = 0; + for (i=0; i < d->fieldsExpanded->size(); i++) { + QueryColumnInfo* ci = d->fieldsExpanded->at(i); +//! @todo QuerySchema itself will also support lookup fields... + LookupFieldSchema *lookupFieldSchema + = ci->field->table() ? ci->field->table()->lookupFieldSchema( *ci->field ) : 0; + if (!lookupFieldSchema || lookupFieldSchema->boundColumn()<0) + continue; + LookupFieldSchema::RowSource& rowSource = lookupFieldSchema->rowSource(); + if (rowSource.type() == LookupFieldSchema::RowSource::Table) { + TableSchema *lookupTable = connection()->tableSchema( rowSource.name() ); + FieldList* visibleColumns = 0; + if (lookupTable + && (uint)lookupFieldSchema->boundColumn() < lookupTable->fieldCount() + && (visibleColumns = lookupTable->subList( lookupFieldSchema->visibleColumns() ))) + { + Field *visibleColumn = 0; + // for single visible column, just add it as-is + if (visibleColumns->fieldCount() == 1) + { + visibleColumn = visibleColumns->fields()->first(); + const QString key( lookupColumnKey(ci->field, visibleColumn) ); + uint *index = lookup_dict[ key ]; + if (index) + ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index ); + } + else { + const QString key( QString::fromLatin1("[multiple_visible_fields_%1]_%2.%3") + .arg( ++numberOfColumnsWithMultipleVisibleFields ) + .arg(ci->field->table()->name()).arg(ci->field->name()) ); + uint *index = lookup_dict[ key ]; + if (index) + ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index ); + } + } + delete visibleColumns; + } + else if (rowSource.type() == LookupFieldSchema::RowSource::Query) { + QuerySchema *lookupQuery = connection()->querySchema( rowSource.name() ); + if (!lookupQuery) + continue; + const QueryColumnInfo::Vector lookupQueryFieldsExpanded( lookupQuery->fieldsExpanded() ); + if ((uint)lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count()) + continue; + QueryColumnInfo *boundColumnInfo = 0; + if (!(boundColumnInfo = lookupQueryFieldsExpanded[ lookupFieldSchema->boundColumn() ])) + continue; + Field *boundField = boundColumnInfo->field; + if (!boundField) + continue; + const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() ); + Field *visibleColumn = 0; + // for single visible column, just add it as-is + if (visibleColumns.count() == 1) { + visibleColumn = lookupQueryFieldsExpanded[ visibleColumns.first() ]->field; + const QString key( lookupColumnKey(ci->field, visibleColumn) ); + uint *index = lookup_dict[ key ]; + if (index) + ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index ); + } + else { + const QString key( QString::fromLatin1("[multiple_visible_fields_%1]_%2.%3") + .arg( ++numberOfColumnsWithMultipleVisibleFields ) + .arg(ci->field->table()->name()).arg(ci->field->name()) ); + uint *index = lookup_dict[ key ]; + if (index) + ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index ); + } + } + else { + KexiDBWarn << "QuerySchema::computeFieldsExpanded(): unsupported row source type " + << rowSource.typeName() << endl; + } + } +} + +QMap<QueryColumnInfo*,int> QuerySchema::columnsOrder(ColumnsOrderOptions options) +{ + if (!d->columnsOrder) + computeFieldsExpanded(); + if (options == UnexpandedList) + return *d->columnsOrder; + else if (options == UnexpandedListWithoutAsterisks) + return *d->columnsOrderWithoutAsterisks; + return *d->columnsOrderExpanded; +} + +QValueVector<int> QuerySchema::pkeyFieldsOrder() +{ + if (d->pkeyFieldsOrder) + return *d->pkeyFieldsOrder; + + TableSchema *tbl = masterTable(); + if (!tbl || !tbl->primaryKey()) + return QValueVector<int>(); + + //get order of PKEY fields (e.g. for rows updating or inserting ) + IndexSchema *pkey = tbl->primaryKey(); + pkey->debug(); + debug(); + d->pkeyFieldsOrder = new QValueVector<int>( pkey->fieldCount(), -1 ); + + const uint fCount = fieldsExpanded().count(); + d->pkeyFieldsCount = 0; + for (uint i = 0; i<fCount; i++) { + QueryColumnInfo *fi = d->fieldsExpanded->at(i); + const int fieldIndex = fi->field->table()==tbl ? pkey->indexOf(fi->field) : -1; + if (fieldIndex!=-1/* field found in PK */ + && d->pkeyFieldsOrder->at(fieldIndex)==-1 /* first time */) + { + KexiDBDbg << "QuerySchema::pkeyFieldsOrder(): FIELD " << fi->field->name() + << " IS IN PKEY AT POSITION #" << fieldIndex << endl; +// (*d->pkeyFieldsOrder)[j]=i; + (*d->pkeyFieldsOrder)[fieldIndex]=i; + d->pkeyFieldsCount++; +// j++; + } + } + KexiDBDbg << "QuerySchema::pkeyFieldsOrder(): " << d->pkeyFieldsCount + << " OUT OF " << pkey->fieldCount() << " PKEY'S FIELDS FOUND IN QUERY " << name() << endl; + return *d->pkeyFieldsOrder; +} + +uint QuerySchema::pkeyFieldsCount() +{ + (void)pkeyFieldsOrder(); /* rebuild information */ + return d->pkeyFieldsCount; +} + +Relationship* QuerySchema::addRelationship( Field *field1, Field *field2 ) +{ +//@todo: find existing global db relationships + Relationship *r = new Relationship(this, field1, field2); + if (r->isEmpty()) { + delete r; + return 0; + } + + d->relations.append( r ); + return r; +} + +QueryColumnInfo::List* QuerySchema::autoIncrementFields() +{ + if (!d->autoincFields) { + d->autoincFields = new QueryColumnInfo::List(); + } + TableSchema *mt = masterTable(); + if (!mt) { + KexiDBWarn << "QuerySchema::autoIncrementFields(): no master table!" << endl; + return d->autoincFields; + } + if (d->autoincFields->isEmpty()) {//no cache + QueryColumnInfo::Vector fexp = fieldsExpanded(); + for (int i=0; i<(int)fexp.count(); i++) { + QueryColumnInfo *fi = fexp[i]; + if (fi->field->table() == mt && fi->field->isAutoIncrement()) { + d->autoincFields->append( fi ); + } + } + } + return d->autoincFields; +} + +QString QuerySchema::sqlColumnsList(QueryColumnInfo::List* infolist, Driver *driver) +{ + if (!infolist) + return QString::null; + QString result; + result.reserve(256); + QueryColumnInfo::ListIterator it( *infolist ); + bool start = true; + for (; it.current(); ++it) { + if (!start) + result += ","; + else + start = false; + result += driver->escapeIdentifier( it.current()->field->name() ); + } + return result; +} + +QString QuerySchema::autoIncrementSQLFieldsList(Driver *driver) +{ + if ((Driver *)d->lastUsedDriverForAutoIncrementSQLFieldsList != driver + || d->autoIncrementSQLFieldsList.isEmpty()) + { + d->autoIncrementSQLFieldsList = QuerySchema::sqlColumnsList( autoIncrementFields(), driver ); + d->lastUsedDriverForAutoIncrementSQLFieldsList = driver; + } + return d->autoIncrementSQLFieldsList; +} + +void QuerySchema::setWhereExpression(BaseExpr *expr) +{ + delete d->whereExpr; + d->whereExpr = expr; +} + +void QuerySchema::addToWhereExpression(KexiDB::Field *field, const QVariant& value, int relation) +{ + int token; + if (value.isNull()) + token = SQL_NULL; + else if (field->isIntegerType()) { + token = INTEGER_CONST; + } + else if (field->isFPNumericType()) { + token = REAL_CONST; + } + else { + token = CHARACTER_STRING_LITERAL; +//! @todo date, time + } + + BinaryExpr * newExpr = new BinaryExpr( + KexiDBExpr_Relational, + new ConstExpr( token, value ), + relation, + new VariableExpr((field->table() ? (field->table()->name()+".") : QString::null)+field->name()) + ); + if (d->whereExpr) { + d->whereExpr = new BinaryExpr( + KexiDBExpr_Logical, + d->whereExpr, + AND, + newExpr + ); + } + else { + d->whereExpr = newExpr; + } +} + +/* +void QuerySchema::addToWhereExpression(KexiDB::Field *field, const QVariant& value) + switch (value.type()) { + case Int: case UInt: case Bool: case LongLong: case ULongLong: + token = INTEGER_CONST; + break; + case Double: + token = REAL_CONST; + break; + default: + token = CHARACTER_STRING_LITERAL; + } +//! @todo date, time + +*/ + +BaseExpr *QuerySchema::whereExpression() const +{ + return d->whereExpr; +} + +void QuerySchema::setOrderByColumnList(const OrderByColumnList& list) +{ + d->orderByColumnList = list; +// all field names should be found, exit otherwise ..........? +} + +OrderByColumnList& QuerySchema::orderByColumnList() const +{ + return d->orderByColumnList; +} + +QuerySchemaParameterList QuerySchema::parameters() +{ + if (!whereExpression()) + return QuerySchemaParameterList(); + QuerySchemaParameterList params; + whereExpression()->getQueryParameters(params); + return params; +} + +/* + new field1, Field *field2 + if (!field1 || !field2) { + KexiDBWarn << "QuerySchema::addRelationship(): !masterField || !detailsField" << endl; + return; + } + if (field1->isQueryAsterisk() || field2->isQueryAsterisk()) { + KexiDBWarn << "QuerySchema::addRelationship(): relationship's fields cannot be asterisks" << endl; + return; + } + if (!hasField(field1) && !hasField(field2)) { + KexiDBWarn << "QuerySchema::addRelationship(): fields do not belong to this query" << endl; + return; + } + if (field1->table() == field2->table()) { + KexiDBWarn << "QuerySchema::addRelationship(): fields cannot belong to the same table" << endl; + return; + } +//@todo: check more things: -types +//@todo: find existing global db relationships + + Field *masterField = 0, *detailsField = 0; + IndexSchema *masterIndex = 0, *detailsIndex = 0; + if (field1->isPrimaryKey() && field2->isPrimaryKey()) { + //2 primary keys + masterField = field1; + masterIndex = masterField->table()->primaryKey(); + detailsField = field2; + detailsIndex = masterField->table()->primaryKey(); + } + else if (field1->isPrimaryKey()) { + masterField = field1; + masterIndex = masterField->table()->primaryKey(); + detailsField = field2; +//@todo: check if it already exists + detailsIndex = new IndexSchema(detailsField->table()); + detailsIndex->addField(detailsField); + detailsIndex->setForeigKey(true); + // detailsField->setForeignKey(true); + } + else if (field2->isPrimaryKey()) { + detailsField = field1; + masterField = field2; + masterIndex = masterField->table()->primaryKey(); +//@todo + } + + if (!masterIndex || !detailsIndex) + return; //failed + + Relationship *rel = new Relationship(masterIndex, detailsIndex); + + d->relations.append( rel ); +}*/ + +//--------------------------------------------------- + +QueryAsterisk::QueryAsterisk( QuerySchema *query, TableSchema *table ) + :Field() + ,m_table(table) +{ + assert(query); + m_parent = query; + setType(Field::Asterisk); +} + +QueryAsterisk::~QueryAsterisk() +{ +} + +Field* QueryAsterisk::copy() const +{ + return new QueryAsterisk(*this); +} + +void QueryAsterisk::setTable(TableSchema *table) +{ + KexiDBDbg << "QueryAsterisk::setTable()" << endl; + m_table=table; +} + +QString QueryAsterisk::debugString() const +{ + QString dbg; + if (isAllTableAsterisk()) { + dbg += "ALL-TABLES ASTERISK (*) ON TABLES("; + TableSchema *table; + QString table_names; + for (TableSchema::ListIterator it( *query()->tables() ); (table = it.current()); ++it) { + if (!table_names.isEmpty()) + table_names += ", "; + table_names += table->name(); + } + dbg += (table_names + ")"); + } + else { + dbg += ("SINGLE-TABLE ASTERISK (" + table()->name() + ".*)"); + } + return dbg; +} + diff --git a/kexi/kexidb/queryschema.h b/kexi/kexidb/queryschema.h new file mode 100644 index 00000000..76dfa757 --- /dev/null +++ b/kexi/kexidb/queryschema.h @@ -0,0 +1,832 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2007 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_QUERY_H +#define KEXIDB_QUERY_H + +#include <qvaluevector.h> +#include <qstring.h> +#include <qmap.h> +#include <qptrlist.h> + +#include "fieldlist.h" +#include "schemadata.h" +#include "tableschema.h" +#include "relationship.h" + +namespace KexiDB { + +class Connection; +class QueryAsterisk; +class QuerySchemaPrivate; +class QuerySchemaParameter; +typedef QValueList<QuerySchemaParameter> QuerySchemaParameterList; + +//! @short Helper class that assigns additional information for the column in a query +/*! The following information is assigned: + - alias + - visibility + QueryColumnInfo::Vector is created and returned by QuerySchema::fieldsExpanded(). + It is efficiently cached within the QuerySchema object. +*/ +class KEXI_DB_EXPORT QueryColumnInfo +{ + public: + typedef QPtrVector<QueryColumnInfo> Vector; + typedef QPtrList<QueryColumnInfo> List; + typedef QPtrListIterator<QueryColumnInfo> ListIterator; + + QueryColumnInfo(Field *f, const QCString& _alias, bool _visible, QueryColumnInfo *foreignColumn = 0); + ~QueryColumnInfo(); + + //! \return alias if it is not empty, field's name otherwise. + inline QCString aliasOrName() const { + return alias.isEmpty() ? field->name().latin1() : (const char*)alias; + } + + //! \return field's caption if it is not empty, field's alias otherwise. + //! If alias is also empty - returns field's name. + inline QString captionOrAliasOrName() const { + return field->caption().isEmpty() ? QString(aliasOrName()) : field->caption(); } + + Field *field; + QCString alias; + + /*! \return index of column with visible lookup value within the 'fields expanded' vector. + -1 means no visible lookup value is available because there is no lookup for the column defined. + Cached for efficiency as we use this information frequently. + @see LookupFieldSchema::visibleVolumn() */ + inline int indexForVisibleLookupValue() const { return m_indexForVisibleLookupValue; } + + /*! Sets index of column with visible lookup value within the 'fields expanded' vector. */ + inline void setIndexForVisibleLookupValue(int index) { m_indexForVisibleLookupValue = index; } + + //! \return non-0 if this column is a visible column for other column + QueryColumnInfo *foreignColumn() const { return m_foreignColumn; } + + /*! \return string for debugging purposes. */ + QString debugString() const; + + //! true if this column is visible to the user (and its data is fetched by the engine) + bool visible : 1; + + private: + /*! Index of column with visible lookup value within the 'fields expanded' vector. + @see indexForVisibleLookupValue() */ + int m_indexForVisibleLookupValue; + + //! Non-0 if this column is a visible column for \a m_foreignColumn + QueryColumnInfo *m_foreignColumn; +}; + +//! @short KexiDB::OrderByColumn provides information about a single query column used for sorting +/*! The column can be expression or table field. */ +class KEXI_DB_EXPORT OrderByColumn +{ + public: + typedef QValueListConstIterator<OrderByColumn> ListConstIterator; + OrderByColumn(); + OrderByColumn(QueryColumnInfo& column, bool ascending = true, int pos = -1); + + //! Like above but used when the field \a field is not present on the list of columns. + //! (e.g. SELECT a FROM t ORDER BY b; where T is a table with fields (a,b)). + OrderByColumn(Field& field, bool ascending = true); + + ~OrderByColumn(); + + //! A column to sort. + inline QueryColumnInfo* column() const { return m_column; } + + /*! A helper for column() that allows you to know that sorting column + was defined by providing its position. -1 by default. + Example query: SELECT a, b FROM T ORDER BY 2 */ + inline int position() const { return m_pos; } + + //! A field to sort, used only in case when the second constructor was used. + inline Field *field() const { return m_field; } + + //! \return true if ascending sorting should be performed (the default). + inline bool ascending() const { return m_ascending; } + + //! \return true if this column is thesame as \a col + bool operator== ( const OrderByColumn& col ) const + { return m_column==col.m_column && m_field==col.m_field + && m_ascending==col.m_ascending; } + + /*! \return string for debugging purposes. */ + QString debugString() const; + + /*! \return a string like "name ASC" usable for building a SQL statement. + If \a includeTableNames is true (the default) field is output in a form + of "tablename.fieldname" (but only if fieldname is not a name of alias). + \a drv and \a identifierEscaping are used for escaping the table and field identifiers. */ + QString toSQLString(bool includeTableName = true, + Driver *drv = 0, int identifierEscaping = Driver::EscapeDriver|Driver::EscapeAsNecessary) const; + + protected: + //! Column to sort + QueryColumnInfo* m_column; //!< 0 if m_field is non-0. + int m_pos; //!< A helper for m_column that allows to know that sorting column + //!< was defined by providing its position. -1 by default. + //!< e.g. SELECT a, b FROM T ORDER BY 2 + Field* m_field; //!< Used only in case when the second contructor is used. + + //! true if ascending sorting should be performed (the default). + bool m_ascending : 1; +}; + +//! A base for KexiDB::OrderByColumnList +typedef QValueList<OrderByColumn> OrderByColumnListBase; + +//! @short KexiDB::OrderByColumnList provides list of sorted columns for a query schema +class KEXI_DB_EXPORT OrderByColumnList : protected OrderByColumnListBase +{ + public: + /*! Constructs empty list of ordered columns. */ + OrderByColumnList(); + + ~OrderByColumnList(); + + /*! Appends multiple fields for sorting. \a querySchema + is used to find appropriate field or alias name. + \return false if there is at least one name for which a field or alias name does not exist + (all the newly appended fields are removed in this case) */ + bool appendFields(QuerySchema& querySchema, + const QString& field1, bool ascending1 = true, + const QString& field2 = QString::null, bool ascending2 = true, + const QString& field3 = QString::null, bool ascending3 = true, + const QString& field4 = QString::null, bool ascending4 = true, + const QString& field5 = QString::null, bool ascending5 = true); + + /*! Appends column \a columnInfo. Ascending sorting is set is \a ascending is true. */ + void appendColumn(QueryColumnInfo& columnInfo, bool ascending = true); + + /*! Appends a field \a field. Ascending sorting is set is \a ascending is true. + Read documentation of \ref OrderByColumn(const Field& field, bool ascending = true) + for more info. */ + void appendField(Field& field, bool ascending = true); + + /*! Appends field with a name \a field. Ascending sorting is set is \a ascending is true. + \return true on successful appending, and false if there is no such field or alias + name in the \a querySchema. */ + bool appendField(QuerySchema& querySchema, const QString& fieldName, + bool ascending = true); + + /*! Appends a column that is at position \a pos (counted from 0). + \return true on successful adding and false if there is no such position \a pos. */ + bool appendColumn(QuerySchema& querySchema, bool ascending = true, int pos = -1); + + /*! Appends \a column to the list. */ + void appendColumn(const OrderByColumn& column); + + /*! \return true if the list is empty. */ + bool isEmpty() const { return OrderByColumnListBase::isEmpty(); } + + /*! \return number of elements of the list. */ + uint count() const { return OrderByColumnListBase::count(); } + + /*! Removes all elements from the list. */ + void clear() { OrderByColumnListBase::clear(); } + + const_iterator constBegin () const { return OrderByColumnListBase::constBegin(); } + const_iterator constEnd () const { return OrderByColumnListBase::constEnd(); } + + /*! \return string for debugging purposes. */ + QString debugString() const; + + /*! \return a string like "name ASC, 2 DESC" usable for building a SQL statement. + If \a includeTableNames is true (the default) fields are output in a form + of "tablename.fieldname". + \a drv and \a identifierEscaping are used for escaping the table and field identifiers. */ + QString toSQLString(bool includeTableNames = true, + Driver *drv = 0, int identifierEscaping = Driver::EscapeDriver|Driver::EscapeAsNecessary) const; +}; + +//! @short KexiDB::QuerySchema provides information about database query +/*! The query that can be executed using KexiDB-compatible SQL database engine + or used as an introspection tool. KexiDB parser builds QuerySchema objects + by parsing SQL statements. */ +class KEXI_DB_EXPORT QuerySchema : public FieldList, public SchemaData +{ + public: + /*! Creates empty query object (without columns). */ + QuerySchema(); + + /*! Creates query schema object that is equivalent to "SELECT * FROM table" + sql command. Schema of \a table is used to contruct this query -- + it is defined by just adding all the fields to the query in natural order. + To avoid problems (e.g. with fields added outside of Kexi using ALTER TABLE) + we do not use "all-tables query asterisk" (see QueryAsterisk) item to achieve + this effect. + + Properties such as the name and caption of the query are inherited + from table schema. + + We consider that query schema based on \a table is not (a least yet) stored + in a system table, so query connection is set to NULL + (even if \a tableSchema's connection is not NULL). + Id of the created query is set to 0. */ + QuerySchema(TableSchema& tableSchema); + + /*! Copy constructor. Creates deep copy of \a querySchema. + QueryAsterisk objects are deeply copied while only pointers to Field objects are copied. */ + QuerySchema(const QuerySchema& querySchema); + + virtual ~QuerySchema(); + + /*! Inserts \a field to the columns list at \a position. + Inserted field will not be owned by this QuerySchema object, + but still by corresponding TableSchema. + + As \a field object you can also pass KexiDB::QueryAsterisk, + (see QueryAsterisk class description). + + Note: After inserting a field, corresponding table will be automatically + added to query's tables list if it is not present there (see tables()). + Field must have its table assigned. + + Added field will be visible. Use insertField(position, field, false) + to add invisible field. + */ + virtual FieldList& insertField(uint position, Field *field); + + /* Like above method, but you can also set column's visibility. + New column is not bound explicitly to any table. + */ + FieldList& insertField(uint position, Field *field, bool visible); + + /* Like above method, but you can also explicitly bound the new column + to specific position on tables list. + If \a visible is true (the default), the field will be visible. + If bindToTable==-1, no particular table should be bound. + @see tableBoundToColumn(uint columnPosition) */ + FieldList& insertField(uint position, Field *field, + int bindToTable, bool visible = true); + + /*! Adds \a field to the columns list. + If \a visible is true (the default), the field will be visible. + \sa insertField() */ + FieldList& addField(Field* field, bool visible = true); + + /*! Adds \a field to the columns list. Also binds to a table + at \a bindToTable position. Use bindToTable==-1 if no table should be bound. + If \a visible is true (the default), the field will be visible. + \sa insertField() + \sa tableBoundToColumn(uint columnPosition) + */ + FieldList& addField(Field* field, int bindToTable, + bool visible = true); + + /*! Removes field from the columns list. Use with care. */ + virtual void removeField(Field *field); + + /*! Adds a field built on top of \a expr expression. + This creates a new Field object and adds it to the query schema using addField(). */ + FieldList& addExpression(BaseExpr* expr, bool visible = true); + + /*! \return visibility flag for column at \a position. + By default column is visible. */ + bool isColumnVisible(uint position) const; + + //! Sets visibility flag for column at \a position to \a v. + void setColumnVisible(uint position, bool v); + + /*! Adds \a asterisk at the and of columns list. */ + FieldList& addAsterisk(QueryAsterisk *asterisk, bool visible = true); + + /*! Removes all columns and their aliases from the columns list, + removes all tables and their aliases from the tables list within this query. + Sets master table information to NULL. + Does not destroy any objects though. Clears name and all other properties. + \sa FieldList::clear() */ + virtual void clear(); + + /*! \return string for debugging purposes. */ + virtual QString debugString(); + + /*! If query was created using a connection, + returns this connection object, otherwise NULL. */ + Connection* connection() const; + + /*! \return table that is master to this query. + All potentially-editable columns within this query belong just to this table. + This method also can return NULL if there are no tables at all, + or if previously assigned master table schema has been removed + with removeTable(). + Every query that has at least one table defined, should have + assigned a master table. + If no master table is assigned explicitym but this method there is only + one table used for this query even if there are table aliases, + a single table is returned here. + (e.g. "T" table is returned for "SELECT T1.A, T2.B FROM T T1, T T2" statement). */ + TableSchema* masterTable() const; + + /*! Sets master table of this query to \a table. + This table should be also added to query's tables list + using addTable(). If \a table equals NULL, nothing is performed. + \sa masterTable() */ + void setMasterTable(TableSchema *table); + + /*! \return list of tables used in a query. + This also includes master table. + \sa masterTable() */ + TableSchema::List* tables() const; + + /*! Adds \a table schema as one of tables used in a query. + if \a alias is not empty, it will be assigned to this table + using setTableAlias(position, alias) + */ + void addTable(TableSchema *table, const QCString& alias = QCString()); + + /*! Removes \a table schema from this query. + This does not destroy \a table object but only takes it out of the list. + If this table was master for the query, master table information is also + invalidated. */ + void removeTable(TableSchema *table); + + /*! \return table with name \a tableName or 0 if this query has no such table. */ + TableSchema* table(const QString& tableName) const; + + /*! \return true if the query uses \a table. */ + bool contains(TableSchema *table) const; + + /*! Convenience function. + \return table field by searching through all tables in this query. + The field does not need to be included on the list of query columns. + Similarly, query aliases are not taken into account. + + \a tableOrTableAndFieldName string may contain table name and field name + with '.' character between them, e.g. "mytable.myfield". + This is recommended way to avoid ambiguity. + 0 is returned if the query has no such + table defined of the table has no such field defined. + If you do not provide a table name, the first field found is returned. + + QuerySchema::table("mytable")->field("myfield") could be + alternative for findTableField("mytable.myfield") but it can crash + if "mytable" is not defined in the query. + + @see KexiDB::splitToTableAndFieldParts() + */ + Field* findTableField(const QString &tableOrTableAndFieldName) const; + + /*! \return alias of a column at \a position or null string + If there is no alias for this column + or if there is no such column within the query defined. + If the column is an expression and has no alias defined, + a new unique alias will be generated automatically on this call. + */ + QCString columnAlias(uint position) const; + + /*! Provided for convenience. + \return true if a column at \a position has non empty alias defined + within the query. + If there is no alias for this column, + or if there is no such column in the query defined, false is returned. */ + bool hasColumnAlias(uint position) const; + + /*! Sets \a alias for a column at \a position, within the query. + Passing empty string to \a alias clears alias for a given column. */ + void setColumnAlias(uint position, const QCString& alias); + + /*! \return a table position (within FROM section), + that is bound to column at \a columnPosition (within SELECT section). + This information can be used to find if there is alias defined for + a table that is referenced by a given column. + + For example, for "SELECT t2.id FROM table1 t1, table2 t2" query statement, + columnBoundToTable(0) returns 1, what means that table at position 1 + (within FROM section) is bound to column at position 0, so we can + now call tableAlias(1) to see if we have used alias for this column (t2.d) + or just a table name (table2.d). + + These checkings are performed e.g. by Connection::queryStatement() + to construct a statement string maximally identical to originally + defined query statement. + + -1 is returned if: + - \a columnPosition is out of range (i.e. < 0 or >= fieldCount()) + - a column at \a columnPosition is not bound to any table (i.e. + no database field is used for this column, + e.g. "1" constant for "SELECT 1 from table" query statement) + */ + int tableBoundToColumn(uint columnPosition) const; + + /*! \return alias of a table at \a position (within FROM section) + or null string if there is no alias for this table + or if there is no such table within the query defined. */ + QCString tableAlias(uint position) const; + + /*! \return table position (within FROM section) that has attached + alias \a name. + If there is no such alias, -1 is returned. + Only first table's position attached for this alias is returned. + It is not especially bad, since aliases rarely can be duplicated, + what leads to ambiguity. + Duplicated aliases are only allowed for trivial queries that have + no database fields used within their columns, + e.g. "SELECT 1 from table1 t, table2 t" is ok + but "SELECT t.id from table1 t, table2 t" is not. + */ + int tablePositionForAlias(const QCString& name) const; + + /*! \return table position (within FROM section) for \a tableName. + -1 is returend if there's no such table declared in the FROM section. + \sa tablePositions() + */ + int tablePosition(const QString& tableName) const; + + /*! \return a list of all \a tableName table occurrences (within FROM section). + E.g. for "SELECT * FROM table t, table t2" [0, 1] list is returned. + Empty list is returned there's no such table declared + in the FROM section at all. + \sa tablePosition() + */ + QValueList<int> tablePositions(const QString& tableName) const; + + /*! Provided for convenience. + \return true if a table at \a position (within FROM section of the the query) + has non empty alias defined. + If there is no alias for this table, + or if there is no such table in the query defined, false is returned. */ + bool hasTableAlias(uint position) const; + + /*! \return column position that has defined alias \a name. + If there is no such alias, -1 is returned. */ + int columnPositionForAlias(const QCString& name) const; + + /*! Sets \a alias for a table at \a position (within FROM section + of the the query). + Passing empty sting to \a alias clears alias for a given table + (only for specified \a position). */ + void setTableAlias(uint position, const QCString& alias); + + /*! \return a list of relationships defined for this query */ + Relationship::List* relationships() const; + + /*! Adds a new relationship defined by \a field1 and \a field2. + Both fields should belong to two different tables of this query. + This is convenience function useful for a typical cases. + It automatically creates Relationship object for this query. + If one of the fields are primary keys, it will be detected + and appropriate master-detail relation will be established. + This functiuon does nothing if the arguments are invalid. */ + Relationship* addRelationship( Field *field1, Field *field2 ); + + /*! \return list of QueryAsterisk objects defined for this query */ + Field::List* asterisks() const; + + /*! \return field for \a identifier or 0 if no field for this name + was found within the query. fieldsExpanded() method is used + to lookup expanded list of the query fields, so queries with asterisks + are processed well. + If a field has alias defined, name is not taken into account, + but only its alias. If a field has no alias: + - field's name is checked + - field's table and field's name are checked in a form of "tablename.fieldname", + so you can provide \a identifier in this form to avoid ambiguity. + + If there are more than one fields with the same name equal to \a identifier, + first-found is returned (checking is performed from first to last query field). + Structures needed to compute result of this method are cached, + so only first usage costs o(n) - another usages cost o(1). + + Example: + Let query be defined by "SELECT T.B AS X, T.* FROM T" statement and let T + be table containing fields A, B, C. + Expanded list of columns for the query is: T.B AS X, T.A, T.B, T.C. + - Calling field("B") will return a pointer to third query column (not the first, + because it is covered by "X" alias). Additionally, calling field("X") + will return the same pointer. + - Calling field("T.A") will return the same pointer as field("A"). + */ + virtual Field* field(const QString& name, bool expanded = true); + + /*! \return field id or NULL if there is no such a field. */ + inline Field* field(uint id) { return FieldList::field(id); } + + /*! Like QuerySchema::field(const QString& name) but returns not only Field + object for \a identifier but entire QueryColumnInfo object. + \a identifier can be: + - a fieldname + - an aliasname + - a tablename.fieldname + - a tablename.aliasname + Note that if there are two occurrrences of the same name, + only the first is accessible using this method. For instance, + calling columnInfo("name") for "SELECT t1.name, t2.name FROM t1, t2" statement + will only return the column related to t1.name and not t2.name, so you'll need to + explicitly specify "t2.name" as the identifier to get the second column. */ + QueryColumnInfo* columnInfo(const QString& identifier, bool expanded = true); + + /*! Options used in fieldsExpanded(). */ + enum FieldsExpandedOptions { + Default, //!< All fields are returned even if duplicated + Unique, //!< Unique list of fields is returned + WithInternalFields, //!< Like Default but internal fields (for lookup) are appended + WithInternalFieldsAndRowID //!< Like WithInternalFields but RowID (big int type) field + //!< is appended after internal fields + }; + + /*! \return fully expanded list of fields. + QuerySchema::fields() returns vector of fields used for the query columns, + but in a case when there are asterisks defined for the query, + it does not expand QueryAsterisk objects to field lists but return every + asterisk as-is. + This could be inconvenient when you need just a fully expanded list of fields, + so this method does the work for you. + + If \a options is Unique, each field is returned in the vector only once + (first found field is selected). + Note however, that the same field can be returned more than once if it has attached + a different alias. + For example, let t be TABLE( a, b ) and let query be defined + by "SELECT *, a AS alfa FROM t" statement. Both fieldsExpanded(Default) + and fieldsExpanded(Unique) will return [ a, b, a (alfa) ] list. + On the other hand, for query defined by "SELECT *, a FROM t" statement, + fieldsExpanded(Default) will return [ a, b, a ] list while + fieldsExpanded(Unique) will return [ a, b ] list. + + If \a options is WithInternalFields or WithInternalFieldsAndRowID, + additional internal fields are also appended to the vector. + + If \a options is WithInternalFieldsAndRowID, + one fake BigInteger column is appended to make space for ROWID column used + by KexiDB::Cursor implementations. For example, let persons be TABLE( surname, city_id ), + let city_number reference cities.is in TABLE cities( id, name ) and let query q be defined + by "SELECT * FROM t" statement. If we want to display persons' city names instead of city_id's. + To do this, cities.name has to be retrieved as well, so the following statement should be used: + "SELECT * FROM persons, cities.name LEFT OUTER JOIN cities ON persons.city_id=cities.id". + Thus, calling fieldsExpanded(WithInternalFieldsAndRowID) will return 4 elements instead of 2: + persons.surname, persons.city_id, cities.name, {ROWID}. The {ROWID} item is the placeholder + used for fetching ROWID by KexiDB cursors. + + By default, all fields are returned in the vector even + if there are multiple occurrences of one or more (options == Default). + + Note: You should assign the resulted vector in your space - it will be shared + and implicity copied on any modification. + This method's result is cached by QuerySchema object. +@todo js: UPDATE CACHE! + */ + QueryColumnInfo::Vector fieldsExpanded(FieldsExpandedOptions options = Default); + + /*! \return list of fields internal fields used for lookup columns. */ + QueryColumnInfo::Vector internalFields(); + + /*! \return info for expanded of internal field at index \a index. + The returned field can be either logical or internal (for lookup), + the latter case is true if \a index >= fieldsExpanded().count(). + Equivalent of QuerySchema::fieldsExpanded(WithInternalFields).at(index). */ + QueryColumnInfo* expandedOrInternalField(uint index); + + /*! Options used in columnsOrder(). */ + enum ColumnsOrderOptions { + UnexpandedList, //!< A map for unexpanded list is created + UnexpandedListWithoutAsterisks, //!< A map for unexpanded list is created, with asterisks skipped + ExpandedList //!< A map for expanded list is created + }; + + /*! \return a map for fast lookup of query columns' order. + - If \a options is UnexpandedList, each QueryColumnInfo pointer is mapped to the index + within (unexpanded) list of fields, i.e. "*" or "table.*" asterisks are considered + to be single items. + - If \a options is UnexpandedListWithoutAsterisks, each QueryColumnInfo pointer + is mapped to the index within (unexpanded) list of columns that come from asterisks + like "*" or "table.*" are not included in the map at all. + - If \a options is ExpandedList (the default) this method provides is exactly opposite + information compared to vector returned by fieldsExpanded(). + + This method's result is cached by the QuerySchema object. + Note: indices of internal fields (see internalFields()) are also returned + here - in this case the index is counted as a sum of size(e) + i (where "e" is + the list of expanded fields and i is the column index within internal fields list). + This feature is used eg. at the end of Connection::updateRow() where need indices of + fields (including internal) to update all the values in memory. + + Example use: let t be table (int id, name text, surname text) and q be query + defined by a statement "select * from t". + + - columnsOrder(ExpandedList) will return the following map: QueryColumnInfo(id)->0, + QueryColumnInfo(name)->1, QueryColumnInfo(surname)->2. + - columnsOrder(UnexpandedList) will return the following map: QueryColumnInfo(id)->0, + QueryColumnInfo(name)->0, QueryColumnInfo(surname)->0 because the column + list is not expanded. This way you can use the returned index to get Field* + pointer using field(uint) method of FieldList superclass. + - columnsOrder(UnexpandedListWithoutAsterisks) will return the following map: + QueryColumnInfo(id)->0, + */ + QMap<QueryColumnInfo*,int> columnsOrder(ColumnsOrderOptions options = ExpandedList); + + /*! \return table describing order of primary key (PKEY) fields within the query. + Indexing is performed against vector returned by fieldsExpanded(). + It is usable for e.g. Conenction::updateRow(), when we need + to locate each primary key's field in a constant time. + + Returned vector is owned and cached by QuerySchema object. When you assign it, + it is implicity shared. Its size is equal to number of primary key + fields defined for master table (masterTable()->primaryKey()->fieldCount()). + + Each element of the returned vector: + - can belong to [0..fieldsExpanded().count()-1] if there is such + primary key's field in the fieldsExpanded() list. + - can be equal to -1 if there is no such primary key's field + in the fieldsExpanded() list. + + If there are more than one primary key's field included in the query, + only first-found column (oin the fieldsExpanded() list) for each pkey's field is included. + + Returns empty vector if there is no master table or no master table's pkey. + @see example for pkeyFieldsCount(). +@todo js: UPDATE CACHE! + */ + QValueVector<int> pkeyFieldsOrder(); + + /*! \return number of master table's primary key fields included in this query. + This method is useful to quickly check whether the vector returned by pkeyFieldsOrder() + if filled completely. + + User e.g. in Connection::updateRow() to check if entire primary + key information is specified. + + Examples: let table T has (ID1 INTEGER, ID2 INTEGER, A INTEGER) fields, + and let (ID1, ID2) is T's primary key. + -# The query defined by "SELECT * FROM T" statement contains all T's + primary key's fields as T is the master table, and thus pkeyFieldsCount() + will return 2 (both primary key's fields are in the fieldsExpanded() list), + and pkeyFieldsOrder() will return vector {0, 1}. + -# The query defined by "SELECT A, ID2 FROM T" statement, and thus pkeyFieldsCount() + will return 1 (only one primary key's field is in the fieldsExpanded() list), + and pkeyFieldsOrder() will return vector {-1, 1}, as second primary key's field + is at position #1 and first field is not specified at all within the query. + */ + uint pkeyFieldsCount(); + + /*! \return a list of field infos for all auto-incremented fields + from master table of this query. This result is cached for efficiency. + fieldsExpanded() is used for that. + */ + QueryColumnInfo::List* autoIncrementFields(); + + /*! \return a preset statement (if any). */ + QString statement() const; + + /*! Forces a query statement (i.e. no statement is composed from QuerySchema's content) */ + void setStatement(const QString &s); + + /*! \return a string that is a result of concatenating all column names + for \a infolist, with "," between each one. + This is usable e.g. as argument like "field1,field2" + for "INSERT INTO (xxx) ..". The result of this method is effectively cached, + and it is invalidated when set of fields changes (e.g. using clear() + or addField()). + + This method is similar to FieldList::sqlFieldsList() it just uses + QueryColumnInfo::List instead of Field::List. + */ + static QString sqlColumnsList(QueryColumnInfo::List* infolist, Driver *driver); + + /*! \return cached sql list created using sqlColumnsList() on a list returned + by autoIncrementFields(). */ + QString autoIncrementSQLFieldsList(Driver *driver); + + /*! Sets a WHERE expression \a exp. It will be owned by this query, + so you can forget about it. Previously set WHERE expression will be deleted. + You can pass 0 to remove expresssion. */ + void setWhereExpression(BaseExpr *expr); + + /*! \return WHERE expression or 0 if this query has no WHERE expression */ + BaseExpr *whereExpression() const; + + /*! Adds a part to WHERE expression. + Simplifies creating of WHERE expression, if used instead + of setWhereExpression(BaseExpr *expr). */ + void addToWhereExpression(KexiDB::Field *field, const QVariant& value, int relation = '='); + + /*! Sets a list of columns for ORDER BY section of the query. + Each name on the list must be a field or alias present within the query + and must not be covered by aliases. If one or more names cannot be found + within the query, the method will have no effect. + Any previous ORDER BY settings will be removed. + + Note that this information is cleared whenever you call methods that + modify list of columns (QueryColumnInfo), i.e. insertFiled(), + addField(), removeField(), addExpression(), etc. + (because OrderByColumn items can point to a QueryColumnInfo that's removed by these + methods), so you should use setOrderByColumnList() method after the query + is completely built. */ + void setOrderByColumnList(const OrderByColumnList& list); + + /*! \return a list of columns listed in ORDER BY section of the query. + Read notes for \ref setOrderByColumnList(). */ + OrderByColumnList& orderByColumnList() const; + + /*! \return query schema parameters. These are taked from the WHERE section + (a tree of expression items). */ + QuerySchemaParameterList parameters(); + + protected: + void init(); + + void computeFieldsExpanded(); + + QuerySchemaPrivate *d; + + friend class Connection; + friend class QuerySchemaPrivate; +}; + +//! @short KexiDB::QueryAsterisk class encapsulates information about single asterisk in query definition +/*! There are two types of query asterisks: + + 1. "Single-table" asterisk, that references all fields of given table used + in the query. + Example SQL statement: + \code + SELECT staff.*, cars.model from staff, cars WHERE staff.car = cars.number; + \endcode + The "staff.*" element is our "single-table" asterisk; + this tells us that we want to get all fields of table "staff". + + 2. "All-tables" asterisk, that references all fields of all tables used in the query. + Example SQL statement: + \code + SELECT * from staff, cars WHERE staff.car = cars.number; + \endcode + The "*" is our "all-tables" asterisk; + this tells us that we want to get all fields of all used tables (here: "staff" and "cars"). + + There can be many asterisks of 1st type defined for given single query. + There can be one asterisk of 2nd type defined for given single query. +*/ +class KEXI_DB_EXPORT QueryAsterisk : public Field +{ + public: + /*! Constructs query asterisk definition object. + Pass table schema to \a table if this asterisk should be + of type "single-table", otherwise (if you want to define + "all-tables" type asterisk), omit this parameter. + + QueryAsterisk objects are owned by QuerySchema object + (not by TableSchema object like for ordinary Field objects) + for that the QueryAsterisk object was added (using QuerySchema::addField()). + */ + QueryAsterisk( QuerySchema *query, TableSchema *table = 0 ); + + virtual ~QueryAsterisk(); + + /*! \return Query object for that this asterisk object is defined */ + QuerySchema *query() const { return static_cast<QuerySchema*>(m_parent); } + + /*! \return Table schema for this asterisk + if it has "single-table" type (1st type) + or NULL if it has "all-tables" type (2nd type) defined. */ + virtual TableSchema* table() const { return m_table; } + + /*! Sets table schema for this asterisk. + \a table may be NULL - then the asterisk becames "all-tables" type asterisk. */ + virtual void setTable(TableSchema *table); + + /*! Reimplemented. */ + virtual bool isQueryAsterisk() const { return true; } + + /*! This is convenience method that returns true + if the asterisk has "all-tables" type (2nd type).*/ + bool isSingleTableAsterisk() const { return m_table!=NULL; } + + /*! This is convenience method that returns true + if the asterisk has "single-tables" type (2nd type).*/ + bool isAllTableAsterisk() const { return m_table==NULL; } + + /*! \return String for debugging purposes. */ + virtual QString debugString() const; + + protected: + //! \return a deep copy of this object. Used in FieldList(const FieldList& fl). + virtual Field* copy() const; + + /*! Table schema for this asterisk */ + TableSchema* m_table; + + friend class QuerySchema; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/queryschemaparameter.cpp b/kexi/kexidb/queryschemaparameter.cpp new file mode 100644 index 00000000..3703de24 --- /dev/null +++ b/kexi/kexidb/queryschemaparameter.cpp @@ -0,0 +1,103 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "queryschemaparameter.h" +#include "driver.h" + +#include <kdebug.h> +#include <qguardedptr.h> + +using namespace KexiDB; + +QuerySchemaParameter::QuerySchemaParameter() + : type(Field::InvalidType) +{ +} + +QuerySchemaParameter::~QuerySchemaParameter() +{ +} + +QString QuerySchemaParameter::debugString() const +{ + return QString("msg=\"%1\" type=\"%2\"").arg(Field::typeName(type)).arg(message); +} + +void KexiDB::debug(const QuerySchemaParameterList& list) +{ + KexiDBDbg << QString("Query parameters (%1):").arg(list.count()) << endl; + foreach(QuerySchemaParameterListConstIterator, it, list) + KexiDBDbg << " - " << (*it).debugString() << endl; +} + +//================================================ + +class QuerySchemaParameterValueListIterator::Private +{ + public: + Private(const Driver& aDriver, const QValueList<QVariant>& aParams) + : driver(&aDriver) + , params(aParams) + { + //move to last item, as the order is reversed due to parser's internals + paramsIt = params.fromLast(); //constBegin(); + paramsItPosition = params.count(); + } + QGuardedPtr<const Driver> driver; + const QValueList<QVariant> params; + QValueList<QVariant>::ConstIterator paramsIt; + uint paramsItPosition; +}; + +QuerySchemaParameterValueListIterator::QuerySchemaParameterValueListIterator( + const Driver& driver, const QValueList<QVariant>& params) + : d( new Private(driver, params) ) +{ +} + +QuerySchemaParameterValueListIterator::~QuerySchemaParameterValueListIterator() +{ + delete d; +} + +QVariant QuerySchemaParameterValueListIterator::getPreviousValue() +{ + if (d->paramsItPosition == 0) { //d->params.constEnd()) { + KexiDBWarn << "QuerySchemaParameterValues::getPreviousValue() no prev value" << endl; + return QVariant(); + } + QVariant res( *d->paramsIt ); + --d->paramsItPosition; + --d->paramsIt; +// ++d->paramsIt; + return res; +} + +QString QuerySchemaParameterValueListIterator::getPreviousValueAsString(Field::Type type) +{ + if (d->paramsItPosition == 0) { //d->params.constEnd()) { + KexiDBWarn << "QuerySchemaParameterValues::getPreviousValueAsString() no prev value" << endl; + return d->driver->valueToSQL(type, QVariant()); //"NULL" + } + QString res( d->driver->valueToSQL(type, *d->paramsIt) ); + --d->paramsItPosition; + --d->paramsIt; +// ++d->paramsIt; + return res; +} diff --git a/kexi/kexidb/queryschemaparameter.h b/kexi/kexidb/queryschemaparameter.h new file mode 100644 index 00000000..e7c00880 --- /dev/null +++ b/kexi/kexidb/queryschemaparameter.h @@ -0,0 +1,69 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_QUERYSCHEMAPARAMETER_H +#define KEXIDB_QUERYSCHEMAPARAMETER_H + +#include "queryschema.h" + +namespace KexiDB +{ + +//! @short A single parameter of a query schema +class KEXI_DB_EXPORT QuerySchemaParameter +{ + public: + QuerySchemaParameter(); + ~QuerySchemaParameter(); + + QString debugString() const; + + Field::Type type; //!< A datatype of the parameter + QString message; //!< A user-visible message that will be displayed to ask for value of the parameter +}; + +typedef QValueList<QuerySchemaParameter> QuerySchemaParameterList; +typedef QValueList<QuerySchemaParameter>::Iterator QuerySchemaParameterListIterator; +typedef QValueList<QuerySchemaParameter>::ConstIterator QuerySchemaParameterListConstIterator; + +//! Shows debug information for \a list +KEXI_DB_EXPORT void debug(const QuerySchemaParameterList& list); + +//! @short An iteratof for a list of values of query schema parameters providing +//! Allows to iterate over parameters and return QVariant value or well-formatted string. +//! The iterator is initially set to the last item because of the parser requirements +class KEXI_DB_EXPORT QuerySchemaParameterValueListIterator +{ + public: + QuerySchemaParameterValueListIterator(const Driver& driver, const QValueList<QVariant>& params); + ~QuerySchemaParameterValueListIterator(); + + //! \return previous value + QVariant getPreviousValue(); + + //! \return previous value as string formatted using driver's escaping + QString getPreviousValueAsString(Field::Type type); + protected: + class Private; + Private * const d; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/record.h b/kexi/kexidb/record.h new file mode 100644 index 00000000..29f3d670 --- /dev/null +++ b/kexi/kexidb/record.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_RECORD_H +#define KEXIDB_RECORD_H + +#include <qvaluelist.h> +#include <qstring.h> + +#include <kexidb/field.h> + +namespace KexiDB { + +/*! KexiDB::Record provides single database record. +*/ + +class KEXI_DB_EXPORT Record { +public: + Record(const QString & name); + +//TODO............. + Table(); + ~Table(); + const QString& name() const; + void setName(const QString& name); + unsigned int fieldCount() const; + KexiDB::Field field(unsigned int id) const; + QStringList primaryKeys() const; + bool hasPrimaryKeys() const; +//js void addField(KexiDB::Field field); +//js void addPrimaryKey(const QString& key); +private: +//js QStringList m_primaryKeys; + QValueList<Field> m_fields; + QString m_name; + Connection* m_conn; +}; + + +/* +class KexiDBTableFields: public QValueList<KexiDBField> { +public: + KexiDBTable(const QString & name); + ~KexiDBTable(); + void addField(KexiDBField); +// const QString& tableName() const; + +private: +// QString m_tableName; +}; +*/ + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/relationship.cpp b/kexi/kexidb/relationship.cpp new file mode 100644 index 00000000..a7796207 --- /dev/null +++ b/kexi/kexidb/relationship.cpp @@ -0,0 +1,201 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/relationship.h> + +#include <kexidb/indexschema.h> +#include <kexidb/tableschema.h> +#include <kexidb/queryschema.h> +#include <kexidb/driver.h> + +#include <kdebug.h> + +using namespace KexiDB; + +Relationship::Relationship() + : m_masterIndex(0) + , m_detailsIndex(0) + , m_masterIndexOwned(false) + , m_detailsIndexOwned(false) +{ + m_pairs.setAutoDelete(true); +} + +Relationship::Relationship(IndexSchema* masterIndex, IndexSchema* detailsIndex) + : m_masterIndex(0) + , m_detailsIndex(0) + , m_masterIndexOwned(false) + , m_detailsIndexOwned(false) +{ + m_pairs.setAutoDelete(true); + setIndices(masterIndex, detailsIndex); +} + +Relationship::Relationship( QuerySchema *query, Field *field1, Field *field2 ) + : m_masterIndex(0) + , m_detailsIndex(0) + , m_masterIndexOwned(false) + , m_detailsIndexOwned(false) +{ + m_pairs.setAutoDelete(true); + createIndices( query, field1, field2 ); +} + +Relationship::~Relationship() +{ + if (m_masterIndexOwned) + delete m_masterIndex; + if (m_detailsIndexOwned) + delete m_detailsIndex; +} + +void Relationship::createIndices( QuerySchema *query, Field *field1, Field *field2 ) +{ + if (!field1 || !field2 || !query) { + KexiDBWarn << "Relationship::addRelationship(): !masterField || !detailsField || !query" << endl; + return; + } + if (field1->isQueryAsterisk() || field2->isQueryAsterisk()) { + KexiDBWarn << "Relationship::addRelationship(): relationship's fields cannot be asterisks" << endl; + return; + } + if (field1->table() == field2->table()) { + KexiDBWarn << "Relationship::addRelationship(): fields cannot belong to the same table" << endl; + return; + } +// if (!query->hasField(field1) && !query->hasField(field2)) { + if (!query->contains(field1->table()) || !query->contains(field2->table())) { + KexiDBWarn << "Relationship::addRelationship(): fields do not belong to this query" << endl; + return; + } +//@todo: check more things: -types +//@todo: find existing global db relationships + + Field *masterField = 0, *detailsField = 0; + bool p1 = field1->isPrimaryKey(), p2 = field2->isPrimaryKey(); + if (p1 && p2) { + //2 primary keys + masterField = field1; + m_masterIndex = masterField->table()->primaryKey(); + detailsField = field2; + m_detailsIndex = detailsField->table()->primaryKey(); + } + else if (!p1 && p2) { + //foreign + primary: swap + Field *tmp = field1; + field1 = field2; + field2 = tmp; + p1 = !p1; + p2 = !p2; + } + + if (p1 && !p2) { + //primary + foreign + masterField = field1; + m_masterIndex = masterField->table()->primaryKey(); + detailsField = field2; + //create foreign key +//@todo: check if it already exists + m_detailsIndex = new IndexSchema(detailsField->table()); + m_detailsIndexOwned = true; + m_detailsIndex->addField(detailsField); + m_detailsIndex->setForeignKey(true); + } + else if (!p1 && !p2) { + masterField = field1; + m_masterIndex = new IndexSchema(masterField->table()); + m_masterIndexOwned = true; + m_masterIndex->addField(masterField); + m_masterIndex->setForeignKey(true); + + detailsField = field2; + m_detailsIndex = new IndexSchema(detailsField->table()); + m_detailsIndexOwned = true; + m_detailsIndex->addField(detailsField); + m_detailsIndex->setForeignKey(true); + } + + if (!m_masterIndex || !m_detailsIndex) + return; //failed + + setIndices(m_masterIndex, m_detailsIndex, false); +} + +TableSchema* Relationship::masterTable() const +{ + return m_masterIndex ? m_masterIndex->table() : 0; +} + +TableSchema* Relationship::detailsTable() const +{ + return m_detailsIndex ? m_detailsIndex->table() : 0; +} + +void Relationship::setIndices(IndexSchema* masterIndex, IndexSchema* detailsIndex) +{ + setIndices(masterIndex, detailsIndex, true); +} + +void Relationship::setIndices(IndexSchema* masterIndex, IndexSchema* detailsIndex, bool ownedByMaster) +{ + m_masterIndex = 0; + m_detailsIndex = 0; + m_pairs.clear(); + if (!masterIndex || !detailsIndex || !masterIndex->table() || !detailsIndex->table() + || masterIndex->table()==detailsIndex->table() || masterIndex->fieldCount()!=detailsIndex->fieldCount()) + return; + Field::ListIterator it1(*masterIndex->fields()); + Field::ListIterator it2(*detailsIndex->fields()); + for (;it1.current() && it1.current(); ++it1, ++it2) { + Field *f1 = it1.current(); //masterIndex->fields()->first(); + Field *f2 = it2.current(); //detailsIndex->fields()->first(); + // while (f1 && f2) { + if (f1->type()!=f1->type() && f1->isIntegerType()!=f2->isIntegerType() && f1->isTextType()!=f2->isTextType()) { + KexiDBWarn << "Relationship::setIndices(INDEX on '"<<masterIndex->table()->name() + <<"',INDEX on "<<detailsIndex->table()->name()<<"): !equal field types: " + <<Driver::defaultSQLTypeName(f1->type())<<" "<<f1->name()<<", " + <<Driver::defaultSQLTypeName(f2->type())<<" "<<f2->name() <<endl; + m_pairs.clear(); + return; + } +#if 0 //too STRICT! + if ((f1->isUnsigned() && !f2->isUnsigned()) || (!f1->isUnsigned() && f1->isUnsigned())) { + KexiDBWarn << "Relationship::setIndices(INDEX on '"<<masterIndex->table()->name() + <<"',INDEX on "<<detailsIndex->table()->name()<<"): !equal signedness of field types: " + <<Driver::defaultSQLTypeName(f1->type())<<" "<<f1->name()<<", " + <<Driver::defaultSQLTypeName(f2->type())<<" "<<f2->name() <<endl; + m_pairs.clear(); + return; + } +#endif + m_pairs.append( new Field::Pair(f1,f2) ); + } + //ok: update information + if (m_masterIndex) {//detach yourself + m_masterIndex->detachRelationship(this); + } + if (m_detailsIndex) {//detach yourself + m_detailsIndex->detachRelationship(this); + } + m_masterIndex = masterIndex; + m_detailsIndex = detailsIndex; + m_masterIndex->attachRelationship(this, ownedByMaster); + m_detailsIndex->attachRelationship(this, ownedByMaster); +} + diff --git a/kexi/kexidb/relationship.h b/kexi/kexidb/relationship.h new file mode 100644 index 00000000..b72c7209 --- /dev/null +++ b/kexi/kexidb/relationship.h @@ -0,0 +1,156 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2004 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIDB_RELATIONSHIP_H +#define KEXIDB_RELATIONSHIP_H + +#include <kexidb/field.h> + +namespace KexiDB { + +/*! KexiDB::Relationship provides information about one-to-many relationship between two tables. + Relationship is defined by a pair of (potentially multi-field) indices: + - "one" or "master" side: unique key + - "many" or "details" side: referenced foreign key + <pre> + [unique key, master] ----< [foreign key, details] + </pre> + + In this documentation, we will call table that owns fields of "one" side as + "master side of the relationship", and the table that owns foreign key fields of + as "details side of the relationship". + Use masterTable(), and detailsTable() to get one-side table and many-side table, respectively. + + Note: some engines (e.g. MySQL with InnoDB) requires that indices at both sides + have to be explicitly created. + + \todo (js) It is planned that this will be handled by KexiDB internally and transparently. + + Each (of the two) key can be defined (just like index) as list of fields owned by one table. + Indeed, relationship info can retrieved from Relationship object in two ways: + -# pair of indices; use masterIndex(), detailsIndex() for that + -# ordered list of field pairs (<master-side-field, details-side-field>); use fieldPairs() for that + + No assigned objects (like fields, indices) are owned by Relationship object. The exception is that + list of field-pairs is internally created (on demand) and owned. + + Relationship object is owned by IndexSchema object (the one that is defined at master-side of the + relationship). + Note also that IndexSchema objects are owned by appropriate tables, so thus + there is implicit ownership between TableSchema and Relationship. + + If Relationship object is not attached to IndexSchema object, + you should care about destroying it by hand. + + Example: + <pre> + ---------- + ---r1--<| | + | Table A [uk]----r3---< + ---r2--<| | + ---------- + </pre> + Table A has two relationships (r1, r2) at details side and one (r3) at master side. + [uk] stands for unique key. +*/ + +class IndexSchema; +class TableSchema; +class QuerySchema; + +class KEXI_DB_EXPORT Relationship +{ + public: + typedef QPtrList<Relationship> List; + typedef QPtrListIterator<Relationship> ListIterator; + + /*! Creates uninitialized Relationship object. + setIndices() will be required to call. + */ + Relationship(); + + /*! Creates Relationship object and initialises it just by + calling setIndices(). If setIndices() failed, object is still uninitialised. + */ + Relationship(IndexSchema* masterIndex, IndexSchema* detailsIndex); + + virtual ~Relationship(); + + /*! \return index defining master side of this relationship + or null if there is no information defined. */ + IndexSchema* masterIndex() const { return m_masterIndex; } + + /*! \return index defining referenced side of this relationship. + or null if there is no information defined. */ + IndexSchema* detailsIndex() const { return m_detailsIndex; } + + /*! \return ordered list of field pairs -- alternative form + for representation of relationship or null if there is no information defined. + Each pair has a form of <master-side-field, details-side-field>. */ + Field::PairList* fieldPairs() { return &m_pairs; } + + bool isEmpty() const { return m_pairs.isEmpty(); } + + /*! \return table assigned at "master / one" side of this relationship. + or null if there is no information defined. */ + TableSchema* masterTable() const; + + /*! \return table assigned at "details / many / foreign" side of this relationship. + or null if there is no information defined. */ + TableSchema* detailsTable() const; + + /*! Sets \a masterIndex and \a detailsIndex indices for this relationship. + This also sets information about tables for master- and details- sides. + Notes: + - both indices must contain the same number of fields + - both indices must not be owned by the same table, and table (owner) must be not null. + - corresponding field types must be the same + - corresponding field types' signedness must be the same + If above rules are not fulfilled, information about this relationship is cleared. + On success, this Relationship object is detached from previous IndexSchema objects that were + assigned before, and new are attached. + */ + void setIndices(IndexSchema* masterIndex, IndexSchema* detailsIndex); + + protected: + Relationship( QuerySchema *query, Field *field1, Field *field2 ); + + void createIndices( QuerySchema *query, Field *field1, Field *field2 ); + + /*! Internal version of setIndices(). \a ownedByMaster parameter is passed + to IndexSchema::attachRelationship() */ + void setIndices(IndexSchema* masterIndex, IndexSchema* detailsIndex, bool ownedByMaster); + + IndexSchema *m_masterIndex; + IndexSchema *m_detailsIndex; + + Field::PairList m_pairs; + + bool m_masterIndexOwned : 1; + bool m_detailsIndexOwned : 1; + + friend class Connection; + friend class TableSchema; + friend class QuerySchema; + friend class IndexSchema; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/roweditbuffer.cpp b/kexi/kexidb/roweditbuffer.cpp new file mode 100644 index 00000000..7b5b5711 --- /dev/null +++ b/kexi/kexidb/roweditbuffer.cpp @@ -0,0 +1,129 @@ +/* This file is part of the KDE project + Copyright (C) 2003,2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "roweditbuffer.h" +#include "utils.h" + +#include <kdebug.h> + +using namespace KexiDB; + + +RowEditBuffer::RowEditBuffer(bool dbAwareBuffer) +: m_simpleBuffer(dbAwareBuffer ? 0 : new SimpleMap()) +, m_simpleBufferIt(dbAwareBuffer ? 0 : new SimpleMap::ConstIterator()) +, m_dbBuffer(dbAwareBuffer ? new DBMap() : 0) +, m_dbBufferIt(dbAwareBuffer ? new DBMap::Iterator() : 0) +, m_defaultValuesDbBuffer(dbAwareBuffer ? new QMap<QueryColumnInfo*,bool>() : 0) +, m_defaultValuesDbBufferIt(dbAwareBuffer ? new QMap<QueryColumnInfo*,bool>::ConstIterator() : 0) +{ +} + +RowEditBuffer::~RowEditBuffer() +{ + delete m_simpleBuffer; + delete m_simpleBufferIt; + delete m_dbBuffer; + delete m_defaultValuesDbBuffer; + delete m_dbBufferIt; +} + +const QVariant* RowEditBuffer::at( QueryColumnInfo& ci, bool useDefaultValueIfPossible ) const +{ + if (!m_dbBuffer) { + KexiDBWarn << "RowEditBuffer::at(QueryColumnInfo&): not db-aware buffer!" << endl; + return 0; + } + *m_dbBufferIt = m_dbBuffer->find( &ci ); + QVariant* result = 0; + if (*m_dbBufferIt!=m_dbBuffer->end()) + result = &(*m_dbBufferIt).data(); + if ( useDefaultValueIfPossible + && (!result || result->isNull()) + && ci.field && !ci.field->defaultValue().isNull() && KexiDB::isDefaultValueAllowed(ci.field) + && !hasDefaultValueAt(ci) ) + { + //no buffered or stored value: try to get a default value declared in a field, so user can modify it + if (!result) + m_dbBuffer->insert(&ci, ci.field->defaultValue() ); + result = &(*m_dbBuffer)[ &ci ]; + m_defaultValuesDbBuffer->insert(&ci, true); + } + return (const QVariant*)result; +} + +const QVariant* RowEditBuffer::at( Field& f ) const +{ + if (!m_simpleBuffer) { + KexiDBWarn << "RowEditBuffer::at(Field&): this is db-aware buffer!" << endl; + return 0; + } + *m_simpleBufferIt = m_simpleBuffer->find( f.name() ); + if (*m_simpleBufferIt==m_simpleBuffer->constEnd()) + return 0; + return &(*m_simpleBufferIt).data(); +} + +const QVariant* RowEditBuffer::at( const QString& fname ) const +{ + if (!m_simpleBuffer) { + KexiDBWarn << "RowEditBuffer::at(Field&): this is db-aware buffer!" << endl; + return 0; + } + *m_simpleBufferIt = m_simpleBuffer->find( fname ); + if (*m_simpleBufferIt==m_simpleBuffer->constEnd()) + return 0; + return &(*m_simpleBufferIt).data(); +} + +void RowEditBuffer::clear() { + if (m_dbBuffer) { + m_dbBuffer->clear(); + m_defaultValuesDbBuffer->clear(); + } + if (m_simpleBuffer) + m_simpleBuffer->clear(); +} + +bool RowEditBuffer::isEmpty() const +{ + if (m_dbBuffer) + return m_dbBuffer->isEmpty(); + if (m_simpleBuffer) + return m_simpleBuffer->isEmpty(); + return true; +} + +void RowEditBuffer::debug() +{ + if (isDBAware()) { + KexiDBDbg << "RowEditBuffer type=DB-AWARE, " << m_dbBuffer->count() <<" items"<< endl; + for (DBMap::ConstIterator it = m_dbBuffer->constBegin(); it!=m_dbBuffer->constEnd(); ++it) { + KexiDBDbg << "* field name=" <<it.key()->field->name()<<" val=" + << (it.data().isNull() ? QString("<NULL>") : it.data().toString()) + << (hasDefaultValueAt(*it.key()) ? " DEFAULT" : "") <<endl; + } + return; + } + KexiDBDbg << "RowEditBuffer type=SIMPLE, " << m_simpleBuffer->count() <<" items"<< endl; + for (SimpleMap::ConstIterator it = m_simpleBuffer->constBegin(); it!=m_simpleBuffer->constEnd(); ++it) { + KexiDBDbg << "* field name=" <<it.key()<<" val=" + << (it.data().isNull() ? QString("<NULL>") : it.data().toString()) <<endl; + } +} diff --git a/kexi/kexidb/roweditbuffer.h b/kexi/kexidb/roweditbuffer.h new file mode 100644 index 00000000..edf48206 --- /dev/null +++ b/kexi/kexidb/roweditbuffer.h @@ -0,0 +1,136 @@ +/* This file is part of the KDE project + Copyright (C) 2003, 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_ROWEDITBUFFER_H +#define KEXIDB_ROWEDITBUFFER_H + +#include <qmap.h> + +#include <kexidb/field.h> +#include <kexidb/queryschema.h> + +namespace KexiDB { + +/*! @short provides data for single edited database row + KexiDB::RowEditBuffer provides data for single edited row, + needed to perform update at the database backend. + Its advantage over pasing e.g. KexiDB::FieldList object is that + EditBuffer contains only changed values. + + EditBuffer offers two modes: db-aware and not-db-aware. + Db-aware buffer addresses a field using references to QueryColumnInfo object, + while not-db-aware buffer addresses a field using its name. + + Example usage of not-db-aware buffer: + <code> + QuerySchema *query = ..... + EditBuffer buf; + buf.insert("name", "Joe"); + buf.insert("surname", "Black"); + buf.at("name"); //returns "Joe" + buf.at("surname"); //returns "Black" + buf.at(query->field("surname")); //returns "Black" too + // Now you can use buf to add or edit records using + // KexiDB::Connection::updateRow(), KexiDB::Connection::insertRow() + </code> + + Example usage of db-aware buffer: + <code> + QuerySchema *query = ..... + QueryColumnInfo *ci1 = ....... //e.g. can be obtained from QueryScehma::fieldsExpanded() + QueryColumnInfo *ci2 = ....... + EditBuffer buf; + buf.insert(*ci1, "Joe"); + buf.insert(*ci2, "Black"); + buf.at(*ci1); //returns "Joe" + buf.at(*ci2); //returns "Black" + // Now you can use buf to add or edit records using + // KexiDB::Connection::updateRow(), KexiDB::Connection::insertRow() + </code> + + You can use QMap::clear() to clear buffer contents, + QMap::isEmpty() to see if buffer is empty. + For more, see QMap documentation. + + Notes: added fields should come from the same (common) QuerySchema object. + However, this isn't checked at QValue& EditBuffer::operator[]( const Field& f ) level. +*/ +class KEXI_DB_EXPORT RowEditBuffer { +public: + typedef QMap<QString,QVariant> SimpleMap; + typedef QMap<QueryColumnInfo*,QVariant> DBMap; + + RowEditBuffer(bool dbAwareBuffer); + ~RowEditBuffer(); + + inline bool isDBAware() const { return m_dbBuffer!=0; } + + void clear(); + + bool isEmpty() const; + + //! Inserts value \a val for db-aware buffer's column \a ci + inline void insert( QueryColumnInfo& ci, QVariant &val ) { + if (m_dbBuffer) { + m_dbBuffer->insert(&ci, val); + m_defaultValuesDbBuffer->remove(&ci); + } + } + + //! Inserts value \a val for not-db-aware buffer's column \a fname + inline void insert( const QString& fname, QVariant &val ) + { if (m_simpleBuffer) m_simpleBuffer->insert(fname,val); } + + /*! Useful only for db-aware buffer. \return value for column \a ci + If there is no value assigned for the buffer, this method tries to remember and return + default value obtained from \a ci if \a useDefaultValueIfPossible is true. + Note that if the column is declared as unique (especially: primary key), + default value will not be used. */ + const QVariant* at( QueryColumnInfo& ci, bool useDefaultValueIfPossible = true ) const; + + //! Useful only for not-db-aware buffer. \return value for field \a f + const QVariant* at( Field& f ) const; + + //! Useful only for not-db-aware buffer. \return value for field \a fname + const QVariant* at( const QString& fname ) const; + + //! Useful only for db-aware buffer: \return true if the value available as + //! at( ci ) is obtained from column's default value + inline bool hasDefaultValueAt( QueryColumnInfo& ci ) const { + return m_defaultValuesDbBuffer->contains(&ci) && (*m_defaultValuesDbBuffer)[ &ci ]; + } + + inline const SimpleMap simpleBuffer() { return *m_simpleBuffer; } + inline const DBMap dbBuffer() { return *m_dbBuffer; } + + //! For debugging purposes + void debug(); + +protected: + SimpleMap *m_simpleBuffer; + SimpleMap::ConstIterator *m_simpleBufferIt; + DBMap *m_dbBuffer; + DBMap::Iterator *m_dbBufferIt; + QMap<QueryColumnInfo*,bool> *m_defaultValuesDbBuffer; + QMap<QueryColumnInfo*,bool>::ConstIterator *m_defaultValuesDbBufferIt; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/schemadata.cpp b/kexi/kexidb/schemadata.cpp new file mode 100644 index 00000000..0a0c2124 --- /dev/null +++ b/kexi/kexidb/schemadata.cpp @@ -0,0 +1,55 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/schemadata.h> +#include <kexidb/connection.h> + +#include <kdebug.h> + +using namespace KexiDB; + +SchemaData::SchemaData(int obj_type) + : m_type(obj_type) + , m_id(-1) + , m_native(false) +{ +} + +SchemaData::~SchemaData() +{ +} + +void SchemaData::clear() +{ + m_id = -1; + m_name = QString::null; + m_caption = QString::null; + m_desc = QString::null; +} + +QString SchemaData::schemaDataDebugString() const +{ + QString desc = m_desc; + if (desc.length()>40) { + desc.truncate(40); + desc+="..."; + } + return QString("id=%1 name='%2' caption='%3' desc='%4'") + .arg(m_id).arg(m_name).arg(m_caption).arg(desc); +} diff --git a/kexi/kexidb/schemadata.h b/kexi/kexidb/schemadata.h new file mode 100644 index 00000000..615f8602 --- /dev/null +++ b/kexi/kexidb/schemadata.h @@ -0,0 +1,92 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_SCHEMADATA_H +#define KEXIDB_SCHEMADATA_H + +#include <qvaluelist.h> +#include <qstring.h> + +#include <kexidb/global.h> +#include <kexidb/field.h> + +namespace KexiDB { + +/*! Container class that stores common kexi object schema's properties like + id, name, caption, help text. + By kexi object we mean in-db storable object like table schema or query schema. +*/ + +class KEXI_DB_EXPORT SchemaData +{ + public: + SchemaData(int obj_type = KexiDB::UnknownObjectType); + virtual ~SchemaData(); + + int type() const { return m_type; } + int id() const { return m_id; } + QString name() const { return m_name; } + /*! The same as name(). Added to avoid conflict with QObject::name() */ + QString objectName() const { return m_name; } + void setName(const QString& n) { m_name=n; } + QString caption() const { return m_caption; } + void setCaption(const QString& c) { m_caption=c; } + QString description() const { return m_desc; } + void setDescription(const QString& desc) { m_desc=desc; } + + /*! \return debug string useful for debugging */ + virtual QString schemaDataDebugString() const; + + /*! \return true if this is schema of native database object, + like, for example like, native table. This flag + is set when object schema (currently -- database table) + is not retrieved using kexi__* schema storage system, + but just based on the information about native table. + + By native object we mean the one that has no additional + data like caption, description, etc. properties (no kexidb extensions). + + Native objects schemas are used mostly for representing + kexi system (kexi__*) tables in memory for later reference; + see Connection::tableNames(). + + By default (on allocation) SchemaData objects are not native. + */ + virtual bool isNative() const { return m_native; } + + /* Sets native flag */ + virtual void setNative(bool set) { m_native=set; } + + protected: + //! Clears all properties except 'type'. + void clear(); + + int m_type; + int m_id; + QString m_name; + QString m_caption; + QString m_desc; + bool m_native : 1; + + friend class Connection; +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/simplecommandlineapp.cpp b/kexi/kexidb/simplecommandlineapp.cpp new file mode 100644 index 00000000..ec73cde2 --- /dev/null +++ b/kexi/kexidb/simplecommandlineapp.cpp @@ -0,0 +1,228 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "simplecommandlineapp.h" + +#include <qfileinfo.h> + +#include <kcmdlineargs.h> +#include <kdebug.h> + +#include <kexidb/connectiondata.h> +#include <kexidb/drivermanager.h> + +using namespace KexiDB; + +static KCmdLineOptions predefinedOptions[] = +{ + { "drv", 0, 0 }, + { "driver <name>", I18N_NOOP("Database driver name"), 0 }, + { "u", 0, 0 }, + { "user <name>", I18N_NOOP("Database user name"), 0 }, + { "p", 0, 0 }, + { "password", I18N_NOOP("Prompt for password"), 0 }, + { "h", 0, 0 }, + { "host <name>", I18N_NOOP("Host (server) name"), 0 }, + { "port <number>", I18N_NOOP("Server's port number"), 0 }, + { "s", 0, 0 }, + { "local-socket <filename>", I18N_NOOP("Server's local socket filename"), 0 }, + KCmdLineLastOption +}; + +//----------------------------------------- + +//! @internal used for SimpleCommandLineApp +class SimpleCommandLineApp::Private +{ +public: + Private() + : conn(0) + {} + ~Private() + { + if (conn) { + conn->disconnect(); + delete (Connection*)conn; + } + delete instance; + + for (KCmdLineOptions *optionsPtr = allOptions; optionsPtr->name; optionsPtr++) { + delete optionsPtr->name; + delete optionsPtr->description; + delete optionsPtr->def; + } + delete allOptions; + } + + KexiDB::DriverManager manager; + KCmdLineOptions *allOptions; + KInstance* instance; + ConnectionData connData; + QGuardedPtr<Connection> conn; +}; + +//----------------------------------------- + +SimpleCommandLineApp::SimpleCommandLineApp( + int argc, char** argv, KCmdLineOptions *options, + const char *programName, const char *version, + const char *shortDescription, int licenseType, + const char *copyrightStatement, const char *text, + const char *homePageAddress, const char *bugsEmailAddress) + : Object() + , d( new Private() ) +{ + QFileInfo fi(argv[0]); + QCString appName( fi.baseName().latin1() ); + KCmdLineArgs::init(argc, argv, + new KAboutData( appName, programName, + version, shortDescription, licenseType, copyrightStatement, text, + homePageAddress, bugsEmailAddress)); + + int predefinedOptionsCount = 0; + for (KCmdLineOptions *optionsPtr = predefinedOptions; optionsPtr->name; optionsPtr++, predefinedOptionsCount++) + ; + int userOptionsCount = 0; + for (KCmdLineOptions *optionsPtr = options; optionsPtr->name; optionsPtr++, userOptionsCount++) + ; + + d->instance = new KInstance(appName); + + // join the predefined options and user options + d->allOptions = new KCmdLineOptions[predefinedOptionsCount + userOptionsCount + 1]; + KCmdLineOptions *allOptionsPtr = d->allOptions; + for (KCmdLineOptions *optionsPtr = predefinedOptions; optionsPtr->name; optionsPtr++, allOptionsPtr++) { + allOptionsPtr->name = qstrdup(optionsPtr->name); + allOptionsPtr->description = qstrdup(optionsPtr->description); + if (optionsPtr == predefinedOptions) //first row == drv + allOptionsPtr->def = qstrdup(KexiDB::Driver::defaultFileBasedDriverName().latin1()); + else + allOptionsPtr->def = qstrdup(optionsPtr->def); + } + for (KCmdLineOptions *optionsPtr = options; optionsPtr->name; optionsPtr++, allOptionsPtr++) { + allOptionsPtr->name = qstrdup(optionsPtr->name); + allOptionsPtr->description = qstrdup(optionsPtr->description); + allOptionsPtr->def = qstrdup(optionsPtr->def); + } + allOptionsPtr->name = 0; //end + allOptionsPtr->description = 0; + allOptionsPtr->def = 0; + KCmdLineArgs::addCmdLineOptions( d->allOptions ); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + d->connData.driverName = args->getOption("driver"); + d->connData.userName = args->getOption("user"); + d->connData.hostName = args->getOption("host"); + d->connData.localSocketFileName = args->getOption("local-socket"); + d->connData.port = args->getOption("port").toInt(); + d->connData.useLocalSocketFile = args->isSet("local-socket"); + + if (args->isSet("password")) { + QString userAtHost = d->connData.userName; + if (!d->connData.userName.isEmpty()) + userAtHost += "@"; + userAtHost += (d->connData.hostName.isEmpty() ? "localhost" : d->connData.hostName); + QTextStream cout(stdout,IO_WriteOnly); + cout << i18n("Enter password for %1: ").arg(userAtHost); +//! @todo make use of pty/tty here! (and care about portability) + QTextStream cin(stdin,IO_ReadOnly); + cin >> d->connData.password; + KexiDBDbg << d->connData.password << endl; + } +} + +SimpleCommandLineApp::~SimpleCommandLineApp() +{ + closeDatabase(); + delete d; +} + +bool SimpleCommandLineApp::openDatabase(const QString& databaseName) +{ + if (!d->conn) { + if (d->manager.error()) { + setError(&d->manager); + return false; + } + + //get the driver + KexiDB::Driver *driver = d->manager.driver(d->connData.driverName); + if (!driver || d->manager.error()) { + setError(&d->manager); + return false; + } + + if (driver->isFileDriver()) + d->connData.setFileName( databaseName ); + + d->conn = driver->createConnection(d->connData); + if (!d->conn || driver->error()) { + setError(driver); + return false; + } + } + if (d->conn->isConnected()) { + // db already opened + if (d->conn->isDatabaseUsed() && d->conn->currentDatabase()==databaseName) //the same: do nothing + return true; + if (!closeDatabase()) // differs: close the first + return false; + } + if (!d->conn->connect()) { + setError(d->conn); + delete d->conn; + d->conn = 0; + return false; + } + + if (!d->conn->useDatabase( databaseName )) { + setError(d->conn); + delete d->conn; + d->conn = 0; + return false; + } + return true; +} + +bool SimpleCommandLineApp::closeDatabase() +{ + if (!d->conn) + return true; + if (!d->conn->disconnect()) { + setError(d->conn); + return false; + } + return true; +} + +KInstance* SimpleCommandLineApp::instance() const +{ + return d->instance; +} + +KexiDB::ConnectionData* SimpleCommandLineApp::connectionData() const +{ + return &d->connData; +} + +KexiDB::Connection* SimpleCommandLineApp::connection() const +{ + return d->conn; +} diff --git a/kexi/kexidb/simplecommandlineapp.h b/kexi/kexidb/simplecommandlineapp.h new file mode 100644 index 00000000..13d1f115 --- /dev/null +++ b/kexi/kexidb/simplecommandlineapp.h @@ -0,0 +1,86 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_SIMPLECMDLINEAPP_H +#define KEXIDB_SIMPLECMDLINEAPP_H + +#include <kexidb/connection.h> +#include <kexidb/driver.h> + +#include <kaboutdata.h> + +struct KCmdLineOptions; + +namespace KexiDB +{ + //! @short A skeleton for creating a simple command line database application. + /*! This class creates a KInstance object and automatically handles the following + command line options: + - --driver \<name\> (Database driver name) or -drv + - --user \<name\> (Database user name) or -u + - --password (Prompt for password) or -p + - --host \<name\> (Server (host) name) or -h + - --port \<number\> (Server's port number) + - --local-socket \<filename\> (Server's local socket filename, if needed) or -s + + You can use this helper class to create test applications or small tools that open + a KexiDB-compatible database using command line arguments, do some data processing + and close the database. + */ + class KEXI_DB_EXPORT SimpleCommandLineApp : public KexiDB::Object + { + public: + SimpleCommandLineApp( + int argc, char** argv, KCmdLineOptions *options, const char *programName, + const char *version, const char *shortDescription=0, + int licenseType=KAboutData::License_Unknown, + const char *copyrightStatement=0, const char *text=0, + const char *homePageAddress=0, const char *bugsEmailAddress="[email protected]"); + + ~SimpleCommandLineApp(); + + //! \return program instance + KInstance* instance() const; + + /*! Opens database \a databaseName for connection data + specified via the command line. \return true in success. + In details: the database driver is loaded, the connection is opened + and the database is used. + Use KexiDB::Object methods to get status of the operation on failure. */ + bool openDatabase(const QString& databaseName); + + /*! Closes database connection previously opened using openDatabase() + \return true on success. This method is called on destruction. + Use KexiDB::Object methods to get status of the operation on failure. */ + bool closeDatabase(); + + /*! \return connection data for this application. */ + KexiDB::ConnectionData* connectionData() const; + + /*! \return connection object for this application or 0 if there is no properly + opened connection. */ + KexiDB::Connection* connection() const; + + protected: + class Private; + Private * const d; + }; +} + +#endif diff --git a/kexi/kexidb/tableschema.cpp b/kexi/kexidb/tableschema.cpp new file mode 100644 index 00000000..8c0f5e07 --- /dev/null +++ b/kexi/kexidb/tableschema.cpp @@ -0,0 +1,453 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Joseph Wenninger <[email protected]> + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "tableschema.h" +#include "driver.h" +#include "connection.h" +#include "lookupfieldschema.h" + +#include <assert.h> +#include <kdebug.h> + +namespace KexiDB { +//! @internal +class TableSchema::Private +{ +public: + Private() + : anyNonPKField(0) + { + } + + ~Private() + { + clearLookupFields(); + } + + void clearLookupFields() + { + for (QMap<const Field*, LookupFieldSchema*>::ConstIterator it = lookupFields.constBegin(); + it!=lookupFields.constEnd(); ++it) + { + delete it.data(); + } + lookupFields.clear(); + } + + Field *anyNonPKField; + QMap<const Field*, LookupFieldSchema*> lookupFields; + QPtrVector<LookupFieldSchema> lookupFieldsList; +}; +} +//------------------------------------- + + +using namespace KexiDB; + +TableSchema::TableSchema(const QString& name) + : FieldList(true) + , SchemaData(KexiDB::TableObjectType) + , m_query(0) + , m_isKexiDBSystem(false) +{ + m_name = name.lower(); + init(); +} + +TableSchema::TableSchema(const SchemaData& sdata) + : FieldList(true) + , SchemaData(sdata) + , m_query(0) + , m_isKexiDBSystem(false) +{ + init(); +} + +TableSchema::TableSchema() + : FieldList(true) + , SchemaData(KexiDB::TableObjectType) + , m_query(0) + , m_isKexiDBSystem(false) +{ + init(); +} + +TableSchema::TableSchema(const TableSchema& ts, bool copyId) + : FieldList(static_cast<const FieldList&>(ts)) + , SchemaData(static_cast<const SchemaData&>(ts)) +{ + init(ts, copyId); +} + +TableSchema::TableSchema(const TableSchema& ts, int setId) + : FieldList(static_cast<const FieldList&>(ts)) + , SchemaData(static_cast<const SchemaData&>(ts)) +{ + init(ts, false); + m_id = setId; +} + +// used by Connection +TableSchema::TableSchema(Connection *conn, const QString & name) + : FieldList(true) + , SchemaData(KexiDB::TableObjectType) + , m_conn( conn ) + , m_query(0) + , m_isKexiDBSystem(false) +{ + d = new Private(); + assert(conn); + m_name = name; + m_indices.setAutoDelete( true ); + m_pkey = new IndexSchema(this); + m_indices.append(m_pkey); +} + +TableSchema::~TableSchema() +{ + if (m_conn) + m_conn->removeMe( this ); + delete m_query; + delete d; +} + +void TableSchema::init() +{ + d = new Private(); + m_indices.setAutoDelete( true ); + m_pkey = new IndexSchema(this); + m_indices.append(m_pkey); +} + +void TableSchema::init(const TableSchema& ts, bool copyId) +{ + m_conn = ts.m_conn; + m_query = 0; //not cached + m_isKexiDBSystem = false; + d = new Private(); + m_name = ts.m_name; + m_indices.setAutoDelete( true ); + m_pkey = 0; //will be copied + if (!copyId) + m_id = -1; + + //deep copy all members + IndexSchema::ListIterator idx_it(ts.m_indices); + for (;idx_it.current();++idx_it) { + IndexSchema *idx = new IndexSchema( + *idx_it.current(), *this /*fields from _this_ table will be assigned to the index*/); + if (idx->isPrimaryKey()) {//assign pkey + m_pkey = idx; + } + m_indices.append(idx); + } +} + +void TableSchema::setPrimaryKey(IndexSchema *pkey) +{ + if (m_pkey && m_pkey!=pkey) { + if (m_pkey->fieldCount()==0) {//this is empty key, probably default - remove it + m_indices.remove(m_pkey); + } + else { + m_pkey->setPrimaryKey(false); //there can be only one pkey.. + //thats ok, the old pkey is still on indices list, if not empty + } +// m_pkey=0; + } + + if (!pkey) {//clearing - set empty pkey + pkey = new IndexSchema(this); + } + m_pkey = pkey; //todo + m_pkey->setPrimaryKey(true); + d->anyNonPKField = 0; //for safety +} + +FieldList& TableSchema::insertField(uint index, Field *field) +{ + assert(field); + FieldList::insertField(index, field); + if (!field || index>m_fields.count()) + return *this; + field->setTable(this); + field->m_order = index; //m_fields.count(); + //update order for next next fields + Field *f = m_fields.at(index+1); + for (int i=index+1; f; i++, f = m_fields.next()) + f->m_order = i; + + //Check for auto-generated indices: + IndexSchema *idx = 0; + if (field->isPrimaryKey()) {// this is auto-generated single-field unique index + idx = new IndexSchema(this); + idx->setAutoGenerated(true); + idx->addField( field ); + setPrimaryKey(idx); + } + if (field->isUniqueKey()) { + if (!idx) { + idx = new IndexSchema(this); + idx->setAutoGenerated(true); + idx->addField( field ); + } + idx->setUnique(true); + } + if (field->isIndexed()) {// this is auto-generated single-field + if (!idx) { + idx = new IndexSchema(this); + idx->setAutoGenerated(true); + idx->addField( field ); + } + } + if (idx) + m_indices.append(idx); + return *this; +} + +void TableSchema::removeField(KexiDB::Field *field) +{ + if (d->anyNonPKField && field == d->anyNonPKField) //d->anyNonPKField will be removed! + d->anyNonPKField = 0; + delete d->lookupFields[field]; + d->lookupFields.remove(field); + FieldList::removeField(field); +} + +#if 0 //original +KexiDB::FieldList& TableSchema::addField(KexiDB::Field* field) +{ + assert(field); + FieldList::addField(field); + field->setTable(this); + field->m_order = m_fields.count(); + //Check for auto-generated indices: + + IndexSchema *idx = 0; + if (field->isPrimaryKey()) {// this is auto-generated single-field unique index + idx = new IndexSchema(this); + idx->setAutoGenerated(true); + idx->addField( field ); + setPrimaryKey(idx); + } + if (field->isUniqueKey()) { + if (!idx) { + idx = new IndexSchema(this); + idx->setAutoGenerated(true); + idx->addField( field ); + } + idx->setUnique(true); + } + if (field->isIndexed()) {// this is auto-generated single-field + if (!idx) { + idx = new IndexSchema(this); + idx->setAutoGenerated(true); + idx->addField( field ); + } + } + if (idx) + m_indices.append(idx); + return *this; +} +#endif + +void TableSchema::clear() +{ + m_indices.clear(); + d->clearLookupFields(); + FieldList::clear(); + SchemaData::clear(); + m_conn = 0; +} + +/* +void TableSchema::addPrimaryKey(const QString& key) +{ + m_primaryKeys.append(key); +}*/ + +/*QStringList TableSchema::primaryKeys() const +{ + return m_primaryKeys; +} + +bool TableSchema::hasPrimaryKeys() const +{ + return !m_primaryKeys.isEmpty(); +} +*/ + +//const QString& TableSchema::name() const +//{ +// return m_name; +//} + +//void TableSchema::setName(const QString& name) +//{ +// m_name=name; +/* ListIterator it( m_fields ); + Field *field; + for (; (field = it.current())!=0; ++it) { + + int fcnt=m_fields.count(); + for (int i=0;i<fcnt;i++) { + m_fields[i].setTable(name); + }*/ +//} + +/*KexiDB::Field TableSchema::field(unsigned int id) const +{ + if (id<m_fields.count()) return m_fields[id]; + return KexiDB::Field(); +} + +unsigned int TableSchema::fieldCount() const +{ + return m_fields.count(); +}*/ + +QString TableSchema::debugString() +{ + return debugString(true); +} + +QString TableSchema::debugString(bool includeTableName) +{ + QString s; + if (includeTableName) + s = QString("TABLE ") + schemaDataDebugString() + "\n"; + s.append( FieldList::debugString() ); + + Field *f; + for (Field::ListIterator it(m_fields); (f = it.current()); ++it) { + LookupFieldSchema *lookupSchema = lookupFieldSchema( *f ); + if (lookupSchema) + s.append( QString("\n") + lookupSchema->debugString() ); + } + return s; +} + +void TableSchema::setKexiDBSystem(bool set) +{ + if (set) + m_native=true; + m_isKexiDBSystem = set; +} + +void TableSchema::setNative(bool set) +{ + if (m_isKexiDBSystem && !set) { + KexiDBWarn << "TableSchema::setNative(): cannot set native off" + " when KexiDB system flag is set on!" << endl; + return; + } + m_native=set; +} + +QuerySchema* TableSchema::query() +{ + if (m_query) + return m_query; + m_query = new QuerySchema( *this ); //it's owned by me + return m_query; +} + +Field* TableSchema::anyNonPKField() +{ + if (!d->anyNonPKField) { + Field *f; + Field::ListIterator it(m_fields); + it.toLast(); //from the end (higher chances to find) + for (; (f = it.current()); --it) { + if (!f->isPrimaryKey() && (!m_pkey || !m_pkey->hasField(f))) + break; + } + d->anyNonPKField = f; + } + return d->anyNonPKField; +} + +bool TableSchema::setLookupFieldSchema( const QString& fieldName, LookupFieldSchema *lookupFieldSchema ) +{ + Field *f = field(fieldName); + if (!f) { + KexiDBWarn << "TableSchema::setLookupFieldSchema(): no such field '" << fieldName + << "' in table " << name() << endl; + return false; + } + if (lookupFieldSchema) + d->lookupFields.replace( f, lookupFieldSchema ); + else { + delete d->lookupFields[f]; + d->lookupFields.remove( f ); + } + d->lookupFieldsList.clear(); //this will force to rebuid the internal cache + return true; +} + +LookupFieldSchema *TableSchema::lookupFieldSchema( const Field& field ) const +{ + return d->lookupFields[ &field ]; +} + +LookupFieldSchema *TableSchema::lookupFieldSchema( const QString& fieldName ) +{ + Field *f = TableSchema::field(fieldName); + if (!f) + return 0; + return lookupFieldSchema( *f ); +} + +const QPtrVector<LookupFieldSchema>& TableSchema::lookupFieldsList() +{ + if (d->lookupFields.isEmpty()) + return d->lookupFieldsList; + if (!d->lookupFields.isEmpty() && !d->lookupFieldsList.isEmpty()) + return d->lookupFieldsList; //already updated + //update + d->lookupFieldsList.clear(); + d->lookupFieldsList.resize( d->lookupFields.count() ); + uint i = 0; + for (Field::ListIterator it(m_fields); it.current(); ++it) { + QMap<const Field*, LookupFieldSchema*>::ConstIterator itMap = d->lookupFields.find( it.current() ); + if (itMap != d->lookupFields.constEnd()) { + d->lookupFieldsList.insert( i, itMap.data() ); + i++; + } + } + return d->lookupFieldsList; +} + +//-------------------------------------- + +InternalTableSchema::InternalTableSchema(const QString& name) + : TableSchema(name) +{ +} + +InternalTableSchema::InternalTableSchema(const TableSchema& ts) + : TableSchema(ts, false) +{ +} + +InternalTableSchema::~InternalTableSchema() +{ +} + diff --git a/kexi/kexidb/tableschema.h b/kexi/kexidb/tableschema.h new file mode 100644 index 00000000..7584d703 --- /dev/null +++ b/kexi/kexidb/tableschema.h @@ -0,0 +1,210 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Joseph Wenninger <[email protected]> + Copyright (C) 2003-2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_TABLE_H +#define KEXIDB_TABLE_H + +#include <qvaluelist.h> +#include <qptrlist.h> +#include <qstring.h> +#include <qguardedptr.h> + +#include <kexidb/fieldlist.h> +#include <kexidb/schemadata.h> +#include <kexidb/indexschema.h> +#include <kexidb/relationship.h> + +namespace KexiDB { + +class Connection; +class LookupFieldSchema; + +/*! KexiDB::TableSchema provides information about native database table + that can be stored using KexiDB database engine. +*/ +class KEXI_DB_EXPORT TableSchema : public FieldList, public SchemaData +{ + public: + typedef QPtrList<TableSchema> List; //!< Type of tables list + typedef QPtrListIterator<TableSchema> ListIterator; //!< Iterator for tables list + + TableSchema(const QString & name); + TableSchema(const SchemaData& sdata); + TableSchema(); + + /*! Copy constructor. + if \a copyId is true, it's copied as well, otherwise the table id becomes -1, + what is usable when we want to store the copy as an independent table. */ + TableSchema(const TableSchema& ts, bool copyId = true); + + /*! Copy constructor like \ref TableSchema(const TableSchema&, bool). + \a setId is set as the table identifier. This is rarely usable, e.g. + in project and data migration routines when we need to need deal with unique identifiers; + @see KexiMigrate::performImport(). */ + TableSchema(const TableSchema& ts, int setId); + + virtual ~TableSchema(); + + /*! Inserts \a field into a specified position (\a index). + 'order' property of \a field is set automatically. */ + virtual FieldList& insertField(uint index, Field *field); + + /*! Reimplemented for internal reasons. */ + virtual void removeField(KexiDB::Field *field); + + /*! \return list of fields that are primary key of this table. + This method never returns 0 value, + if there is no primary key, empty IndexSchema object is returned. + IndexSchema object is owned by the table schema. */ + IndexSchema* primaryKey() const { return m_pkey; } + + /*! Sets table's primary key index to \a pkey. Pass pkey==0 if you want to unassign + existing primary key ("primary" property of given IndexSchema object will be + cleared then so this index becomes ordinary index, still existing on table indeices list). + + If this table already has primary key assigned, + it is unassigned using setPrimaryKey(0) call. + + Before assigning as primary key, you should add the index to indices list + with addIndex() (this is not done automatically!). + */ + void setPrimaryKey(IndexSchema *pkey); + + const IndexSchema::ListIterator indicesIterator() const + { return IndexSchema::ListIterator(m_indices); } + + const IndexSchema::List* indices() { return &m_indices; } + + /*! Removes all fields from the list, clears name and all other properties. + \sa FieldList::clear() */ + virtual void clear(); + + /*! \return String for debugging purposes, if \a includeTableName is true, + table name, caption, etc. is prepended, else only debug string for + the fields are returned. */ + QString debugString(bool includeTableName); + + /*! \return String for debugging purposes. Equal to debugString(true). */ + virtual QString debugString(); + + /*! \return connection object if table was created/retrieved using a connection, + otherwise 0. */ + Connection* connection() const { return m_conn; } + + /*! \return true if this is KexiDB storage system's table + (used internally by KexiDB). This helps in hiding such tables + in applications (if desired) and will also enable lookup of system + tables for schema export/import functionality. + + Any internal KexiDB system table's schema (kexi__*) has + cleared its SchemaData part, e.g. id=-1 for such table, + and no description, caption and so on. This is because + it represents a native database table rather that extended Kexi table. + + isKexiDBSystem()==true implies isNative()==true. + + By default (after allocation), TableSchema object + has this property set to false. */ + bool isKexiDBSystem() const { return m_isKexiDBSystem; } + + /*! Sets KexiDBSystem flag to on or off. When on, native flag is forced to be on. + When off, native flag is not affected. + \sa isKexiDBSystem() */ + void setKexiDBSystem(bool set); + + /*! \return true if this is schema of native database object, + When this is kexiDBSystem table, native flag is forced to be on. */ + virtual bool isNative() const { return m_native || m_isKexiDBSystem; } + + /* Sets native flag. Does not allow to set this off for system KexiDB table. */ + virtual void setNative(bool set); + + /*! \return query schema object that is defined by "select * from <this_table_name>" + This query schema object is owned by the table schema object. + It is convenient way to get such a query when it is not available otherwise. + Always returns non-0. */ + QuerySchema* query(); + + /*! \return any field not being a part of primary key of this table. + If there is no such field, returns 0. */ + Field* anyNonPKField(); + + /*! Sets lookup field schema \a lookupFieldSchema for \a fieldName. + Passing null \a lookupFieldSchema will remove the previously set lookup field. + \return true if \a lookupFieldSchema has been added, + or false if there is no such field \a fieldName. */ + bool setLookupFieldSchema( const QString& fieldName, LookupFieldSchema *lookupFieldSchema ); + + /*! \return lookup field schema for \a field. + 0 is returned if there is no such field in the table or this field has no lookup schema. + Note that even id non-zero is returned here, you may want to check whether lookup field's + rowSource().name() is empty (if so, the field should behave as there was no lookup field + defined at all). */ + LookupFieldSchema *lookupFieldSchema( const Field& field ) const; + + /*! \overload LookupFieldSchema *TableSchema::lookupFieldSchema( Field& field ) const */ + LookupFieldSchema *lookupFieldSchema( const QString& fieldName ); + + /*! \return list of lookup field schemas for this table. + The order is the same as the order of fields within the table. */ + const QPtrVector<LookupFieldSchema>& lookupFieldsList(); + + protected: + /*! Automatically retrieves table schema via connection. */ + TableSchema(Connection *conn, const QString & name = QString::null); + + IndexSchema::List m_indices; + + QGuardedPtr<Connection> m_conn; + + IndexSchema *m_pkey; + + QuerySchema *m_query; //!< cached query schema that is defined by "select * from <this_table_name>" + + class Private; + Private *d; + + private: + //! Used by some ctors. + void init(); + + //! Used by some ctors. + void init(const TableSchema& ts, bool copyId); + + bool m_isKexiDBSystem : 1; + + friend class Connection; +}; + +/*! Internal table with a name \a name. Rarely used. + Use Connection::createTable() to create a table using this schema. + The table will not be visible as user table. + For example, 'kexi__blobs' table is created this way by Kexi application. */ +class KEXI_DB_EXPORT InternalTableSchema : public TableSchema +{ + public: + InternalTableSchema(const QString& name); + InternalTableSchema(const TableSchema& ts); + virtual ~InternalTableSchema(); +}; + +} //namespace KexiDB + +#endif diff --git a/kexi/kexidb/transaction.cpp b/kexi/kexidb/transaction.cpp new file mode 100644 index 00000000..c0607448 --- /dev/null +++ b/kexi/kexidb/transaction.cpp @@ -0,0 +1,165 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kexidb/transaction.h> +#include <kexidb/connection.h> + +#include <kdebug.h> + +#include <assert.h> + +//remove debug +#undef KexiDBDbg +#define KexiDBDbg if (0) kdDebug() + +using namespace KexiDB; + +//helper for debugging +KEXI_DB_EXPORT int Transaction::globalcount = 0; +KEXI_DB_EXPORT int Transaction::globalCount() { return Transaction::globalcount; } +KEXI_DB_EXPORT int TransactionData::globalcount = 0; +KEXI_DB_EXPORT int TransactionData::globalCount() { return TransactionData::globalcount; } + +TransactionData::TransactionData(Connection *conn) + : m_conn(conn) + , m_active(true) + , refcount(1) +{ + assert(conn); + Transaction::globalcount++; //because refcount(1) init. + TransactionData::globalcount++; + KexiDBDbg << "-- TransactionData::globalcount == " << TransactionData::globalcount << endl; +} + +TransactionData::~TransactionData() +{ + TransactionData::globalcount--; + KexiDBDbg << "-- TransactionData::globalcount == " << TransactionData::globalcount << endl; +} + +//--------------------------------------------------- + +const Transaction Transaction::null; + +Transaction::Transaction() + : QObject(0,"kexidb_transaction") + , m_data(0) +{ +} + +Transaction::Transaction( const Transaction& trans ) + : QObject(0,"kexidb_transaction") + , m_data(trans.m_data) +{ + if (m_data) { + m_data->refcount++; + Transaction::globalcount++; + } +} + +Transaction::~Transaction() +{ + if (m_data) { + m_data->refcount--; + Transaction::globalcount--; + KexiDBDbg << "~Transaction(): m_data->refcount==" << m_data->refcount << endl; + if (m_data->refcount==0) + delete m_data; + } + else { + KexiDBDbg << "~Transaction(): null" << endl; + } + KexiDBDbg << "-- Transaction::globalcount == " << Transaction::globalcount << endl; +} + +Transaction& Transaction::operator=(const Transaction& trans) +{ + if (m_data) { + m_data->refcount--; + Transaction::globalcount--; + KexiDBDbg << "Transaction::operator=: m_data->refcount==" << m_data->refcount << endl; + if (m_data->refcount==0) + delete m_data; + } + m_data = trans.m_data; + if (m_data) { + m_data->refcount++; + Transaction::globalcount++; + } + return *this; +} + +bool Transaction::operator==(const Transaction& trans) const +{ + return m_data==trans.m_data; +} + +Connection* Transaction::connection() const +{ + return m_data ? m_data->m_conn : 0; +} + +bool Transaction::active() const +{ + return m_data && m_data->m_active; +} + +bool Transaction::isNull() const +{ + return m_data==0; +} + +//--------------------------------------------------- + +TransactionGuard::TransactionGuard( Connection& conn ) + : m_trans( conn.beginTransaction() ) + , m_doNothing(false) +{ +} + +TransactionGuard::TransactionGuard( const Transaction& trans ) + : m_trans(trans) + , m_doNothing(false) +{ +} + +TransactionGuard::TransactionGuard() + : m_doNothing(false) +{ +} + +TransactionGuard::~TransactionGuard() +{ + if (!m_doNothing && m_trans.active() && m_trans.connection()) + m_trans.connection()->rollbackTransaction(m_trans); +} + +bool TransactionGuard::commit() +{ + if (m_trans.active() && m_trans.connection()) { + return m_trans.connection()->commitTransaction(m_trans); + } + return false; +} + +void TransactionGuard::doNothing() +{ + m_doNothing = true; +} + diff --git a/kexi/kexidb/transaction.h b/kexi/kexidb/transaction.h new file mode 100644 index 00000000..2ec065d9 --- /dev/null +++ b/kexi/kexidb/transaction.h @@ -0,0 +1,159 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Jaroslaw Staniek <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_TRANSACTION_H +#define KEXIDB_TRANSACTION_H + +#include <qguardedptr.h> + +#include <kexidb/kexidb_export.h> + +namespace KexiDB { + +class Connection; + +/*! Internal prototype for storing transaction handles for Transaction object. + Only for driver developers: reimplement this class for driver that + support transaction handles. +*/ +class KEXI_DB_EXPORT TransactionData +{ + public: + TransactionData(Connection *conn); + ~TransactionData(); + + //helper for debugging + static int globalcount; + //helper for debugging + static int globalCount(); + + Connection *m_conn; + bool m_active : 1; + uint refcount; +}; + +//! This class encapsulates transaction handle. +/*! Transaction handle is sql driver-dependent, + but outside Transaction is visible as universal container + for any handler implementation. + + Transaction object is value-based, internal data (handle) structure, + reference-counted. +*/ +class KEXI_DB_EXPORT Transaction : public QObject +{ + public: + /*! Constructs uninitialised (null) transaction. + Only in Conenction code it can be initialised */ + Transaction(); + + //! Copy ctor. + Transaction( const Transaction& trans ); + + virtual ~Transaction(); + + Transaction& operator=(const Transaction& trans); + + bool operator==(const Transaction& trans ) const; + + Connection* connection() const; + + /*! \return true if transaction is avtive (ie. started) + Returns false also if transaction is uninitialised (null). */ + bool active() const; + + /*! \return true if transaction is uinitialised (null). */ + bool isNull() const; + + /*! shortcut that offers uinitialised (null) transaction */ + static const Transaction null; + + //helper for debugging + static int globalCount(); + static int globalcount; + protected: + + TransactionData *m_data; + + friend class Connection; +}; + +//! Helper class for using inside methods for given connection. +/*! It can be used in two ways: + - start new transaction in constructor and rollback on destruction (1st constructor), + - use already started transaction and rollback on destruction (2nd constructor). + In any case, if transaction is committed or rolled back outside this TransactionGuard + object in the meantime, nothing happens on TransactionGuard destruction. + <code> + Example usage: + void myclas::my_method() + { + Transaction *transaction = connection->beginTransaction(); + TransactionGuard tg(transaction); + ...some code that operates inside started transaction... + if (something) + return //after return from this code block: tg will call + //connection->rollbackTransaction() automatically + if (something_else) + transaction->commit(); + //for now tg won't do anything because transaction does not exist + } + </code> +*/ +class KEXI_DB_EXPORT TransactionGuard +{ + public: + /*! Constructor #1: Starts new transaction constructor for \a connection. + Started transaction handle is available via transaction().*/ + TransactionGuard( Connection& conn ); + + /*! Constructor #2: Uses already started transaction. */ + TransactionGuard( const Transaction& trans ); + + /*! Constructor #3: Creates TransactionGuard without transaction assinged. + setTransaction() can be used later to do so. */ + TransactionGuard(); + + /*! Rollbacks not committed transaction. */ + ~TransactionGuard(); + + /*! Assigns transaction \a trans to this guard. + Previously assigned transaction will be unassigned from this guard. */ + void setTransaction( const Transaction& trans ) { m_trans = trans; } + + /*! Comits the guarded transaction. + It is convenient shortcut to connection->commitTransaction(this->transaction()) */ + bool commit(); + + /*! Makes guarded transaction not guarded, so nothing will be performed on guard's desctruction. */ + void doNothing(); + + /*! Transaction that are controlled by this guard. */ + const Transaction transaction() const { return m_trans; } + + protected: + Transaction m_trans; + bool m_doNothing : 1; +}; + +} //namespace KexiDB + +#endif + + diff --git a/kexi/kexidb/utils.cpp b/kexi/kexidb/utils.cpp new file mode 100644 index 00000000..78b7e416 --- /dev/null +++ b/kexi/kexidb/utils.cpp @@ -0,0 +1,1262 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "utils.h" +#include "cursor.h" +#include "drivermanager.h" +#include "lookupfieldschema.h" + +#include <qmap.h> +#include <qthread.h> +#include <qdom.h> +#include <qintdict.h> +#include <qbuffer.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kstaticdeleter.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kiconloader.h> + +#include "utils_p.h" + +using namespace KexiDB; + +//! Cache +struct TypeCache +{ + QMap< uint, TypeGroupList > tlist; + QMap< uint, QStringList > nlist; + QMap< uint, QStringList > slist; + QMap< uint, Field::Type > def_tlist; +}; + +static KStaticDeleter<TypeCache> KexiDB_typeCacheDeleter; +TypeCache *KexiDB_typeCache = 0; + +static void initList() +{ + KexiDB_typeCacheDeleter.setObject( KexiDB_typeCache, new TypeCache() ); + + for (uint t=0; t<=KexiDB::Field::LastType; t++) { + const uint tg = KexiDB::Field::typeGroup( t ); + TypeGroupList list; + QStringList name_list, str_list; + if (KexiDB_typeCache->tlist.find( tg )!=KexiDB_typeCache->tlist.end()) { + list = KexiDB_typeCache->tlist[ tg ]; + name_list = KexiDB_typeCache->nlist[ tg ]; + str_list = KexiDB_typeCache->slist[ tg ]; + } + list+= t; + name_list += KexiDB::Field::typeName( t ); + str_list += KexiDB::Field::typeString( t ); + KexiDB_typeCache->tlist[ tg ] = list; + KexiDB_typeCache->nlist[ tg ] = name_list; + KexiDB_typeCache->slist[ tg ] = str_list; + } + + KexiDB_typeCache->def_tlist[ Field::InvalidGroup ] = Field::InvalidType; + KexiDB_typeCache->def_tlist[ Field::TextGroup ] = Field::Text; + KexiDB_typeCache->def_tlist[ Field::IntegerGroup ] = Field::Integer; + KexiDB_typeCache->def_tlist[ Field::FloatGroup ] = Field::Double; + KexiDB_typeCache->def_tlist[ Field::BooleanGroup ] = Field::Boolean; + KexiDB_typeCache->def_tlist[ Field::DateTimeGroup ] = Field::Date; + KexiDB_typeCache->def_tlist[ Field::BLOBGroup ] = Field::BLOB; +} + +const TypeGroupList KexiDB::typesForGroup(KexiDB::Field::TypeGroup typeGroup) +{ + if (!KexiDB_typeCache) + initList(); + return KexiDB_typeCache->tlist[ typeGroup ]; +} + +QStringList KexiDB::typeNamesForGroup(KexiDB::Field::TypeGroup typeGroup) +{ + if (!KexiDB_typeCache) + initList(); + return KexiDB_typeCache->nlist[ typeGroup ]; +} + +QStringList KexiDB::typeStringsForGroup(KexiDB::Field::TypeGroup typeGroup) +{ + if (!KexiDB_typeCache) + initList(); + return KexiDB_typeCache->slist[ typeGroup ]; +} + +KexiDB::Field::Type KexiDB::defaultTypeForGroup(KexiDB::Field::TypeGroup typeGroup) +{ + if (!KexiDB_typeCache) + initList(); + return (typeGroup <= Field::LastTypeGroup) ? KexiDB_typeCache->def_tlist[ typeGroup ] : Field::InvalidType; +} + +void KexiDB::getHTMLErrorMesage(Object* obj, QString& msg, QString &details) +{ + Connection *conn = 0; + if (!obj || !obj->error()) { + if (dynamic_cast<Cursor*>(obj)) { + conn = dynamic_cast<Cursor*>(obj)->connection(); + obj = conn; + } + else { + return; + } + } +// if (dynamic_cast<Connection*>(obj)) { + // conn = dynamic_cast<Connection*>(obj); + //} + if (!obj || !obj->error()) + return; + //lower level message is added to the details, if there is alread message specified + if (!obj->msgTitle().isEmpty()) + msg += "<p>" + obj->msgTitle(); + + if (msg.isEmpty()) + msg = "<p>" + obj->errorMsg(); + else + details += "<p>" + obj->errorMsg(); + + if (!obj->serverErrorMsg().isEmpty()) + details += "<p><b><nobr>" +i18n("Message from server:") + "</nobr></b><br>" + obj->serverErrorMsg(); + if (!obj->recentSQLString().isEmpty()) + details += "<p><b><nobr>" +i18n("SQL statement:") + QString("</nobr></b><br><tt>%1</tt>").arg(obj->recentSQLString()); + int serverResult; + QString serverResultName; + if (obj->serverResult()!=0) { + serverResult = obj->serverResult(); + serverResultName = obj->serverResultName(); + } + else { + serverResult = obj->previousServerResult(); + serverResultName = obj->previousServerResultName(); + } + if (!serverResultName.isEmpty()) + details += (QString("<p><b><nobr>")+i18n("Server result name:")+"</nobr></b><br>"+serverResultName); + if (!details.isEmpty() + && (!obj->serverErrorMsg().isEmpty() || !obj->recentSQLString().isEmpty() || !serverResultName.isEmpty() || serverResult!=0) ) + { + details += (QString("<p><b><nobr>")+i18n("Server result number:")+"</nobr></b><br>"+QString::number(serverResult)); + } + + if (!details.isEmpty() && !details.startsWith("<qt>")) { + if (details.startsWith("<p>")) + details = QString::fromLatin1("<qt>")+details; + else + details = QString::fromLatin1("<qt><p>")+details; + } +} + +void KexiDB::getHTMLErrorMesage(Object* obj, QString& msg) +{ + getHTMLErrorMesage(obj, msg, msg); +} + +void KexiDB::getHTMLErrorMesage(Object* obj, ResultInfo *result) +{ + getHTMLErrorMesage(obj, result->msg, result->desc); +} + +int KexiDB::idForObjectName( Connection &conn, const QString& objName, int objType ) +{ + RowData data; + if (true!=conn.querySingleRecord(QString("select o_id from kexi__objects where lower(o_name)='%1' and o_type=%2") + .arg(objName.lower()).arg(objType), data)) + return 0; + bool ok; + int id = data[0].toInt(&ok); + return ok ? id : 0; +} + +//----------------------------------------- + +TableOrQuerySchema::TableOrQuerySchema(Connection *conn, const QCString& name) + : m_name(name) +{ + m_table = conn->tableSchema(QString(name)); + m_query = m_table ? 0 : conn->querySchema(QString(name)); + if (!m_table && !m_query) + KexiDBWarn << "TableOrQuery(FieldList &tableOrQuery) : " + " tableOrQuery is neither table nor query!" << endl; +} + +TableOrQuerySchema::TableOrQuerySchema(Connection *conn, const QCString& name, bool table) + : m_name(name) + , m_table(table ? conn->tableSchema(QString(name)) : 0) + , m_query(table ? 0 : conn->querySchema(QString(name))) +{ + if (table && !m_table) + KexiDBWarn << "TableOrQuery(Connection *conn, const QCString& name, bool table) : " + "no table specified!" << endl; + if (!table && !m_query) + KexiDBWarn << "TableOrQuery(Connection *conn, const QCString& name, bool table) : " + "no query specified!" << endl; +} + +TableOrQuerySchema::TableOrQuerySchema(FieldList &tableOrQuery) + : m_table(dynamic_cast<TableSchema*>(&tableOrQuery)) + , m_query(dynamic_cast<QuerySchema*>(&tableOrQuery)) +{ + if (!m_table && !m_query) + KexiDBWarn << "TableOrQuery(FieldList &tableOrQuery) : " + " tableOrQuery is nether table nor query!" << endl; +} + +TableOrQuerySchema::TableOrQuerySchema(Connection *conn, int id) +{ + m_table = conn->tableSchema(id); + m_query = m_table ? 0 : conn->querySchema(id); + if (!m_table && !m_query) + KexiDBWarn << "TableOrQuery(Connection *conn, int id) : no table or query found for id==" + << id << "!" << endl; +} + +TableOrQuerySchema::TableOrQuerySchema(TableSchema* table) + : m_table(table) + , m_query(0) +{ + if (!m_table) + KexiDBWarn << "TableOrQuery(TableSchema* table) : no table specified!" << endl; +} + +TableOrQuerySchema::TableOrQuerySchema(QuerySchema* query) + : m_table(0) + , m_query(query) +{ + if (!m_query) + KexiDBWarn << "TableOrQuery(QuerySchema* query) : no query specified!" << endl; +} + +uint TableOrQuerySchema::fieldCount() const +{ + if (m_table) + return m_table->fieldCount(); + if (m_query) + return m_query->fieldsExpanded().size(); + return 0; +} + +const QueryColumnInfo::Vector TableOrQuerySchema::columns(bool unique) +{ + if (m_table) + return m_table->query()->fieldsExpanded(unique ? QuerySchema::Unique : QuerySchema::Default); + + if (m_query) + return m_query->fieldsExpanded(unique ? QuerySchema::Unique : QuerySchema::Default); + + KexiDBWarn << "TableOrQuerySchema::column() : no query or table specified!" << endl; + return QueryColumnInfo::Vector(); +} + +QCString TableOrQuerySchema::name() const +{ + if (m_table) + return m_table->name().latin1(); + if (m_query) + return m_query->name().latin1(); + return m_name; +} + +QString TableOrQuerySchema::captionOrName() const +{ + SchemaData *sdata = m_table ? static_cast<SchemaData *>(m_table) : static_cast<SchemaData *>(m_query); + if (!sdata) + return m_name; + return sdata->caption().isEmpty() ? sdata->name() : sdata->caption(); +} + +Field* TableOrQuerySchema::field(const QString& name) +{ + if (m_table) + return m_table->field(name); + if (m_query) + return m_query->field(name); + + return 0; +} + +QueryColumnInfo* TableOrQuerySchema::columnInfo(const QString& name) +{ + if (m_table) + return m_table->query()->columnInfo(name); + + if (m_query) + return m_query->columnInfo(name); + + return 0; +} + +QString TableOrQuerySchema::debugString() +{ + if (m_table) + return m_table->debugString(); + else if (m_query) + return m_query->debugString(); + return QString::null; +} + +void TableOrQuerySchema::debug() +{ + if (m_table) + return m_table->debug(); + else if (m_query) + return m_query->debug(); +} + +Connection* TableOrQuerySchema::connection() const +{ + if (m_table) + return m_table->connection(); + else if (m_query) + return m_query->connection(); + return 0; +} + + +//------------------------------------------ + +class ConnectionTestThread : public QThread { + public: + ConnectionTestThread(ConnectionTestDialog *dlg, const KexiDB::ConnectionData& connData); + virtual void run(); + protected: + ConnectionTestDialog* m_dlg; + KexiDB::ConnectionData m_connData; +}; + +ConnectionTestThread::ConnectionTestThread(ConnectionTestDialog* dlg, const KexiDB::ConnectionData& connData) + : m_dlg(dlg), m_connData(connData) +{ +} + +void ConnectionTestThread::run() +{ + KexiDB::DriverManager manager; + KexiDB::Driver* drv = manager.driver(m_connData.driverName); +// KexiGUIMessageHandler msghdr; + if (!drv || manager.error()) { +//move msghdr.showErrorMessage(&Kexi::driverManager()); + m_dlg->error(&manager); + return; + } + KexiDB::Connection * conn = drv->createConnection(m_connData); + if (!conn || drv->error()) { +//move msghdr.showErrorMessage(drv); + delete conn; + m_dlg->error(drv); + return; + } + if (!conn->connect() || conn->error()) { +//move msghdr.showErrorMessage(conn); + m_dlg->error(conn); + delete conn; + return; + } + // SQL database backends like PostgreSQL require executing "USE database" + // if we really want to know connection to the server succeeded. + QString tmpDbName; + if (!conn->useTemporaryDatabaseIfNeeded( tmpDbName )) { + m_dlg->error(conn); + delete conn; + return; + } + delete conn; + m_dlg->error(0); +} + +ConnectionTestDialog::ConnectionTestDialog(QWidget* parent, + const KexiDB::ConnectionData& data, + KexiDB::MessageHandler& msgHandler) + : KProgressDialog(parent, "testconn_dlg", + i18n("Test Connection"), i18n("<qt>Testing connection to <b>%1</b> database server...</qt>") + .arg(data.serverInfoString(true)), true /*modal*/) + , m_thread(new ConnectionTestThread(this, data)) + , m_connData(data) + , m_msgHandler(&msgHandler) + , m_elapsedTime(0) + , m_errorObj(0) + , m_stopWaiting(false) +{ + showCancelButton(true); + progressBar()->setPercentageVisible(false); + progressBar()->setTotalSteps(0); + connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout())); + adjustSize(); + resize(250, height()); +} + +ConnectionTestDialog::~ConnectionTestDialog() +{ + m_wait.wakeAll(); + m_thread->terminate(); + delete m_thread; +} + +int ConnectionTestDialog::exec() +{ + m_timer.start(20); + m_thread->start(); + const int res = KProgressDialog::exec(); + m_thread->wait(); + m_timer.stop(); + return res; +} + +void ConnectionTestDialog::slotTimeout() +{ +// KexiDBDbg << "ConnectionTestDialog::slotTimeout() " << m_errorObj << endl; + bool notResponding = false; + if (m_elapsedTime >= 1000*5) {//5 seconds + m_stopWaiting = true; + notResponding = true; + } + if (m_stopWaiting) { + m_timer.disconnect(this); + m_timer.stop(); + slotCancel(); +// reject(); +// close(); + if (m_errorObj) { + m_msgHandler->showErrorMessage(m_errorObj); + m_errorObj = 0; + } + else if (notResponding) { + KMessageBox::sorry(0, + i18n("<qt>Test connection to <b>%1</b> database server failed. The server is not responding.</qt>") + .arg(m_connData.serverInfoString(true)), + i18n("Test Connection")); + } + else { + KMessageBox::information(0, + i18n("<qt>Test connection to <b>%1</b> database server established successfully.</qt>") + .arg(m_connData.serverInfoString(true)), + i18n("Test Connection")); + } +// slotCancel(); +// reject(); + m_wait.wakeAll(); + return; + } + m_elapsedTime += 20; + progressBar()->setProgress( m_elapsedTime ); +} + +void ConnectionTestDialog::error(KexiDB::Object *obj) +{ + KexiDBDbg << "ConnectionTestDialog::error()" << endl; + m_stopWaiting = true; + m_errorObj = obj; +/* reject(); + m_msgHandler->showErrorMessage(obj); + if (obj) { + } + else { + accept(); + }*/ + m_wait.wait(); +} + +void ConnectionTestDialog::slotCancel() +{ +// m_wait.wakeAll(); + m_thread->terminate(); + m_timer.disconnect(this); + m_timer.stop(); + KProgressDialog::slotCancel(); +} + +void KexiDB::connectionTestDialog(QWidget* parent, const KexiDB::ConnectionData& data, + KexiDB::MessageHandler& msgHandler) +{ + ConnectionTestDialog dlg(parent, data, msgHandler); + dlg.exec(); +} + +int KexiDB::rowCount(Connection &conn, const QString& sql) +{ + int count = -1; //will be changed only on success of querySingleNumber() + QString selectSql( QString::fromLatin1("SELECT COUNT() FROM (") + sql + ")" ); + conn.querySingleNumber(selectSql, count); + return count; +} + +int KexiDB::rowCount(const KexiDB::TableSchema& tableSchema) +{ +//! @todo does not work with non-SQL data sources + if (!tableSchema.connection()) { + KexiDBWarn << "KexiDB::rowsCount(const KexiDB::TableSchema&): no tableSchema.connection() !" << endl; + return -1; + } + int count = -1; //will be changed only on success of querySingleNumber() + tableSchema.connection()->querySingleNumber( + QString::fromLatin1("SELECT COUNT(*) FROM ") + + tableSchema.connection()->driver()->escapeIdentifier(tableSchema.name()), + count + ); + return count; +} + +int KexiDB::rowCount(KexiDB::QuerySchema& querySchema) +{ +//! @todo does not work with non-SQL data sources + if (!querySchema.connection()) { + KexiDBWarn << "KexiDB::rowsCount(const KexiDB::QuerySchema&): no querySchema.connection() !" << endl; + return -1; + } + int count = -1; //will be changed only on success of querySingleNumber() + querySchema.connection()->querySingleNumber( + QString::fromLatin1("SELECT COUNT(*) FROM (") + + querySchema.connection()->selectStatement(querySchema) + ")", + count + ); + return count; +} + +int KexiDB::rowCount(KexiDB::TableOrQuerySchema& tableOrQuery) +{ + if (tableOrQuery.table()) + return rowCount( *tableOrQuery.table() ); + if (tableOrQuery.query()) + return rowCount( *tableOrQuery.query() ); + return -1; +} + +int KexiDB::fieldCount(KexiDB::TableOrQuerySchema& tableOrQuery) +{ + if (tableOrQuery.table()) + return tableOrQuery.table()->fieldCount(); + if (tableOrQuery.query()) + return tableOrQuery.query()->fieldsExpanded().count(); + return -1; +} + +QMap<QString,QString> KexiDB::toMap( const ConnectionData& data ) +{ + QMap<QString,QString> m; + m["caption"] = data.caption; + m["description"] = data.description; + m["driverName"] = data.driverName; + m["hostName"] = data.hostName; + m["port"] = QString::number(data.port); + m["useLocalSocketFile"] = QString::number((int)data.useLocalSocketFile); + m["localSocketFileName"] = data.localSocketFileName; + m["password"] = data.password; + m["savePassword"] = QString::number((int)data.savePassword); + m["userName"] = data.userName; + m["fileName"] = data.fileName(); + return m; +} + +void KexiDB::fromMap( const QMap<QString,QString>& map, ConnectionData& data ) +{ + data.caption = map["caption"]; + data.description = map["description"]; + data.driverName = map["driverName"]; + data.hostName = map["hostName"]; + data.port = map["port"].toInt(); + data.useLocalSocketFile = map["useLocalSocketFile"].toInt()==1; + data.localSocketFileName = map["localSocketFileName"]; + data.password = map["password"]; + data.savePassword = map["savePassword"].toInt()==1; + data.userName = map["userName"]; + data.setFileName(map["fileName"]); +} + +bool KexiDB::splitToTableAndFieldParts(const QString& string, + QString& tableName, QString& fieldName, + SplitToTableAndFieldPartsOptions option) +{ + const int id = string.find('.'); + if (option & SetFieldNameIfNoTableName && id==-1) { + tableName = QString::null; + fieldName = string; + return !fieldName.isEmpty(); + } + if (id<=0 || id==int(string.length()-1)) + return false; + tableName = string.left(id); + fieldName = string.mid(id+1); + return !tableName.isEmpty() && !fieldName.isEmpty(); +} + +bool KexiDB::supportsVisibleDecimalPlacesProperty(Field::Type type) +{ +//! @todo add check for decimal type as well + return Field::isFPNumericType(type); +} + +QString KexiDB::formatNumberForVisibleDecimalPlaces(double value, int decimalPlaces) +{ +//! @todo round? + if (decimalPlaces < 0) { + QString s( QString::number(value, 'f', 10 /*reasonable precision*/)); + uint i = s.length()-1; + while (i>0 && s[i]=='0') + i--; + if (s[i]=='.') //remove '.' + i--; + s = s.left(i+1).replace('.', KGlobal::locale()->decimalSymbol()); + return s; + } + if (decimalPlaces == 0) + return QString::number((int)value); + return KGlobal::locale()->formatNumber(value, decimalPlaces); +} + +KexiDB::Field::Type KexiDB::intToFieldType( int type ) +{ + if (type<(int)KexiDB::Field::InvalidType || type>(int)KexiDB::Field::LastType) { + KexiDBWarn << "KexiDB::intToFieldType(): invalid type " << type << endl; + return KexiDB::Field::InvalidType; + } + return (KexiDB::Field::Type)type; +} + +static bool setIntToFieldType( Field& field, const QVariant& value ) +{ + bool ok; + const int intType = value.toInt(&ok); + if (!ok || KexiDB::Field::InvalidType == intToFieldType(intType)) {//for sanity + KexiDBWarn << "KexiDB::setFieldProperties(): invalid type" << endl; + return false; + } + field.setType((KexiDB::Field::Type)intType); + return true; +} + +//! for KexiDB::isBuiltinTableFieldProperty() +static KStaticDeleter< QAsciiDict<char> > KexiDB_builtinFieldPropertiesDeleter; +//! for KexiDB::isBuiltinTableFieldProperty() +QAsciiDict<char>* KexiDB_builtinFieldProperties = 0; + +bool KexiDB::isBuiltinTableFieldProperty( const QCString& propertyName ) +{ + if (!KexiDB_builtinFieldProperties) { + KexiDB_builtinFieldPropertiesDeleter.setObject( KexiDB_builtinFieldProperties, new QAsciiDict<char>(499) ); +#define ADD(name) KexiDB_builtinFieldProperties->insert(name, (char*)1) + ADD("type"); + ADD("primaryKey"); + ADD("indexed"); + ADD("autoIncrement"); + ADD("unique"); + ADD("notNull"); + ADD("allowEmpty"); + ADD("unsigned"); + ADD("name"); + ADD("caption"); + ADD("description"); + ADD("length"); + ADD("precision"); + ADD("defaultValue"); + ADD("width"); + ADD("visibleDecimalPlaces"); +//! @todo always update this when new builtins appear! +#undef ADD + } + return KexiDB_builtinFieldProperties->find( propertyName ); +} + +bool KexiDB::setFieldProperties( Field& field, const QMap<QCString, QVariant>& values ) +{ + QMapConstIterator<QCString, QVariant> it; + if ( (it = values.find("type")) != values.constEnd() ) { + if (!setIntToFieldType(field, *it)) + return false; + } + +#define SET_BOOLEAN_FLAG(flag, value) { \ + constraints |= KexiDB::Field::flag; \ + if (!value) \ + constraints ^= KexiDB::Field::flag; \ + } + + uint constraints = field.constraints(); + bool ok = true; + if ( (it = values.find("primaryKey")) != values.constEnd() ) + SET_BOOLEAN_FLAG(PrimaryKey, (*it).toBool()); + if ( (it = values.find("indexed")) != values.constEnd() ) + SET_BOOLEAN_FLAG(Indexed, (*it).toBool()); + if ( (it = values.find("autoIncrement")) != values.constEnd() + && KexiDB::Field::isAutoIncrementAllowed(field.type()) ) + SET_BOOLEAN_FLAG(AutoInc, (*it).toBool()); + if ( (it = values.find("unique")) != values.constEnd() ) + SET_BOOLEAN_FLAG(Unique, (*it).toBool()); + if ( (it = values.find("notNull")) != values.constEnd() ) + SET_BOOLEAN_FLAG(NotNull, (*it).toBool()); + if ( (it = values.find("allowEmpty")) != values.constEnd() ) + SET_BOOLEAN_FLAG(NotEmpty, !(*it).toBool()); + field.setConstraints( constraints ); + + uint options = 0; + if ( (it = values.find("unsigned")) != values.constEnd()) { + options |= KexiDB::Field::Unsigned; + if (!(*it).toBool()) + options ^= KexiDB::Field::Unsigned; + } + field.setOptions( options ); + + if ( (it = values.find("name")) != values.constEnd()) + field.setName( (*it).toString() ); + if ( (it = values.find("caption")) != values.constEnd()) + field.setCaption( (*it).toString() ); + if ( (it = values.find("description")) != values.constEnd()) + field.setDescription( (*it).toString() ); + if ( (it = values.find("length")) != values.constEnd()) + field.setLength( (*it).isNull() ? 0/*default*/ : (*it).toUInt(&ok) ); + if (!ok) + return false; + if ( (it = values.find("precision")) != values.constEnd()) + field.setPrecision( (*it).isNull() ? 0/*default*/ : (*it).toUInt(&ok) ); + if (!ok) + return false; + if ( (it = values.find("defaultValue")) != values.constEnd()) + field.setDefaultValue( *it ); + if ( (it = values.find("width")) != values.constEnd()) + field.setWidth( (*it).isNull() ? 0/*default*/ : (*it).toUInt(&ok) ); + if (!ok) + return false; + if ( (it = values.find("visibleDecimalPlaces")) != values.constEnd() + && KexiDB::supportsVisibleDecimalPlacesProperty(field.type()) ) + field.setVisibleDecimalPlaces( (*it).isNull() ? -1/*default*/ : (*it).toInt(&ok) ); + if (!ok) + return false; + + // set custom properties + typedef QMap<QCString, QVariant> PropertiesMap; + foreach( PropertiesMap::ConstIterator, it, values ) { + if (!isBuiltinTableFieldProperty( it.key() ) && !isExtendedTableFieldProperty( it.key() )) { + field.setCustomProperty( it.key(), it.data() ); + } + } + return true; +#undef SET_BOOLEAN_FLAG +} + +//! for KexiDB::isExtendedTableFieldProperty() +static KStaticDeleter< QAsciiDict<char> > KexiDB_extendedPropertiesDeleter; +//! for KexiDB::isExtendedTableFieldProperty() +QAsciiDict<char>* KexiDB_extendedProperties = 0; + +bool KexiDB::isExtendedTableFieldProperty( const QCString& propertyName ) +{ + if (!KexiDB_extendedProperties) { + KexiDB_extendedPropertiesDeleter.setObject( KexiDB_extendedProperties, new QAsciiDict<char>(499, false) ); +#define ADD(name) KexiDB_extendedProperties->insert(name, (char*)1) + ADD("visibleDecimalPlaces"); + ADD("rowSource"); + ADD("rowSourceType"); + ADD("rowSourceValues"); + ADD("boundColumn"); + ADD("visibleColumn"); + ADD("columnWidths"); + ADD("showColumnHeaders"); + ADD("listRows"); + ADD("limitToList"); + ADD("displayWidget"); +#undef ADD + } + return KexiDB_extendedProperties->find( propertyName ); +} + +bool KexiDB::setFieldProperty( Field& field, const QCString& propertyName, const QVariant& value ) +{ +#define SET_BOOLEAN_FLAG(flag, value) { \ + constraints |= KexiDB::Field::flag; \ + if (!value) \ + constraints ^= KexiDB::Field::flag; \ + field.setConstraints( constraints ); \ + return true; \ + } +#define GET_INT(method) { \ + const uint ival = value.toUInt(&ok); \ + if (!ok) \ + return false; \ + field.method( ival ); \ + return true; \ + } + + if (propertyName.isEmpty()) + return false; + + bool ok; + if (KexiDB::isExtendedTableFieldProperty(propertyName)) { + //a little speedup: identify extended property in O(1) + if ( "visibleDecimalPlaces" == propertyName + && KexiDB::supportsVisibleDecimalPlacesProperty(field.type()) ) + { + GET_INT( setVisibleDecimalPlaces ); + } + else { + if (!field.table()) { + KexiDBWarn << QString("KexiDB::setFieldProperty() Cannot set \"%1\" property - no table assinged for field!") + .arg(propertyName) << endl; + } + else { + LookupFieldSchema *lookup = field.table()->lookupFieldSchema(field); + const bool hasLookup = lookup != 0; + if (!hasLookup) + lookup = new LookupFieldSchema(); + if (LookupFieldSchema::setProperty( *lookup, propertyName, value )) { + if (!hasLookup && lookup) + field.table()->setLookupFieldSchema( field.name(), lookup ); + return true; + } + delete lookup; + } + } + } + else {//non-extended + if ( "type" == propertyName ) + return setIntToFieldType(field, value); + + uint constraints = field.constraints(); + if ( "primaryKey" == propertyName ) + SET_BOOLEAN_FLAG(PrimaryKey, value.toBool()); + if ( "indexed" == propertyName ) + SET_BOOLEAN_FLAG(Indexed, value.toBool()); + if ( "autoIncrement" == propertyName + && KexiDB::Field::isAutoIncrementAllowed(field.type()) ) + SET_BOOLEAN_FLAG(AutoInc, value.toBool()); + if ( "unique" == propertyName ) + SET_BOOLEAN_FLAG(Unique, value.toBool()); + if ( "notNull" == propertyName ) + SET_BOOLEAN_FLAG(NotNull, value.toBool()); + if ( "allowEmpty" == propertyName ) + SET_BOOLEAN_FLAG(NotEmpty, !value.toBool()); + + uint options = 0; + if ( "unsigned" == propertyName ) { + options |= KexiDB::Field::Unsigned; + if (!value.toBool()) + options ^= KexiDB::Field::Unsigned; + field.setOptions( options ); + return true; + } + + if ( "name" == propertyName ) { + if (value.toString().isEmpty()) + return false; + field.setName( value.toString() ); + return true; + } + if ( "caption" == propertyName ) { + field.setCaption( value.toString() ); + return true; + } + if ( "description" == propertyName ) { + field.setDescription( value.toString() ); + return true; + } + if ( "length" == propertyName ) + GET_INT( setLength ); + if ( "precision" == propertyName ) + GET_INT( setPrecision ); + if ( "defaultValue" == propertyName ) { + field.setDefaultValue( value ); + return true; + } + if ( "width" == propertyName ) + GET_INT( setWidth ); + + // last chance that never fails: custom field property + field.setCustomProperty(propertyName, value); + } + + KexiDBWarn << "KexiDB::setFieldProperty() property \"" << propertyName << "\" not found!" << endl; + return false; +#undef SET_BOOLEAN_FLAG +#undef GET_INT +} + +int KexiDB::loadIntPropertyValueFromDom( const QDomNode& node, bool* ok ) +{ + QCString valueType = node.nodeName().latin1(); + if (valueType.isEmpty() || valueType!="number") { + if (ok) + *ok = false; + return 0; + } + const QString text( QDomNode(node).toElement().text() ); + int val = text.toInt(ok); + return val; +} + +QString KexiDB::loadStringPropertyValueFromDom( const QDomNode& node, bool* ok ) +{ + QCString valueType = node.nodeName().latin1(); + if (valueType!="string") { + if (ok) + *ok = false; + return 0; + } + return QDomNode(node).toElement().text(); +} + +QVariant KexiDB::loadPropertyValueFromDom( const QDomNode& node ) +{ + QCString valueType = node.nodeName().latin1(); + if (valueType.isEmpty()) + return QVariant(); + const QString text( QDomNode(node).toElement().text() ); + bool ok; + if (valueType == "string") { + return text; + } + else if (valueType == "cstring") { + return QCString(text.latin1()); + } + else if (valueType == "number") { // integer or double + if (text.find('.')!=-1) { + double val = text.toDouble(&ok); + if (ok) + return val; + } + else { + const int val = text.toInt(&ok); + if (ok) + return val; + const Q_LLONG valLong = text.toLongLong(&ok); + if (ok) + return valLong; + } + } + else if (valueType == "bool") { + return QVariant(text.lower()=="true" || text=="1", 1); + } +//! @todo add more QVariant types + KexiDBWarn << "loadPropertyValueFromDom(): unknown type '" << valueType << "'" << endl; + return QVariant(); +} + +QDomElement KexiDB::saveNumberElementToDom(QDomDocument& doc, QDomElement& parentEl, + const QString& elementName, int value) +{ + QDomElement el( doc.createElement(elementName) ); + parentEl.appendChild( el ); + QDomElement numberEl( doc.createElement("number") ); + el.appendChild( numberEl ); + numberEl.appendChild( doc.createTextNode( QString::number(value) ) ); + return el; +} + +QDomElement KexiDB::saveBooleanElementToDom(QDomDocument& doc, QDomElement& parentEl, + const QString& elementName, bool value) +{ + QDomElement el( doc.createElement(elementName) ); + parentEl.appendChild( el ); + QDomElement numberEl( doc.createElement("bool") ); + el.appendChild( numberEl ); + numberEl.appendChild( doc.createTextNode( + value ? QString::fromLatin1("true") : QString::fromLatin1("false") ) ); + return el; +} + +//! Used in KexiDB::emptyValueForType() +static KStaticDeleter< QValueVector<QVariant> > KexiDB_emptyValueForTypeCacheDeleter; +QValueVector<QVariant> *KexiDB_emptyValueForTypeCache = 0; + +QVariant KexiDB::emptyValueForType( KexiDB::Field::Type type ) +{ + if (!KexiDB_emptyValueForTypeCache) { + KexiDB_emptyValueForTypeCacheDeleter.setObject( KexiDB_emptyValueForTypeCache, + new QValueVector<QVariant>(int(Field::LastType)+1) ); +#define ADD(t, value) (*KexiDB_emptyValueForTypeCache)[t]=value; + ADD(Field::Byte, 0); + ADD(Field::ShortInteger, 0); + ADD(Field::Integer, 0); + ADD(Field::BigInteger, 0); + ADD(Field::Boolean, QVariant(false, 0)); + ADD(Field::Float, 0.0); + ADD(Field::Double, 0.0); +//! @todo ok? we have no better defaults + ADD(Field::Text, QString(" ")); + ADD(Field::LongText, QString(" ")); + ADD(Field::BLOB, QByteArray()); +#undef ADD + } + const QVariant val( KexiDB_emptyValueForTypeCache->at( + (type<=Field::LastType) ? type : Field::InvalidType) ); + if (!val.isNull()) + return val; + else { //special cases + if (type==Field::Date) + return QDate::currentDate(); + if (type==Field::DateTime) + return QDateTime::currentDateTime(); + if (type==Field::Time) + return QTime::currentTime(); + } + KexiDBWarn << "KexiDB::emptyValueForType() no value for type " + << Field::typeName(type) << endl; + return QVariant(); +} + +//! Used in KexiDB::notEmptyValueForType() +static KStaticDeleter< QValueVector<QVariant> > KexiDB_notEmptyValueForTypeCacheDeleter; +QValueVector<QVariant> *KexiDB_notEmptyValueForTypeCache = 0; + +QVariant KexiDB::notEmptyValueForType( KexiDB::Field::Type type ) +{ + if (!KexiDB_notEmptyValueForTypeCache) { + KexiDB_notEmptyValueForTypeCacheDeleter.setObject( KexiDB_notEmptyValueForTypeCache, + new QValueVector<QVariant>(int(Field::LastType)+1) ); +#define ADD(t, value) (*KexiDB_notEmptyValueForTypeCache)[t]=value; + // copy most of the values + for (int i = int(Field::InvalidType) + 1; i<=Field::LastType; i++) { + if (i==Field::Date || i==Field::DateTime || i==Field::Time) + continue; //'current' value will be returned + if (i==Field::Text || i==Field::LongText) { + ADD(i, QVariant(QString(""))); + continue; + } + if (i==Field::BLOB) { +//! @todo blobs will contain other mime types too + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + QPixmap pm(SmallIcon("filenew")); + pm.save( &buffer, "PNG"/*! @todo default? */ ); + ADD(i, ba); + continue; + } + ADD(i, KexiDB::emptyValueForType((Field::Type)i)); + } +#undef ADD + } + const QVariant val( KexiDB_notEmptyValueForTypeCache->at( + (type<=Field::LastType) ? type : Field::InvalidType) ); + if (!val.isNull()) + return val; + else { //special cases + if (type==Field::Date) + return QDate::currentDate(); + if (type==Field::DateTime) + return QDateTime::currentDateTime(); + if (type==Field::Time) + return QTime::currentTime(); + } + KexiDBWarn << "KexiDB::notEmptyValueForType() no value for type " + << Field::typeName(type) << endl; + return QVariant(); +} + +QString KexiDB::escapeBLOB(const QByteArray& array, BLOBEscapingType type) +{ + const int size = array.size(); + if (size==0) + return QString::null; + int escaped_length = size*2; + if (type == BLOBEscape0xHex || type == BLOBEscapeOctal) + escaped_length += 2/*0x or X'*/; + else if (type == BLOBEscapeXHex) + escaped_length += 3; //X' + ' + QString str; + str.reserve(escaped_length); + if (str.capacity() < (uint)escaped_length) { + KexiDBWarn << "KexiDB::Driver::escapeBLOB(): no enough memory (cannot allocate "<< + escaped_length<<" chars)" << endl; + return QString::null; + } + if (type == BLOBEscapeXHex) + str = QString::fromLatin1("X'"); + else if (type == BLOBEscape0xHex) + str = QString::fromLatin1("0x"); + else if (type == BLOBEscapeOctal) + str = QString::fromLatin1("'"); + + int new_length = str.length(); //after X' or 0x, etc. + if (type == BLOBEscapeOctal) { + // only escape nonprintable characters as in Table 8-7: + // http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html + // i.e. escape for bytes: < 32, >= 127, 39 ('), 92(\). + for (int i = 0; i < size; i++) { + const unsigned char val = array[i]; + if (val<32 || val>=127 || val==39 || val==92) { + str[new_length++] = '\\'; + str[new_length++] = '\\'; + str[new_length++] = '0' + val/64; + str[new_length++] = '0' + (val % 64) / 8; + str[new_length++] = '0' + val % 8; + } + else { + str[new_length++] = val; + } + } + } + else { + for (int i = 0; i < size; i++) { + const unsigned char val = array[i]; + str[new_length++] = (val/16) < 10 ? ('0'+(val/16)) : ('A'+(val/16)-10); + str[new_length++] = (val%16) < 10 ? ('0'+(val%16)) : ('A'+(val%16)-10); + } + } + if (type == BLOBEscapeXHex || type == BLOBEscapeOctal) + str[new_length++] = '\''; + return str; +} + +QByteArray KexiDB::pgsqlByteaToByteArray(const char* data, int length) +{ + QByteArray array; + int output=0; + for (int pass=0; pass<2; pass++) {//2 passes to avoid allocating buffer twice: + // 0: count #of chars; 1: copy data + const char* s = data; + const char* end = s + length; + if (pass==1) { + KexiDBDbg << "processBinaryData(): real size == " << output << endl; + array.resize(output); + output=0; + } + for (int input=0; s < end; output++) { + // KexiDBDbg<<(int)s[0]<<" "<<(int)s[1]<<" "<<(int)s[2]<<" "<<(int)s[3]<<" "<<(int)s[4]<<endl; + if (s[0]=='\\' && (s+1)<end) { + //special cases as in http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html + if (s[1]=='\'') {// \' + if (pass==1) + array[output] = '\''; + s+=2; + } + else if (s[1]=='\\') { // 2 backslashes + if (pass==1) + array[output] = '\\'; + s+=2; + } + else if ((input+3)<length) {// \\xyz where xyz are 3 octal digits + if (pass==1) + array[output] = char( (int(s[1]-'0')*8+int(s[2]-'0'))*8+int(s[3]-'0') ); + s+=4; + } + else { + KexiDBDrvWarn << "processBinaryData(): no octal value after backslash" << endl; + s++; + } + } + else { + if (pass==1) + array[output] = s[0]; + s++; + } + // KexiDBDbg<<output<<": "<<(int)array[output]<<endl; + } + } + return array; +} + +QString KexiDB::variantToString( const QVariant& v ) +{ + if (v.type()==QVariant::ByteArray) + return KexiDB::escapeBLOB(v.toByteArray(), KexiDB::BLOBEscapeHex); + return v.toString(); +} + +QVariant KexiDB::stringToVariant( const QString& s, QVariant::Type type, bool &ok ) +{ + if (s.isNull()) { + ok = true; + return QVariant(); + } + if (QVariant::Invalid==type) { + ok = false; + return QVariant(); + } + if (type==QVariant::ByteArray) {//special case: hex string + const uint len = s.length(); + QByteArray ba(len/2 + len%2); + for (uint i=0; i<(len-1); i+=2) { + int c = s.mid(i,2).toInt(&ok, 16); + if (!ok) { + KexiDBWarn << "KexiDB::stringToVariant(): Error in digit " << i << endl; + return QVariant(); + } + ba[i/2] = (char)c; + } + ok = true; + return ba; + } + QVariant result(s); + if (!result.cast( type )) { + ok = false; + return QVariant(); + } + ok = true; + return result; +} + +bool KexiDB::isDefaultValueAllowed( KexiDB::Field* field ) +{ + return field && !field->isUniqueKey(); +} + +void KexiDB::getLimitsForType(Field::Type type, int &minValue, int &maxValue) +{ + switch (type) { + case Field::Byte: +//! @todo always ok? + minValue = 0; + maxValue = 255; + break; + case Field::ShortInteger: + minValue = -32768; + maxValue = 32767; + break; + case Field::Integer: + case Field::BigInteger: //cannot return anything larger + default: + minValue = (int)-0x07FFFFFFF; + maxValue = (int)(0x080000000-1); + } +} + +void KexiDB::debugRowData(const RowData& rowData) +{ + KexiDBDbg << QString("ROW DATA (%1 columns):").arg(rowData.count()) << endl; + foreach(RowData::ConstIterator, it, rowData) + KexiDBDbg << "- " << (*it) << endl; +} + +Field::Type KexiDB::maximumForIntegerTypes(Field::Type t1, Field::Type t2) +{ + if (!Field::isIntegerType(t1) || !Field::isIntegerType(t2)) + return Field::InvalidType; + if (t1==t2) + return t2; + if (t1==Field::ShortInteger && t2!=Field::Integer && t2!=Field::BigInteger) + return t1; + if (t1==Field::Integer && t2!=Field::BigInteger) + return t1; + if (t1==Field::BigInteger) + return t1; + return KexiDB::maximumForIntegerTypes(t2, t1); //swap +} + +QString KexiDB::simplifiedTypeName(const Field& field) +{ + if (field.isNumericType()) + return i18n("Number"); //simplify + else if (field.type() == Field::BLOB) +//! @todo support names of other BLOB subtypes + return i18n("Image"); //simplify + + return field.typeGroupName(); +} + +#include "utils_p.moc" diff --git a/kexi/kexidb/utils.h b/kexi/kexidb/utils.h new file mode 100644 index 00000000..a455f5b8 --- /dev/null +++ b/kexi/kexidb/utils.h @@ -0,0 +1,476 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + + +#ifndef KEXIDB_UTILS_H +#define KEXIDB_UTILS_H + +#include <qvaluelist.h> +#include <qvariant.h> + +#include <kexidb/connection.h> +#include <kexidb/driver.h> + +class QDomNode; +class QDomElement; +class QDomDocument; + +namespace KexiDB +{ + //! for convenience + inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, TableSchema *table, + const QString &keyname, const QString &keyval) + { + return table!=0 && conn.executeSQL("DELETE FROM " + table->name() + " WHERE " + + keyname + "=" + conn.driver()->valueToSQL( Field::Text, QVariant(keyval) )); + } + + inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, const QString &tableName, + const QString &keyname, const QString &keyval) + { + return conn.executeSQL("DELETE FROM " + tableName + " WHERE " + + keyname + "=" + conn.driver()->valueToSQL( Field::Text, QVariant(keyval) )); + } + + inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, TableSchema *table, + const QString &keyname, int keyval) + { + return table!=0 && conn.executeSQL("DELETE FROM " + table->name() + " WHERE " + + keyname + "=" + conn.driver()->valueToSQL( Field::Integer, QVariant(keyval) )); + } + + inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, const QString &tableName, + const QString &keyname, int keyval) + { + return conn.executeSQL("DELETE FROM " + tableName + " WHERE " + + keyname + "=" + conn.driver()->valueToSQL( Field::Integer, QVariant(keyval) )); + } + + /*! Delete row with two generic criterias. */ + inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, const QString &tableName, + const QString &keyname1, Field::Type keytype1, const QVariant& keyval1, + const QString &keyname2, Field::Type keytype2, const QVariant& keyval2) + { + return conn.executeSQL("DELETE FROM " + tableName + " WHERE " + + keyname1 + "=" + conn.driver()->valueToSQL( keytype1, keyval1 ) + + " AND " + keyname2 + "=" + conn.driver()->valueToSQL( keytype2, keyval2 )); + } + + inline KEXI_DB_EXPORT bool replaceRow(Connection &conn, TableSchema *table, + const QString &keyname, const QString &keyval, const QString &valname, QVariant val, int ftype) + { + if (!table || !KexiDB::deleteRow(conn, table, keyname, keyval)) + return false; + return conn.executeSQL("INSERT INTO " + table->name() + + " (" + keyname + "," + valname + ") VALUES (" + + conn.driver()->valueToSQL( Field::Text, QVariant(keyval) ) + "," + + conn.driver()->valueToSQL( ftype, val) + ")"); + } + + typedef QValueList<uint> TypeGroupList; + + /*! \return list of types for type group \a typeGroup. */ + KEXI_DB_EXPORT const TypeGroupList typesForGroup(Field::TypeGroup typeGroup); + + /*! \return list of i18n'd type names for type group \a typeGroup. */ + KEXI_DB_EXPORT QStringList typeNamesForGroup(Field::TypeGroup typeGroup); + + /*! \return list of (not-i18n'd) type names for type group \a typeGroup. */ + KEXI_DB_EXPORT QStringList typeStringsForGroup(Field::TypeGroup typeGroup); + + /*! \return default field type for type group \a typeGroup, + for example, Field::Integer for Field::IntegerGroup. + It is used e.g. in KexiAlterTableDialog, to properly fill + 'type' property when user selects type group for a field. */ + KEXI_DB_EXPORT Field::Type defaultTypeForGroup(Field::TypeGroup typeGroup); + + /*! \return a slightly simplified type name for \a field. + For BLOB type it returns i18n'd "Image" string or other, depending on the mime type. + For numbers (either floating-point or integer) it returns i18n'd "Number: string. + For other types it the same string as Field::typeGroupName() is returned. */ +//! @todo support names of other BLOB subtypes + KEXI_DB_EXPORT QString simplifiedTypeName(const Field& field); + + /*! \return true if \a v represents an empty (but not null) value. + Values of some types (as for strings) can be both empty and not null. */ + inline bool isEmptyValue(Field *f, const QVariant &v) { + if (f->hasEmptyProperty() && v.toString().isEmpty() && !v.toString().isNull()) + return true; + return v.isNull(); + } + + /*! Sets \a msg to an error message retrieved from object \a obj, and \a details + to details of this error (server message and result number). + Does nothing if \a obj is null or no error occurred. + \a msg and \a details strings are not overwritten. + If \a msg is not empty, \a obj's error message is appended to \a details. + */ + KEXI_DB_EXPORT void getHTMLErrorMesage(Object* obj, QString& msg, QString &details); + + /*! This methods works like above, but appends both a message and a description + to \a msg. */ + KEXI_DB_EXPORT void getHTMLErrorMesage(Object* obj, QString& msg); + + /*! This methods works like above, but works on \a result's members instead. */ + KEXI_DB_EXPORT void getHTMLErrorMesage(Object* obj, ResultInfo *result); + + /*! Function useful for building WHERE parts of sql statements. + Constructs an sql string like "fielname = value" for specific \a drv driver, + field type \a t, \a fieldName and \a value. If \a value is null, "fieldname is NULL" + string is returned. */ + inline KEXI_DB_EXPORT QString sqlWhere(Driver *drv, Field::Type t, + const QString fieldName, const QVariant value) + { + if (value.isNull()) + return fieldName + " is NULL"; + return fieldName + "=" + drv->valueToSQL( t, value ); + } + + /*! \return identifier for object \a objName of type \a objType + or 0 if such object does not exist. */ + KEXI_DB_EXPORT int idForObjectName( Connection &conn, const QString& objName, int objType ); + + /*! Variant class providing a pointer to table or query. */ + class KEXI_DB_EXPORT TableOrQuerySchema { + public: + /*! Creates a new TableOrQuerySchema variant object, retrieving table or query schema + using \a conn connection and \a name. If both table and query exists for \a name, + table has priority over query. + You should check whether a query or table has been found by testing + (query() || table()) expression. */ + TableOrQuerySchema(Connection *conn, const QCString& name); + + /*! Creates a new TableOrQuerySchema variant object, retrieving table or query schema + using \a conn connection and \a name. If \a table is true, \a name is assumed + to be a table name, otherwise \a name is assumed to be a query name. + You should check whether a query or table has been found by testing + (query() || table()) expression. */ + TableOrQuerySchema(Connection *conn, const QCString& name, bool table); + + /*! Creates a new TableOrQuerySchema variant object. \a tableOrQuery must be of + class TableSchema or QuerySchema. + You should check whether a query or table has been found by testing + (query() || table()) expression. */ + TableOrQuerySchema(FieldList &tableOrQuery); + + /*! Creates a new TableOrQuerySchema variant object, retrieving table or query schema + using \a conn connection and \a id. + You should check whether a query or table has been found by testing + (query() || table()) expression. */ + TableOrQuerySchema(Connection *conn, int id); + + /*! Creates a new TableOrQuerySchema variant object, keeping a pointer so \a table + object. */ + TableOrQuerySchema(TableSchema* table); + + /*! Creates a new TableOrQuerySchema variant object, keeping a pointer so \a query + object. */ + TableOrQuerySchema(QuerySchema* query); + + //! \return a pointer to the query if it's provided + QuerySchema* query() const { return m_query; } + + //! \return a pointer to the table if it's provided + TableSchema* table() const { return m_table; } + + //! \return name of a query or table + QCString name() const; + + //! \return caption (if present) or name of the table/query + QString captionOrName() const; + + //! \return number of fields + uint fieldCount() const; + + //! \return all columns for the table or the query + const QueryColumnInfo::Vector columns(bool unique = false); + + /*! \return a field of the table or the query schema for name \a name + or 0 if there is no such field. */ + Field* field(const QString& name); + + /*! Like Field* field(const QString& name); + but returns all information associated with field/column \a name. */ + QueryColumnInfo* columnInfo(const QString& name); + + /*! \return connection object, for table or query or 0 if there's no table or query defined. */ + Connection* connection() const; + + /*! \return String for debugging purposes. */ + QString debugString(); + + /*! Shows debug information about table or query. */ + void debug(); + + protected: + QCString m_name; //!< the name is kept here because m_table and m_table can be 0 + //! and we still want name() and acptionOrName() work. + TableSchema* m_table; + QuerySchema* m_query; + }; + +//! @todo perhaps use Q_ULLONG here? + /*! \return number of rows that can be retrieved after executing \a sql statement + within a connection \a conn. The statement should be of type SELECT. + For SQL data sources it does not fetch any records, only "COUNT(*)" + SQL aggregation is used at the backed. + -1 is returned if error occured. */ + int rowCount(Connection &conn, const QString& sql); + +//! @todo perhaps use Q_ULLONG here? + /*! \return number of rows that can be retrieved from \a tableSchema. + The table must be created or retrieved using a Connection object, + i.e. tableSchema.connection() must not return 0. + For SQL data sources it does not fetch any records, only "COUNT(*)" + SQL aggregation is used at the backed. + -1 is returned if error occurred. */ + KEXI_DB_EXPORT int rowCount(const TableSchema& tableSchema); + +//! @todo perhaps use Q_ULLONG here? + /*! Like above but operates on a query schema. */ + KEXI_DB_EXPORT int rowCount(QuerySchema& querySchema); + +//! @todo perhaps use Q_ULLONG here? + /*! Like above but operates on a table or query schema variant. */ + KEXI_DB_EXPORT int rowCount(TableOrQuerySchema& tableOrQuery); + + /*! \return a number of columns that can be retrieved from table or query schema. + In case of query, expanded fields are counted. Can return -1 if \a tableOrQuery + has neither table or query assigned. */ + KEXI_DB_EXPORT int fieldCount(TableOrQuerySchema& tableOrQuery); + + /*! shows connection test dialog with a progress bar indicating connection testing + (within a second thread). + \a data is used to perform a (temporary) test connection. \a msgHandler is used to display errors. + On successful connecting, a message is displayed. After testing, temporary connection is closed. */ + KEXI_DB_EXPORT void connectionTestDialog(QWidget* parent, const ConnectionData& data, + MessageHandler& msgHandler); + + /*! Saves connection data \a data into \a map. */ + KEXI_DB_EXPORT QMap<QString,QString> toMap( const ConnectionData& data ); + + /*! Restores connection data \a data from \a map. */ + KEXI_DB_EXPORT void fromMap( const QMap<QString,QString>& map, ConnectionData& data ); + + //! Used in splitToTableAndFieldParts(). + enum SplitToTableAndFieldPartsOptions { + FailIfNoTableOrFieldName = 0, //!< default value for splitToTableAndFieldParts() + SetFieldNameIfNoTableName = 1 //!< see splitToTableAndFieldParts() + }; + + /*! Splits \a string like "table.field" into "table" and "field" parts. + On success, a table name is passed to \a tableName and a field name is passed to \a fieldName. + The function fails if either: + - \a string is empty, or + - \a string does not contain '.' character and \a option is FailIfNoTableOrFieldName + (the default), or + - '.' character is the first of last character of \a string (in this case table name + or field name could become empty what is not allowed). + + If \a option is SetFieldNameIfNoTableName and \a string does not contain '.', + \a string is passed to \a fieldName and \a tableName is set to QString::null + without failure. + + If function fails, \a tableName and \a fieldName remain unchanged. + \return true on success. */ + KEXI_DB_EXPORT bool splitToTableAndFieldParts(const QString& string, + QString& tableName, QString& fieldName, + SplitToTableAndFieldPartsOptions option = FailIfNoTableOrFieldName); + + /*! \return true if \a type supports "visibleDecimalPlaces" property. */ + KEXI_DB_EXPORT bool supportsVisibleDecimalPlacesProperty(Field::Type type); + + /*! \return string constructed by converting \a value. + * If \a decimalPlaces is < 0, all meaningful fractional digits are returned. + * If \a automatically is 0, just integer part is returned. + * If \a automatically is > 0, fractional part should take exactly + N digits: if the fractional part is shorter than N, additional zeros are appended. + For example, "12.345" becomes "12.345000" if N=6. + + No rounding is actually performed. + KLocale::formatNumber() and KLocale::decimalSymbol() are used to get locale settings. + + @see KexiDB::Field::visibleDecimalPlaces() */ + KEXI_DB_EXPORT QString formatNumberForVisibleDecimalPlaces(double value, int decimalPlaces); + + //! \return true if \a propertyName is a builtin field property. + KEXI_DB_EXPORT bool isBuiltinTableFieldProperty( const QCString& propertyName ); + + //! \return true if \a propertyName is an extended field property. + KEXI_DB_EXPORT bool isExtendedTableFieldProperty( const QCString& propertyName ); + + /*! \return type of field for integer value \a type. + If \a type cannot be casted to KexiDB::Field::Type, KexiDB::Field::InvalidType is returned. + This can be used when type information is deserialized from a string or QVariant. */ + KEXI_DB_EXPORT Field::Type intToFieldType( int type ); + + /*! Sets property values for \a field. \return true if all the values are valid and allowed. + On failure contents of \a field is undefined. + Properties coming from extended schema are also supported. + This function is used e.g. by AlterTableHandler when property information comes in form of text. + */ + KEXI_DB_EXPORT bool setFieldProperties( Field& field, const QMap<QCString, QVariant>& values ); + + /*! Sets property value for \a field. \return true if the property has been found and + the value is valid for this property. On failure contents of \a field is undefined. + Properties coming from extended schema are also supported as well as + QVariant customProperty(const QString& propertyName) const; + + This function is used e.g. by AlterTableHandler when property information comes in form of text. + */ + KEXI_DB_EXPORT bool setFieldProperty(Field& field, const QCString& propertyName, + const QVariant& value); + + /*! @return property value loaded from a DOM \a node, written in a QtDesigner-like + notation: <number>int</number> or <bool>bool</bool>, etc. Supported types are + "string", "cstring", "bool", "number". For invalid values null QVariant is returned. + You can check the validity of the returned value using QVariant::type(). */ + KEXI_DB_EXPORT QVariant loadPropertyValueFromDom( const QDomNode& node ); + + /*! Convenience version of loadPropertyValueFromDom(). \return int value. */ + KEXI_DB_EXPORT int loadIntPropertyValueFromDom( const QDomNode& node, bool* ok ); + + /*! Convenience version of loadPropertyValueFromDom(). \return QString value. */ + KEXI_DB_EXPORT QString loadStringPropertyValueFromDom( const QDomNode& node, bool* ok ); + + /*! Saves integer element for value \a value to \a doc document within parent element + \a parentEl. The value will be enclosed in "number" element and "elementName" element. + Example: saveNumberElementToDom(doc, parentEl, "height", 15) will create + \code + <height><number>15</number></height> + \endcode + \return the reference to element created with tag elementName. */ + KEXI_DB_EXPORT QDomElement saveNumberElementToDom(QDomDocument& doc, QDomElement& parentEl, + const QString& elementName, int value); + + /*! Saves boolean element for value \a value to \a doc document within parent element + \a parentEl. Like saveNumberElementToDom() but creates "bool" tags. True/false values will be + saved as "true"/"false" strings. + \return the reference to element created with tag elementName. */ + KEXI_DB_EXPORT QDomElement saveBooleanElementToDom(QDomDocument& doc, QDomElement& parentEl, + const QString& elementName, bool value); + + /*! \return an empty value that can be set for a database field of type \a type having + "null" property set. Empty string is returned for text type, 0 for integer + or floating-point types, false for boolean type, empty null byte array for BLOB type. + For date, time and date/time types current date, time, date+time is returned, respectively. + Returns null QVariant for unsupported values like KexiDB::Field::InvalidType. + This function is efficient (uses a cache) and is heavily used by the AlterTableHandler + for filling new columns. */ + KEXI_DB_EXPORT QVariant emptyValueForType( Field::Type type ); + + /*! \return a value that can be set for a database field of type \a type having + "notEmpty" property set. It works in a similar way as + @ref QVariant emptyValueForType( KexiDB::Field::Type type ) with the following differences: + - " " string (a single space) is returned for Text and LongText types + - a byte array with saved "filenew" PNG image (icon) for BLOB type + Returns null QVariant for unsupported values like KexiDB::Field::InvalidType. + This function is efficient (uses a cache) and is heavily used by the AlterTableHandler + for filling new columns. */ + KEXI_DB_EXPORT QVariant notEmptyValueForType( Field::Type type ); + + //! Escaping types used in escapeBLOB(). + enum BLOBEscapingType { + BLOBEscapeXHex = 1, //!< escaping like X'1FAD', used by sqlite (hex numbers) + BLOBEscape0xHex, //!< escaping like 0x1FAD, used by mysql (hex numbers) + BLOBEscapeHex, //!< escaping like 1FAD without quotes or prefixes + BLOBEscapeOctal //!< escaping like 'zk\\000$x', used by pgsql + //!< (only non-printable characters are escaped using octal numbers) + //!< See http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html + }; + +//! @todo reverse function for BLOBEscapeOctal is available: processBinaryData() in pqxxcursor.cpp - move it here + /*! \return a string containing escaped, printable representation of \a array. + Escaping is controlled by \a type. For empty array QString::null is returned, + so if you want to use this function in an SQL statement, empty arrays should be + detected and "NULL" string should be put instead. + This is helper, used in Driver::escapeBLOB() and KexiDB::variantToString(). */ + KEXI_DB_EXPORT QString escapeBLOB(const QByteArray& array, BLOBEscapingType type); + + /*! \return byte array converted from \a data of length \a length. + \a data is escaped in format used by PostgreSQL's bytea datatype + described at http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html + This function is used by PostgreSQL KexiDB and migration drivers. */ + KEXI_DB_EXPORT QByteArray pgsqlByteaToByteArray(const char* data, int length); + + /*! \return string value serialized from a variant value \a v. + This functions works like QVariant::toString() except the case when \a v is of type ByteArray. + In this case KexiDB::escapeBLOB(v.toByteArray(), KexiDB::BLOBEscapeHex) is used. + This function is needed for handling values of random type, for example "defaultValue" + property of table fields can contain value of any type. + Note: the returned string is an unescaped string. */ + KEXI_DB_EXPORT QString variantToString( const QVariant& v ); + + /*! \return variant value of type \a type for a string \a s that was previously serialized using + \ref variantToString( const QVariant& v ) function. + \a ok is set to the result of the operation. */ + KEXI_DB_EXPORT QVariant stringToVariant( const QString& s, QVariant::Type type, bool &ok ); + + /*! \return true if setting default value for \a field field is allowed. Fields with unique + (and thus primary key) flags set do not accept default values. + False is returned aslo if \a field is 0. */ + KEXI_DB_EXPORT bool isDefaultValueAllowed( Field* field ); + + /*! Gets limits for values of type \a type. The result is put into \a minValue and \a maxValue. + Supported types are Byte, ShortInteger, Integer and BigInteger + Results for BigInteger or non-integer types are the same as for Integer due + to limitation of int type. + Signed integers are assumed. */ +//! @todo add support for unsigned flag + KEXI_DB_EXPORT void getLimitsForType(Field::Type type, int &minValue, int &maxValue); + + /*! Shows debug information about \a rowData row data. */ + KEXI_DB_EXPORT void debugRowData(const RowData& rowData); + + /*! \return type that's maximum of two integer types \a t1 and \a t2, e.g. Integer for (Byte, Integer). + If one of the types is not of the integer group, Field::InvalidType is returned. */ + KEXI_DB_EXPORT Field::Type maximumForIntegerTypes(Field::Type t1, Field::Type t2); + + /*! \return QVariant value converted from null-terminated \a data string. + In case of BLOB type, \a data is not nul lterminated, so passing length is needed. */ + inline QVariant cstringToVariant(const char* data, KexiDB::Field* f, int length = -1) + { + if (!data) + return QVariant(); + // from mo st to least frequently used types: + + if (!f || f->isTextType()) + return QString::fromUtf8(data, length); + if (f->isIntegerType()) { + if (f->type()==KexiDB::Field::BigInteger) + return QVariant( QString::fromLatin1(data, length).toLongLong() ); + return QVariant( QString::fromLatin1(data, length).toInt() ); + } + if (f->isFPNumericType()) + return QString::fromLatin1(data, length).toDouble(); + if (f->type()==KexiDB::Field::BLOB) { + QByteArray ba; + ba.duplicate(data, length); + return ba; + } + // the default +//! @todo date/time? + QVariant result(QString::fromUtf8(data, length)); + if (!result.cast( KexiDB::Field::variantType(f->type()) )) + return QVariant(); + return result; + } +} + +#endif diff --git a/kexi/kexidb/utils_p.h b/kexi/kexidb/utils_p.h new file mode 100644 index 00000000..6129196b --- /dev/null +++ b/kexi/kexidb/utils_p.h @@ -0,0 +1,59 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDB_UTILS_P_H +#define KEXIDB_UTILS_P_H + +#include <qtimer.h> +#include <qwaitcondition.h> + +#include <kprogress.h> + +#include "msghandler.h" + +class ConnectionTestThread; + +class ConnectionTestDialog : protected KProgressDialog +{ + Q_OBJECT + public: + ConnectionTestDialog(QWidget* parent, + const KexiDB::ConnectionData& data, KexiDB::MessageHandler& msgHandler); + virtual ~ConnectionTestDialog(); + + int exec(); + + void error(KexiDB::Object *obj); + + protected slots: + void slotTimeout(); + virtual void slotCancel(); + + protected: + ConnectionTestThread* m_thread; + KexiDB::ConnectionData m_connData; + QTimer m_timer; + KexiDB::MessageHandler* m_msgHandler; + uint m_elapsedTime; + KexiDB::Object *m_errorObj; + QWaitCondition m_wait; + bool m_stopWaiting : 1; +}; + +#endif |