diff --git a/ChangeLog.md b/ChangeLog.md index 02a1f21b76..69b5dfa5ce 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,9 @@ # ChangeLog +### 3.4.12 +- BIGFIX: #5179 Fixed results returned from query joining the same table twice. +- BIGFIX: #5179 Fixed smart execution when joining tables with USING clause, so the result metadata is extracted properly and results can be edited. + ### 3.4.11 - CHANGE: SQLite updated to 3.47.2 - BUGFIX: #5161 An ultimate fix for dialog windows positioning, so they no longer can appear outside of visible screen. diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp index 420e29eef7..5347169ef3 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp @@ -61,7 +61,7 @@ QHash> QueryExecutorAddRowIds::addR return rowIdColsMap; } - core->rebuildTokens(); + select->rebuildTokens(); // Getting all tables we need to get ROWID for SelectResolver resolver(db, select->tokens.detokenize(), context->dbNameToAttach); diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp index 511c746322..b447f8472f 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp @@ -69,13 +69,15 @@ bool QueryExecutorColumns::exec() context->resultColumns << resultColumn; // store it in context for later usage by any step } -// qDebug() << "before: " << context->processedQuery; + //qDebug() << "before: " << context->processedQuery; // Update query select->rebuildTokens(); - wrapWithAliasedColumns(select.data()); + // #5179 does not seem to be needed anymore, because query executor alias is applied always in the main column loop above. + // Keeping the commented reference here for a while, but to be removed in future (due to end of 2025). + //wrapWithAliasedColumns(select.data()); updateQueries(); -// qDebug() << "after: " << context->processedQuery; + //qDebug() << "after: " << context->processedQuery; return true; } @@ -176,11 +178,13 @@ SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect } } - selectResultColumn->asKw = true; if (!col.alias.isNull()) - selectResultColumn->alias = col.alias; - else - selectResultColumn->alias = resultColumn->queryExecutorAlias; + selectResultColumn->expr->column = col.alias; + + // #5179 duplicate of the same source table columns (but with different table alias) requires executor alias to be applied + // always and immediately here to get proper results. + selectResultColumn->asKw = true; + selectResultColumn->alias = resultColumn->queryExecutorAlias; // If this alias was already used we need to use sequential alias static_qstring(aliasTpl, "%1:%2"); @@ -212,63 +216,6 @@ bool QueryExecutorColumns::isRowIdColumnAlias(const QString& alias) return false; } -void QueryExecutorColumns::wrapWithAliasedColumns(SqliteSelect* select) -{ - // Wrap everything in a surrounding SELECT and given query executor alias to all columns this time - TokenList sepTokens; - sepTokens << TokenPtr::create(Token::OPERATOR, ",") << TokenPtr::create(Token::SPACE, " "); - - bool first = true; - TokenList outerColumns; - QStringList columnNamesUsed; - QString baseColName; - QString colName; - static_qstring(colNameTpl, "%1:%2"); - for (QueryExecutor::ResultColumnPtr& resCol : context->resultColumns) - { - if (!first) - outerColumns += sepTokens; - - // 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->queryExecutorAlias.isNull()) - baseColName = resCol->alias; - else if (!resCol->expression) - baseColName = resCol->column; - - if (!baseColName.isNull()) - { - colName = baseColName; - for (int i = 1; columnNamesUsed.contains(colName, Qt::CaseInsensitive); i++) - colName = colNameTpl.arg(resCol->column, QString::number(i)); - - columnNamesUsed << colName; - outerColumns << TokenPtr::create(Token::OTHER, wrapObjIfNeeded(colName)); - outerColumns << TokenPtr::create(Token::SPACE, " "); - outerColumns << TokenPtr::create(Token::KEYWORD, "AS"); - outerColumns << TokenPtr::create(Token::SPACE, " "); - } - outerColumns << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias); - first = false; - } - - for (QueryExecutor::ResultRowIdColumnPtr& rowIdColumn : context->rowIdColumns) - { - for (QString& alias : rowIdColumn->queryExecutorAliasToColumn.keys()) - { - if (!first) - outerColumns += sepTokens; - - outerColumns << TokenPtr::create(Token::OTHER, alias); - first = false; - } - } - - //QString t = outerColumns.detokenize(); // keeping it for debug purposes - select->tokens = wrapSelect(select->tokens, outerColumns); -} - bool QueryExecutorColumns::isRowIdColumn(const QString& columnAlias) { // In case of "SELECT * FROM (SELECT * FROM test);" the SelectResolver will return ROWID columns twice for each table listed, diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h index 8e1ebd28d7..e5650cb3ba 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h @@ -66,7 +66,6 @@ class QueryExecutorColumns : public QueryExecutorStep */ bool isRowIdColumnAlias(const QString& alias); - void wrapWithAliasedColumns(SqliteSelect* select); bool isRowIdColumn(const QString& columnAlias); QStringList rowIdColNames; }; diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp index 1ffc1bfd0c..cbb385ca82 100644 --- a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp @@ -14,7 +14,7 @@ bool QueryExecutorExecute::exec() { -// qDebug() << "q:" << context->processedQuery; + //qDebug() << "q:" << context->processedQuery; startTime = QDateTime::currentMSecsSinceEpoch(); return executeQueries(); diff --git a/SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp b/SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp index 3fafef4e22..0e4dad558a 100644 --- a/SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp @@ -725,7 +725,19 @@ QList SelectResolver::resolveSingleSourceSubSelect(Sqlit QList SelectResolver::resolveOtherSource(SqliteSelect::Core::JoinSourceOther *otherSrc) { - return resolveSingleSource(otherSrc->singleSource); + QList joinedColumns = resolveSingleSource(otherSrc->singleSource); + if (!otherSrc->joinConstraint || otherSrc->joinConstraint->expr) + return joinedColumns; + + // Skip right-hand (aka "other") source column if it matches any of names listed in USING clause. + QSet usingColumns; + for (QString& colName : otherSrc->joinConstraint->columnNames) + usingColumns << colName.toLower(); + + return filter(joinedColumns, [usingColumns](const SelectResolver::Column& col) + { + return !usingColumns.contains((col.alias.isNull() ? col.column : col.alias).toLower()); + }); } QList SelectResolver::resolveSubSelect(SqliteSelect *select) @@ -761,9 +773,15 @@ QList SelectResolver::resolveSubSelect(SqliteSelect *sel } else { + static_qstring(colTpl, "%1.%2 AS %3"); + auto fn = [](const Column& c) {return colTpl.arg(c.table, c.column, c.alias);}; + QStringList resolverColumns = map(columnSourcesFromInternal, fn); + QStringList sqliteColumns = map(columnSources, fn); qCritical() << "Number of columns resolved by internal SchemaResolver is different than resolved by SQLite API:" << columnSourcesFromInternal.size() << "!=" << columnSources.size() - << ", therefore table alias may be identified incorrectly (from resolver, but not by SQLite API)"; + << ", therefore table alias may be identified incorrectly (from resolver, but not by SQLite API)" + << ". Columns were resolved from query:" << query << ". Colums resolved by SchemaResolver:" + << resolverColumns.join(", ") << ", while columns from SQLite:" << sqliteColumns.join(", "); } if (compound) diff --git a/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp index 79418607f1..3b708ec660 100644 --- a/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp @@ -42,7 +42,7 @@ DEFINE_SINGLETON(SQLiteStudio) -static const int sqlitestudioVersion = 30411; +static const int sqlitestudioVersion = 30412; SQLiteStudio::SQLiteStudio() {