summaryrefslogtreecommitdiffstats
path: root/kexi/kexidb
diff options
context:
space:
mode:
Diffstat (limited to 'kexi/kexidb')
-rw-r--r--kexi/kexidb/Makefile.am64
-rw-r--r--kexi/kexidb/admin.cpp42
-rw-r--r--kexi/kexidb/admin.h56
-rw-r--r--kexi/kexidb/alter.cpp1115
-rw-r--r--kexi/kexidb/alter.h468
-rw-r--r--kexi/kexidb/common.pro8
-rw-r--r--kexi/kexidb/connection.cpp3552
-rw-r--r--kexi/kexidb/connection.h1198
-rw-r--r--kexi/kexidb/connection_p.h40
-rw-r--r--kexi/kexidb/connectiondata.cpp114
-rw-r--r--kexi/kexidb/connectiondata.h239
-rw-r--r--kexi/kexidb/cursor.cpp571
-rw-r--r--kexi/kexidb/cursor.h365
-rw-r--r--kexi/kexidb/cursor_p.h40
-rw-r--r--kexi/kexidb/dbobjectnamevalidator.cpp51
-rw-r--r--kexi/kexidb/dbobjectnamevalidator.h49
-rw-r--r--kexi/kexidb/dbproperties.cpp148
-rw-r--r--kexi/kexidb/dbproperties.h67
-rw-r--r--kexi/kexidb/driver.cpp367
-rw-r--r--kexi/kexidb/driver.h375
-rw-r--r--kexi/kexidb/driver_p.cpp129
-rw-r--r--kexi/kexidb/driver_p.h262
-rw-r--r--kexi/kexidb/drivermanager.cpp435
-rw-r--r--kexi/kexidb/drivermanager.h104
-rw-r--r--kexi/kexidb/drivermanager_p.h94
-rw-r--r--kexi/kexidb/drivers/Makefile.am11
-rw-r--r--kexi/kexidb/drivers/common.pro11
-rw-r--r--kexi/kexidb/drivers/configure.in.bot99
-rw-r--r--kexi/kexidb/drivers/configure.in.in244
-rw-r--r--kexi/kexidb/drivers/drivers.pro7
-rw-r--r--kexi/kexidb/drivers/mySQL/Makefile.am33
-rw-r--r--kexi/kexidb/drivers/mySQL/kexidb_mysqldriver.desktop11
-rw-r--r--kexi/kexidb/drivers/mySQL/mySQL.pro28
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlconnection.cpp208
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlconnection.h87
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlconnection_p.cpp175
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlconnection_p.h101
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlcursor.cpp218
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlcursor.h68
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqldriver.cpp212
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqldriver.h59
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlkeywords.cpp338
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.cpp298
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.h56
-rw-r--r--kexi/kexidb/drivers/odbc/Makefile.am21
-rw-r--r--kexi/kexidb/drivers/odbc/kexidb_odbcdriver.desktop54
-rw-r--r--kexi/kexidb/drivers/odbc/odbcconnection.cpp153
-rw-r--r--kexi/kexidb/drivers/odbc/odbcconnection.h92
-rw-r--r--kexi/kexidb/drivers/odbc/odbcdriver.cpp108
-rw-r--r--kexi/kexidb/drivers/odbc/odbcdriver.h73
-rw-r--r--kexi/kexidb/drivers/pqxx/Makefile.am22
-rw-r--r--kexi/kexidb/drivers/pqxx/README18
-rw-r--r--kexi/kexidb/drivers/pqxx/kexidb_pqxxsqldriver.desktop11
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxconnection.cpp448
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxconnection.h104
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxconnection_p.cpp51
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxconnection_p.h63
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxcursor.cpp339
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxcursor.h110
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxdriver.cpp181
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxdriver.h71
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxkeywords.cpp244
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.cpp56
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.h49
-rw-r--r--kexi/kexidb/drivers/sqlite/Makefile.am27
-rw-r--r--kexi/kexidb/drivers/sqlite/driver/sqlite.h687
-rw-r--r--kexi/kexidb/drivers/sqlite/kexidb_sqlite3driver.desktop56
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlite.pro10
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlite_common.pro16
-rw-r--r--kexi/kexidb/drivers/sqlite/sqliteadmin.cpp64
-rw-r--r--kexi/kexidb/drivers/sqlite/sqliteadmin.h36
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitealter.cpp114
-rw-r--r--kexi/kexidb/drivers/sqlite/sqliteconnection.cpp414
-rw-r--r--kexi/kexidb/drivers/sqlite/sqliteconnection.h125
-rw-r--r--kexi/kexidb/drivers/sqlite/sqliteconnection_p.h73
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitecursor.cpp567
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitecursor.h92
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitedriver.cpp159
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitedriver.h82
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitekeywords.cpp39
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.cpp242
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.h50
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitevacuum.cpp150
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitevacuum.h70
-rw-r--r--kexi/kexidb/drivers/sqlite2/Makefile.am31
-rw-r--r--kexi/kexidb/drivers/sqlite2/kexidb_sqlite2driver.desktop57
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlite2.pro12
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqliteadmin.cpp1
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqliteadmin.h1
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitealter.cpp1
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqliteconnection.cpp1
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqliteconnection.h2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqliteconnection_p.h2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitecursor.cpp2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitecursor.h2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitedriver.cpp2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitedriver.h2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitepreparedstatement.cpp1
-rw-r--r--kexi/kexidb/error.h138
-rw-r--r--kexi/kexidb/expression.cpp914
-rw-r--r--kexi/kexidb/expression.h311
-rw-r--r--kexi/kexidb/field.cpp726
-rw-r--r--kexi/kexidb/field.h632
-rw-r--r--kexi/kexidb/fieldlist.cpp278
-rw-r--r--kexi/kexidb/fieldlist.h175
-rw-r--r--kexi/kexidb/fieldvalidator.cpp100
-rw-r--r--kexi/kexidb/fieldvalidator.h49
-rw-r--r--kexi/kexidb/global.cpp55
-rw-r--r--kexi/kexidb/global.h171
-rw-r--r--kexi/kexidb/indexschema.cpp199
-rw-r--r--kexi/kexidb/indexschema.h209
-rw-r--r--kexi/kexidb/kexidb.pro51
-rw-r--r--kexi/kexidb/kexidb_driver.desktop73
-rw-r--r--kexi/kexidb/kexidb_export.h61
-rw-r--r--kexi/kexidb/keywords.cpp92
-rw-r--r--kexi/kexidb/lookupfieldschema.cpp394
-rw-r--r--kexi/kexidb/lookupfieldschema.h236
-rw-r--r--kexi/kexidb/msghandler.cpp62
-rw-r--r--kexi/kexidb/msghandler.h98
-rw-r--r--kexi/kexidb/object.cpp191
-rw-r--r--kexi/kexidb/object.h186
-rw-r--r--kexi/kexidb/parser/Makefile.am38
-rw-r--r--kexi/kexidb/parser/TODO9
-rwxr-xr-xkexi/kexidb/parser/extract_tokens.sh7
-rw-r--r--kexi/kexidb/parser/parser.cpp155
-rw-r--r--kexi/kexidb/parser/parser.h240
-rw-r--r--kexi/kexidb/parser/parser_p.cpp641
-rw-r--r--kexi/kexidb/parser/parser_p.h86
-rw-r--r--kexi/kexidb/parser/sqlparser.cpp3472
-rw-r--r--kexi/kexidb/parser/sqlparser.h778
-rw-r--r--kexi/kexidb/parser/sqlparser.y1368
-rw-r--r--kexi/kexidb/parser/sqlscanner.cpp2051
-rw-r--r--kexi/kexidb/parser/sqlscanner.l318
-rw-r--r--kexi/kexidb/parser/sqltypes.h80
-rw-r--r--kexi/kexidb/parser/tokens.cpp25
-rw-r--r--kexi/kexidb/preparedstatement.cpp136
-rw-r--r--kexi/kexidb/preparedstatement.h117
-rw-r--r--kexi/kexidb/queryschema.cpp1859
-rw-r--r--kexi/kexidb/queryschema.h832
-rw-r--r--kexi/kexidb/queryschemaparameter.cpp103
-rw-r--r--kexi/kexidb/queryschemaparameter.h69
-rw-r--r--kexi/kexidb/record.h71
-rw-r--r--kexi/kexidb/relationship.cpp201
-rw-r--r--kexi/kexidb/relationship.h156
-rw-r--r--kexi/kexidb/roweditbuffer.cpp129
-rw-r--r--kexi/kexidb/roweditbuffer.h136
-rw-r--r--kexi/kexidb/schemadata.cpp55
-rw-r--r--kexi/kexidb/schemadata.h92
-rw-r--r--kexi/kexidb/simplecommandlineapp.cpp228
-rw-r--r--kexi/kexidb/simplecommandlineapp.h86
-rw-r--r--kexi/kexidb/tableschema.cpp453
-rw-r--r--kexi/kexidb/tableschema.h210
-rw-r--r--kexi/kexidb/transaction.cpp165
-rw-r--r--kexi/kexidb/transaction.h159
-rw-r--r--kexi/kexidb/utils.cpp1262
-rw-r--r--kexi/kexidb/utils.h476
-rw-r--r--kexi/kexidb/utils_p.h59
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 : &paramValuesIt;
+ 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 &gt;= 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: &lt;number&gt;int&lt;/number&gt; or &lt;bool&gt;bool&lt;/bool&gt;, 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