Skip to content

Commit ba9b48c

Browse files
committed
#5179 Fixed results returned from query joining the same table twice.
1 parent 4e74c32 commit ba9b48c

7 files changed

Lines changed: 38 additions & 70 deletions

File tree

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# ChangeLog
22

3+
### 3.4.12
4+
- BIGFIX: #5179 Fixed results returned from query joining the same table twice.
5+
- BIGFIX: #5179 Fixed smart execution when joining tables with USING clause, so the result metadata is extracted properly and results can be edited.
6+
37
### 3.4.11
48
- CHANGE: SQLite updated to 3.47.2
59
- BUGFIX: #5161 An ultimate fix for dialog windows positioning, so they no longer can appear outside of visible screen.

SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ QHash<SelectResolver::Table,QHash<QString,QString>> QueryExecutorAddRowIds::addR
6161
return rowIdColsMap;
6262

6363
}
64-
core->rebuildTokens();
64+
select->rebuildTokens();
6565

6666
// Getting all tables we need to get ROWID for
6767
SelectResolver resolver(db, select->tokens.detokenize(), context->dbNameToAttach);

SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp

Lines changed: 11 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,15 @@ bool QueryExecutorColumns::exec()
6969
context->resultColumns << resultColumn; // store it in context for later usage by any step
7070
}
7171

72-
// qDebug() << "before: " << context->processedQuery;
72+
//qDebug() << "before: " << context->processedQuery;
7373
// Update query
7474
select->rebuildTokens();
75-
wrapWithAliasedColumns(select.data());
75+
// #5179 does not seem to be needed anymore, because query executor alias is applied always in the main column loop above.
76+
// Keeping the commented reference here for a while, but to be removed in future (due to end of 2025).
77+
//wrapWithAliasedColumns(select.data());
7678
updateQueries();
7779

78-
// qDebug() << "after: " << context->processedQuery;
80+
//qDebug() << "after: " << context->processedQuery;
7981

8082
return true;
8183
}
@@ -176,11 +178,13 @@ SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect
176178
}
177179
}
178180

179-
selectResultColumn->asKw = true;
180181
if (!col.alias.isNull())
181-
selectResultColumn->alias = col.alias;
182-
else
183-
selectResultColumn->alias = resultColumn->queryExecutorAlias;
182+
selectResultColumn->expr->column = col.alias;
183+
184+
// #5179 duplicate of the same source table columns (but with different table alias) requires executor alias to be applied
185+
// always and immediately here to get proper results.
186+
selectResultColumn->asKw = true;
187+
selectResultColumn->alias = resultColumn->queryExecutorAlias;
184188

185189
// If this alias was already used we need to use sequential alias
186190
static_qstring(aliasTpl, "%1:%2");
@@ -212,63 +216,6 @@ bool QueryExecutorColumns::isRowIdColumnAlias(const QString& alias)
212216
return false;
213217
}
214218

215-
void QueryExecutorColumns::wrapWithAliasedColumns(SqliteSelect* select)
216-
{
217-
// Wrap everything in a surrounding SELECT and given query executor alias to all columns this time
218-
TokenList sepTokens;
219-
sepTokens << TokenPtr::create(Token::OPERATOR, ",") << TokenPtr::create(Token::SPACE, " ");
220-
221-
bool first = true;
222-
TokenList outerColumns;
223-
QStringList columnNamesUsed;
224-
QString baseColName;
225-
QString colName;
226-
static_qstring(colNameTpl, "%1:%2");
227-
for (QueryExecutor::ResultColumnPtr& resCol : context->resultColumns)
228-
{
229-
if (!first)
230-
outerColumns += sepTokens;
231-
232-
// If alias was given, we use it. If it was anything but expression, we also use its display name,
233-
// because it's explicit column (no matter if from table, or table alias).
234-
baseColName = QString();
235-
if (!resCol->queryExecutorAlias.isNull())
236-
baseColName = resCol->alias;
237-
else if (!resCol->expression)
238-
baseColName = resCol->column;
239-
240-
if (!baseColName.isNull())
241-
{
242-
colName = baseColName;
243-
for (int i = 1; columnNamesUsed.contains(colName, Qt::CaseInsensitive); i++)
244-
colName = colNameTpl.arg(resCol->column, QString::number(i));
245-
246-
columnNamesUsed << colName;
247-
outerColumns << TokenPtr::create(Token::OTHER, wrapObjIfNeeded(colName));
248-
outerColumns << TokenPtr::create(Token::SPACE, " ");
249-
outerColumns << TokenPtr::create(Token::KEYWORD, "AS");
250-
outerColumns << TokenPtr::create(Token::SPACE, " ");
251-
}
252-
outerColumns << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias);
253-
first = false;
254-
}
255-
256-
for (QueryExecutor::ResultRowIdColumnPtr& rowIdColumn : context->rowIdColumns)
257-
{
258-
for (QString& alias : rowIdColumn->queryExecutorAliasToColumn.keys())
259-
{
260-
if (!first)
261-
outerColumns += sepTokens;
262-
263-
outerColumns << TokenPtr::create(Token::OTHER, alias);
264-
first = false;
265-
}
266-
}
267-
268-
//QString t = outerColumns.detokenize(); // keeping it for debug purposes
269-
select->tokens = wrapSelect(select->tokens, outerColumns);
270-
}
271-
272219
bool QueryExecutorColumns::isRowIdColumn(const QString& columnAlias)
273220
{
274221
// In case of "SELECT * FROM (SELECT * FROM test);" the SelectResolver will return ROWID columns twice for each table listed,

SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ class QueryExecutorColumns : public QueryExecutorStep
6666
*/
6767
bool isRowIdColumnAlias(const QString& alias);
6868

69-
void wrapWithAliasedColumns(SqliteSelect* select);
7069
bool isRowIdColumn(const QString& columnAlias);
7170
QStringList rowIdColNames;
7271
};

SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
bool QueryExecutorExecute::exec()
1616
{
17-
// qDebug() << "q:" << context->processedQuery;
17+
//qDebug() << "q:" << context->processedQuery;
1818

1919
startTime = QDateTime::currentMSecsSinceEpoch();
2020
return executeQueries();

SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,19 @@ QList<SelectResolver::Column> SelectResolver::resolveSingleSourceSubSelect(Sqlit
725725

726726
QList<SelectResolver::Column> SelectResolver::resolveOtherSource(SqliteSelect::Core::JoinSourceOther *otherSrc)
727727
{
728-
return resolveSingleSource(otherSrc->singleSource);
728+
QList<SelectResolver::Column> joinedColumns = resolveSingleSource(otherSrc->singleSource);
729+
if (!otherSrc->joinConstraint || otherSrc->joinConstraint->expr)
730+
return joinedColumns;
731+
732+
// Skip right-hand (aka "other") source column if it matches any of names listed in USING clause.
733+
QSet<QString> usingColumns;
734+
for (QString& colName : otherSrc->joinConstraint->columnNames)
735+
usingColumns << colName.toLower();
736+
737+
return filter<SelectResolver::Column>(joinedColumns, [usingColumns](const SelectResolver::Column& col)
738+
{
739+
return !usingColumns.contains((col.alias.isNull() ? col.column : col.alias).toLower());
740+
});
729741
}
730742

731743
QList<SelectResolver::Column> SelectResolver::resolveSubSelect(SqliteSelect *select)
@@ -761,9 +773,15 @@ QList<SelectResolver::Column> SelectResolver::resolveSubSelect(SqliteSelect *sel
761773
}
762774
else
763775
{
776+
static_qstring(colTpl, "%1.%2 AS %3");
777+
auto fn = [](const Column& c) {return colTpl.arg(c.table, c.column, c.alias);};
778+
QStringList resolverColumns = map<Column, QString>(columnSourcesFromInternal, fn);
779+
QStringList sqliteColumns = map<Column, QString>(columnSources, fn);
764780
qCritical() << "Number of columns resolved by internal SchemaResolver is different than resolved by SQLite API:"
765781
<< columnSourcesFromInternal.size() << "!=" << columnSources.size()
766-
<< ", therefore table alias may be identified incorrectly (from resolver, but not by SQLite API)";
782+
<< ", therefore table alias may be identified incorrectly (from resolver, but not by SQLite API)"
783+
<< ". Columns were resolved from query:" << query << ". Colums resolved by SchemaResolver:"
784+
<< resolverColumns.join(", ") << ", while columns from SQLite:" << sqliteColumns.join(", ");
767785
}
768786

769787
if (compound)

SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
DEFINE_SINGLETON(SQLiteStudio)
4444

45-
static const int sqlitestudioVersion = 30411;
45+
static const int sqlitestudioVersion = 30412;
4646

4747
SQLiteStudio::SQLiteStudio()
4848
{

0 commit comments

Comments
 (0)