diff options
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/db')
12 files changed, 97 insertions, 90 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp index 4b3165b..68c6ad7 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp @@ -177,22 +177,22 @@ ReadWriteLocker::Mode AbstractDb::getLockingMode(const QString &query, Flags fla return ReadWriteLocker::getMode(query, getDialect(), flags.testFlag(Flag::NO_LOCK)); } -QString AbstractDb::getName() +QString AbstractDb::getName() const { return name; } -QString AbstractDb::getPath() +QString AbstractDb::getPath() const { return path; } -quint8 AbstractDb::getVersion() +quint8 AbstractDb::getVersion() const { return version; } -Dialect AbstractDb::getDialect() +Dialect AbstractDb::getDialect() const { if (version == 2) return Dialect::Sqlite2; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h index 89edf03..3f2b4c0 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h @@ -45,10 +45,10 @@ class API_EXPORT AbstractDb : public Db virtual ~AbstractDb(); bool isOpen(); - QString getName(); - QString getPath(); - quint8 getVersion(); - Dialect getDialect(); + QString getName() const; + QString getPath() const; + quint8 getVersion() const; + Dialect getDialect() const; QString getEncoding(); QHash<QString,QVariant>& getConnectionOptions(); void setName(const QString& value); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h index 7a84ec2..9cb58aa 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h @@ -868,7 +868,8 @@ bool AbstractDb3<T>::Query::execInternal(const QList<QVariant>& args) if (res != T::OK) return false; - for (int paramIdx = 1; paramIdx <= queryWithParams.second; paramIdx++) + + for (int paramIdx = 1, argCount = args.size(); paramIdx <= queryWithParams.second && paramIdx <= argCount; paramIdx++) { res = bindParam(paramIdx, args[paramIdx-1]); if (res != T::OK) @@ -906,16 +907,25 @@ bool AbstractDb3<T>::Query::execInternal(const QHash<QString, QVariant>& args) if (res != T::OK) return false; - int paramIdx = 1; - foreach (const QString& paramName, queryWithParams.second) + int paramIdx = -1; + for (const QString& paramName : queryWithParams.second) { if (!args.contains(paramName)) { + qWarning() << "Could not bind parameter" << paramName << "because it was not found in passed arguments."; + setError(SqlErrorCode::OTHER_EXECUTION_ERROR, "Error while preparing statement: could not bind parameter " + paramName); + return false; + } + + paramIdx = T::bind_parameter_index(stmt, paramName.toUtf8().constData()); + if (paramIdx <= 0) + { + qWarning() << "Could not bind parameter" << paramName << "because it was not found in prepared statement."; setError(SqlErrorCode::OTHER_EXECUTION_ERROR, "Error while preparing statement: could not bind parameter " + paramName); return false; } - res = bindParam(paramIdx++, args[paramName]); + res = bindParam(paramIdx, args[paramName]); if (res != T::OK) { db->extractLastError(); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/db.cpp b/SQLiteStudio3/coreSQLiteStudio/db/db.cpp index c89349c..7676f30 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/db.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/db.cpp @@ -1,5 +1,6 @@ #include "db.h" #include <QMetaEnum> +#include <QDebug> Db::Db() { @@ -55,3 +56,10 @@ void Sqlite2ColumnDataTypeHelper::clearBinaryTypes() { binaryColumns.clear(); } + + +QDebug operator<<(QDebug dbg, const Db* db) +{ + dbg.nospace() << "<DB:" << db->getName() << ">"; + return dbg.space(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/db.h b/SQLiteStudio3/coreSQLiteStudio/db/db.h index e11a844..efadd41 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/db.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/db.h @@ -195,13 +195,13 @@ class API_EXPORT Db : public QObject, public Interruptable * @brief Gets database symbolic name. * @return Database symbolic name (as it was defined in call to DbManager#addDb() or DbManager#updateDb()). */ - virtual QString getName() = 0; + virtual QString getName() const = 0; /** * @brief Gets database file path. * @return Database file path (as it was defined in call to DbManager#addDb() or DbManager#updateDb()). */ - virtual QString getPath() = 0; + virtual QString getPath() const = 0; /** * @brief Gets SQLite version major number for this database. @@ -209,7 +209,7 @@ class API_EXPORT Db : public QObject, public Interruptable * * You don't have to open the database. This information is always available. */ - virtual quint8 getVersion() = 0; + virtual quint8 getVersion() const = 0; /** * @brief Gets database dialect. @@ -217,7 +217,7 @@ class API_EXPORT Db : public QObject, public Interruptable * * You don't have to open the database. This information is always available. */ - virtual Dialect getDialect() = 0; + virtual Dialect getDialect() const = 0; /** * @brief Gets database encoding. @@ -816,6 +816,8 @@ class API_EXPORT Db : public QObject, public Interruptable QDataStream &operator<<(QDataStream &out, const Db* myObj); QDataStream &operator>>(QDataStream &in, Db*& myObj); +QDebug operator<<(QDebug dbg, const Db* db); + Q_DECLARE_METATYPE(Db*) Q_DECLARE_OPERATORS_FOR_FLAGS(Db::Flags) diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h b/SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h index 7b594ef..e2ed66f 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h @@ -27,6 +27,11 @@ struct DbPluginOption { /** + * @brief Handler for custom path browser implemented by the plugin. + */ + typedef std::function<QString(const QString&)> CustomBrowseHandler; + + /** * @brief Option data type * * Determinates what kind of input widget will be added to DbDialog. @@ -39,7 +44,8 @@ struct DbPluginOption DOUBLE = 3, /**< QDoubleSpinBox will be added */ FILE = 4, /**< QLineEdit will be added */ PASSWORD = 5, /**< QLineEdit with value masking will be added */ - CHOICE = 6 /**< QComboBox will be added */ + CHOICE = 6, /**< QComboBox will be added */ + CUSTOM_PATH_BROWSE = 7 /**< File path browse button will be handled by the plugin. No additional editor will be added. */ }; /** @@ -92,6 +98,11 @@ struct DbPluginOption * @brief Expected data type for the option. */ Type type; + + /** + * @brief Handler for custom path browser implemented by the plugin. + */ + CustomBrowseHandler customBrowseHandler = nullptr; }; #endif // DBPLUGINOPTION_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp index e4810a1..bdf3ef6 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp @@ -12,22 +12,22 @@ bool InvalidDb::isOpen() return false; } -QString InvalidDb::getName() +QString InvalidDb::getName() const { return name; } -QString InvalidDb::getPath() +QString InvalidDb::getPath() const { return path; } -quint8 InvalidDb::getVersion() +quint8 InvalidDb::getVersion() const { return 0; } -Dialect InvalidDb::getDialect() +Dialect InvalidDb::getDialect() const { return Dialect::Sqlite3; } diff --git a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h index 759aa4c..a9d58e0 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h @@ -9,10 +9,10 @@ class API_EXPORT InvalidDb : public Db InvalidDb(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions); bool isOpen(); - QString getName(); - QString getPath(); - quint8 getVersion(); - Dialect getDialect(); + QString getName() const; + QString getPath() const; + quint8 getVersion() const; + Dialect getDialect() const; QString getEncoding(); QHash<QString, QVariant>& getConnectionOptions(); void setName(const QString& value); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp index 6acfb6f..287a8ce 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp @@ -40,14 +40,13 @@ bool QueryExecutorColumns::exec() core->resultColumns.clear(); // Count total rowId columns - int rowIdColCount = 0; for (const QueryExecutor::ResultRowIdColumnPtr& rowIdCol : context->rowIdColumns) - rowIdColCount += rowIdCol->queryExecutorAliasToColumn.size(); + rowIdColNames += rowIdCol->queryExecutorAliasToColumn.keys(); // Defining result columns QueryExecutor::ResultColumnPtr resultColumn; SqliteSelect::Core::ResultColumn* resultColumnForSelect = nullptr; - bool isRowIdColumn = false; + bool rowIdColumn = false; int i = 0; for (const SelectResolver::Column& col : columns) { @@ -55,15 +54,18 @@ bool QueryExecutorColumns::exec() resultColumn = getResultColumn(col); // Adding new result column to the query - isRowIdColumn = (i < rowIdColCount); - resultColumnForSelect = getResultColumnForSelect(resultColumn, col, isRowIdColumn); + rowIdColumn = isRowIdColumn(col.alias); + if (rowIdColumn && col.alias.contains(":")) + continue; // duplicate ROWID column provided by SelectResolver. See isRowIdColumn() for details. + + resultColumnForSelect = getResultColumnForSelect(resultColumn, col); if (!resultColumnForSelect) return false; resultColumnForSelect->setParent(core); core->resultColumns << resultColumnForSelect; - if (!isRowIdColumn) + if (!rowIdColumn) context->resultColumns << resultColumn; // store it in context for later usage by any step i++; @@ -89,7 +91,6 @@ QueryExecutor::ResultColumnPtr QueryExecutorColumns::getResultColumn(const Selec resultColumn->column = resolvedColumn.column; resultColumn->alias = resolvedColumn.alias; resultColumn->expression = true; - resultColumn->queryExecutorAlias = getNextColName(); } else { @@ -111,20 +112,20 @@ QueryExecutor::ResultColumnPtr QueryExecutorColumns::getResultColumn(const Selec resultColumn->tableAlias = resolvedColumn.tableAlias; resultColumn->alias = resolvedColumn.alias; resultColumn->displayName = resolvedColumn.displayName; + } - if (isRowIdColumnAlias(resultColumn->alias)) - { - resultColumn->queryExecutorAlias = resultColumn->alias; - } - else - { - resultColumn->queryExecutorAlias = getNextColName(); - } + if (isRowIdColumnAlias(resultColumn->alias)) + { + resultColumn->queryExecutorAlias = resultColumn->alias; + } + else + { + resultColumn->queryExecutorAlias = getNextColName(); } return resultColumn; } -SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect(const QueryExecutor::ResultColumnPtr& resultColumn, const SelectResolver::Column& col, bool rowIdColumn) +SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect(const QueryExecutor::ResultColumnPtr& resultColumn, const SelectResolver::Column& col) { SqliteSelect::Core::ResultColumn* selectResultColumn = new SqliteSelect::Core::ResultColumn(); @@ -164,28 +165,6 @@ SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect selectResultColumn->expr->table = resultColumn->table; } - -// // SQLite2 requires special treatment here. It won't allow selecting db.table.col from a subquery - it needs escaped ID that reflects db.table.col. -// if (dialect == Dialect::Sqlite2) -// { -// selectResultColumn->expr->rebuildTokens(); -// colString = wrapObjIfNeeded(selectResultColumn->expr->detokenize(), dialect); -// delete selectResultColumn->expr; -// selectResultColumn->expr = nullptr; - -// expr = parser.parseExpr(colString); -// if (!expr) -// { -// qWarning() << "Could not parse result column expr in SQLite2's second parsing phase:" << colString; -// if (parser.getErrors().size() > 0) -// qWarning() << "The error was:" << parser.getErrors().first()->getFrom() << ":" << parser.getErrors().first()->getMessage(); - -// return nullptr; -// } - -// expr->setParent(selectResultColumn); -// selectResultColumn->expr = expr; -// } } if (!col.alias.isNull()) @@ -193,7 +172,7 @@ SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect selectResultColumn->asKw = true; selectResultColumn->alias = col.alias; } - else if (rowIdColumn || resultColumn->expression) + else { selectResultColumn->asKw = true; selectResultColumn->alias = resultColumn->queryExecutorAlias; @@ -252,12 +231,10 @@ void QueryExecutorColumns::wrapWithAliasedColumns(SqliteSelect* select) // If alias was given, we use it. If it was anything but expression, we also use its display name, // because it's explicit column (no matter if from table, or table alias). baseColName = QString(); - if (!resCol->alias.isNull()) + if (!resCol->queryExecutorAlias.isNull()) baseColName = resCol->alias; - else if (dialect == Dialect::Sqlite3 && !resCol->expression) + else if (!resCol->expression) baseColName = resCol->column; - else if (dialect == Dialect::Sqlite2 && !resCol->expression) - baseColName = getAliasedColumnNameForSqlite2(resCol); if (!baseColName.isNull()) { @@ -279,20 +256,17 @@ void QueryExecutorColumns::wrapWithAliasedColumns(SqliteSelect* select) select->tokens = wrapSelect(select->tokens, outerColumns); } -QString QueryExecutorColumns::getAliasedColumnNameForSqlite2(const QueryExecutor::ResultColumnPtr& resCol) +bool QueryExecutorColumns::isRowIdColumn(const QString& columnAlias) { - QStringList colNameParts; - if (!resCol->table.isNull()) - { - if (!resCol->database.isNull()) - { - if (context->dbNameToAttach.containsLeft(resCol->database, Qt::CaseInsensitive)) - colNameParts << context->dbNameToAttach.valueByLeft(resCol->database, Qt::CaseInsensitive); - else - colNameParts << wrapObjIfNeeded(resCol->database, dialect); - } - colNameParts << wrapObjIfNeeded(resCol->table, dialect); - } - colNameParts << wrapObjIfNeeded(resCol->column, dialect); - return colNameParts.join("."); + // In case of "SELECT * FROM (SELECT * FROM test);" the SelectResolver will return ROWID columns twice for each table listed, + // because ROWID columns are recurrently handled by QueryExecutorAddRowIds step. We need to identify such columns and make them unique + // in the final query. + // Currently all columns have QueryExecutor aliased names, so we can assume they have unified alias name in form ResCol_N. + // If SelectResolver returns any column like ResCol_N:X, then it means that the column is result of the query like above. + // Note, that this assumption is correct for RowId columns. There can be columns aliased by user and those aliases won't be unified. + QString aliasOnly = columnAlias; + if (aliasOnly.contains(":")) + aliasOnly = aliasOnly.left(aliasOnly.indexOf(":")); + + return rowIdColNames.contains(aliasOnly); } diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h index fd85651..e23b9f6 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h @@ -50,7 +50,7 @@ class QueryExecutorColumns : public QueryExecutorStep * @param rowIdColumn Indicates if this is a call for ROWID column added by QueryExecutorRowId step. * @return Result column object ready for rebuilding tokens and detokenizing. */ - SqliteSelect::Core::ResultColumn* getResultColumnForSelect(const QueryExecutor::ResultColumnPtr& resultColumn, const SelectResolver::Column& col, bool rowIdColumn); + SqliteSelect::Core::ResultColumn* getResultColumnForSelect(const QueryExecutor::ResultColumnPtr& resultColumn, const SelectResolver::Column& col); /** * @brief Translates attach name into database name. @@ -67,8 +67,8 @@ class QueryExecutorColumns : public QueryExecutorStep bool isRowIdColumnAlias(const QString& alias); void wrapWithAliasedColumns(SqliteSelect* select); - - QString getAliasedColumnNameForSqlite2(const QueryExecutor::ResultColumnPtr& resCol); + bool isRowIdColumn(const QString& columnAlias); + QStringList rowIdColNames; }; #endif // QUERYEXECUTORCOLUMNS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp index f42f647..df2ed68 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp @@ -8,6 +8,7 @@ #include <QDateTime> #include <QDebug> #include <schemaresolver.h> +#include <common/table.h> bool QueryExecutorExecute::exec() { @@ -120,17 +121,17 @@ void QueryExecutorExecute::setupSqlite2ColumnDataTypes(SqlQueryPtr results) if (!sqlite2Helper) return; - QPair<QString,QString> key; + Table key; SqliteCreateTablePtr createTable; SchemaResolver resolver(db); - QHash<QPair<QString,QString>,SqliteCreateTablePtr> tables; + QHash<Table,SqliteCreateTablePtr> tables; for (QueryExecutor::SourceTablePtr tab : context->sourceTables) { if (tab->table.isNull()) continue; - key = QPair<QString,QString>(tab->database, tab->table); + key = Table(tab->database, tab->table); createTable = resolver.getParsedObject(tab->database, tab->table, SchemaResolver::TABLE).dynamicCast<SqliteCreateTable>(); tables[key] = createTable; } @@ -142,7 +143,7 @@ void QueryExecutorExecute::setupSqlite2ColumnDataTypes(SqlQueryPtr results) for (QueryExecutor::ResultColumnPtr resCol : context->resultColumns) { idx++; - key = QPair<QString,QString>(resCol->database, resCol->table); + key = Table(resCol->database, resCol->table); if (!tables.contains(key)) continue; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h b/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h index eff3621..2f26aaf 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h @@ -41,6 +41,7 @@ static int bind_int(stmt* a1, int a2, int a3) {return Prefix##sqlite3_bind_int(a1, a2, a3);} \ static int bind_int64(stmt* a1, int a2, int64 a3) {return Prefix##sqlite3_bind_int64(a1, a2, a3);} \ static int bind_null(stmt* a1, int a2) {return Prefix##sqlite3_bind_null(a1, a2);} \ + static int bind_parameter_index(stmt* a1, const char* a2) {return Prefix##sqlite3_bind_parameter_index(a1, a2);} \ static int bind_text16(stmt* a1, int a2, const void* a3, int a4, void(*a5)(void*)) {return Prefix##sqlite3_bind_text16(a1, a2, a3, a4, a5);} \ static void result_blob(context* a1, const void* a2, int a3, void(*a4)(void*)) {Prefix##sqlite3_result_blob(a1, a2, a3, a4);} \ static void result_double(context* a1, double a2) {Prefix##sqlite3_result_double(a1, a2);} \ |
