aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp')
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp439
1 files changed, 439 insertions, 0 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp b/SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp
new file mode 100644
index 0000000..978395f
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp
@@ -0,0 +1,439 @@
+#include "completioncomparer.h"
+#include "completionhelper.h"
+#include "parser/ast/sqliteselect.h"
+#include "db/db.h"
+#include "parser/token.h"
+#include <QDebug>
+
+CompletionComparer::CompletionComparer(CompletionHelper *helper)
+ : helper(helper)
+{
+ dialect = helper->db->getDialect();
+ init();
+}
+
+bool CompletionComparer::operator ()(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2)
+{
+ if ((token1->priority > 0 || token2->priority > 0) && token1->priority != token2->priority)
+ return token1->priority > token2->priority;
+
+ if (token1->type != token2->type)
+ return token1->type < token2->type;
+
+ switch (token1->type)
+ {
+ case ExpectedToken::COLUMN:
+ return compareColumns(token1, token2);
+ case ExpectedToken::TABLE:
+ return compareTables(token1, token2);
+ case ExpectedToken::INDEX:
+ return compareIndexes(token1, token2);
+ case ExpectedToken::TRIGGER:
+ return compareTriggers(token1, token2);
+ case ExpectedToken::VIEW:
+ return compareViews(token1, token2);
+ case ExpectedToken::DATABASE:
+ return compareDatabases(token1, token2);
+ case ExpectedToken::KEYWORD:
+ case ExpectedToken::FUNCTION:
+ case ExpectedToken::OPERATOR:
+ case ExpectedToken::PRAGMA:
+ return compareValues(token1, token2);
+ case ExpectedToken::COLLATION:
+ {
+ if (dialect == Dialect::Sqlite3)
+ return compareValues(token1, token2);
+ else
+ return false;
+ }
+ case ExpectedToken::OTHER:
+ case ExpectedToken::STRING:
+ case ExpectedToken::NUMBER:
+ case ExpectedToken::BLOB:
+ case ExpectedToken::NO_VALUE:
+ return false;
+ }
+
+ return false;
+}
+
+void CompletionComparer::init()
+{
+ if (helper->originalParsedQuery)
+ {
+ bool contextObjectsInitialized = false;
+ if (helper->originalParsedQuery->queryType == SqliteQueryType::Select)
+ contextObjectsInitialized = initSelect();
+
+ if (!contextObjectsInitialized)
+ {
+ contextColumns = helper->originalParsedQuery->getContextColumns();
+ contextTables = helper->originalParsedQuery->getContextTables();
+ contextDatabases = helper->originalParsedQuery->getContextDatabases(false);
+ }
+
+ foreach (SelectResolver::Table table, helper->selectAvailableTables + helper->parentSelectAvailableTables)
+ availableTableNames += table.table;
+ }
+}
+
+bool CompletionComparer::initSelect()
+{
+ if (!helper->originalCurrentSelectCore)
+ return false;
+
+ // This is similar to what is done in init() itself, except here it's limited
+ // to the current select core, excluding parent statement.
+ contextColumns = helper->originalCurrentSelectCore->getContextColumns(false);
+ contextTables = helper->originalCurrentSelectCore->getContextTables(false);
+ contextDatabases = helper->originalCurrentSelectCore->getContextDatabases(false);
+
+ foreach (SqliteSelect::Core* core, helper->parentSelectCores)
+ {
+ parentContextColumns += core->getContextColumns(false);
+ parentContextTables += core->getContextTables(false);
+ parentContextDatabases += core->getContextDatabases(false);
+ }
+
+ if (helper->context == CompletionHelper::Context::SELECT_RESULT_COLUMN)
+ {
+ // Getting list of result columns already being selected in the query
+ resultColumns = helper->selectResolver->resolve(helper->currentSelectCore);
+ }
+
+ return true;
+}
+
+bool CompletionComparer::compareColumns(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2)
+{
+ if (!helper->parsedQuery)
+ return compareValues(token1, token2);
+
+ bool ok = false;
+ bool result = true;
+ switch (helper->context)
+ {
+ case CompletionHelper::Context::SELECT_WHERE:
+ case CompletionHelper::Context::SELECT_GROUP_BY:
+ case CompletionHelper::Context::SELECT_HAVING:
+ case CompletionHelper::Context::SELECT_RESULT_COLUMN:
+ case CompletionHelper::Context::SELECT_ORDER_BY:
+ result = compareColumnsForSelectResCol(token1, token2, &ok);
+ break;
+ case CompletionHelper::Context::UPDATE_COLUMN:
+ case CompletionHelper::Context::UPDATE_WHERE:
+ result = compareColumnsForUpdateCol(token1, token2, &ok);
+ break;
+ case CompletionHelper::Context::DELETE_WHERE:
+ result = compareColumnsForDeleteCol(token1, token2, &ok);
+ break;
+ case CompletionHelper::Context::CREATE_TABLE:
+ result = compareColumnsForCreateTable(token1, token2, &ok);
+ break;
+ default:
+ return compareValues(token1, token2);
+ }
+
+ if (ok)
+ return result;
+
+ result = compareByContext(token1->value, token2->value, {contextColumns, parentContextColumns}, true, &ok);
+ if (ok)
+ return result;
+
+ // Context info of the column has a strong meaning when sorting (system tables are pushed to the end)
+ bool firstIsSystem = token1->contextInfo.toLower().startsWith("sqlite_");
+ bool secondIsSystem = token2->contextInfo.toLower().startsWith("sqlite_");
+ if (firstIsSystem && !secondIsSystem)
+ return false;
+
+ if (!firstIsSystem && secondIsSystem)
+ return true;
+
+ return compareValues(token1->value, token2->value, true);
+}
+
+bool CompletionComparer::compareColumnsForSelectResCol(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool *result)
+{
+ *result = true;
+
+ // Checking if columns are on list of columns available in FROM clause
+ bool token1available = isTokenOnAvailableList(token1);
+ bool token2available = isTokenOnAvailableList(token2);
+ if (token1available && !token2available)
+ return true;
+
+ if (!token1available && token2available)
+ return false;
+
+ // Checking if columns are on list of columns available in FROM clause of any parent SELECT core
+ bool token1parentAvailable = isTokenOnParentAvailableList(token1);
+ bool token2parentAvailable = isTokenOnParentAvailableList(token2);
+ if (token1parentAvailable && !token2parentAvailable)
+ return true;
+
+ if (!token1parentAvailable && token2parentAvailable)
+ return false;
+
+ // Checking if columns were already mentioned in results list.
+ // It it was, it should be pushed back.
+ bool token1onResCols = isTokenOnResultColumns(token1);
+ bool token2onResCols = isTokenOnResultColumns(token2);
+ if (token1onResCols && !token2onResCols)
+ return false;
+
+ if (!token1onResCols && token2onResCols)
+ return true;
+
+ *result = false;
+ return false;
+}
+
+bool CompletionComparer::compareColumnsForUpdateCol(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool *result)
+{
+ *result = true;
+ if (token1->contextInfo == token2->contextInfo)
+ return compareValues(token1->value, token2->value);
+
+ return compareByContext(token1->contextInfo, token2->contextInfo, contextTables);
+}
+
+bool CompletionComparer::compareColumnsForDeleteCol(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool *result)
+{
+ *result = true;
+ if (token1->contextInfo == token2->contextInfo)
+ return compareValues(token1->value, token2->value);
+
+ return compareByContext(token1->contextInfo, token2->contextInfo, contextTables);
+}
+
+bool CompletionComparer::compareColumnsForCreateTable(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result)
+{
+ *result = true;
+
+ bool token1OnAvailableList = helper->favoredColumnNames.contains(token1->value) && contextTables.contains(token1->contextInfo);
+ bool token2OnAvailableList = helper->favoredColumnNames.contains(token2->value) && contextTables.contains(token2->contextInfo);
+ if (token1OnAvailableList && !token2OnAvailableList)
+ return true;
+
+ if (!token1OnAvailableList && token2OnAvailableList)
+ return false;
+
+ *result = false;
+ return false;
+}
+
+bool CompletionComparer::compareTables(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2)
+{
+ if (!helper->parsedQuery || helper->parsedQuery->queryType != SqliteQueryType::Select)
+ return compareValues(token1, token2);
+
+ if (helper->context == CompletionHelper::Context::SELECT_FROM)
+ {
+ // In case the table was already mentioned in any FROM clause, we push it back.
+ bool token1OnAvailableList = availableTableNames.contains(token1->value);
+ bool token2OnAvailableList = availableTableNames.contains(token2->value);
+ if (token1OnAvailableList && !token2OnAvailableList)
+ return false;
+
+ if (!token1OnAvailableList && token2OnAvailableList)
+ return true;
+ }
+
+ bool ok;
+ bool result = compareByContext(token1->value, token2->value, contextTables, &ok);
+ if (ok)
+ return result;
+
+ result = compareByContext(token1->contextInfo, token2->contextInfo, contextDatabases, &ok);
+ if (ok)
+ return result;
+
+ result = compareByContext(token1->value, token2->value, parentContextTables, &ok);
+ if (ok)
+ return result;
+
+ result = compareByContext(token1->contextInfo, token2->contextInfo, parentContextDatabases, &ok);
+ if (ok)
+ return result;
+
+ return compareValues(token1->value, token2->value, true);
+}
+
+bool CompletionComparer::compareIndexes(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2)
+{
+ return compareValues(token1, token2, true);
+}
+
+bool CompletionComparer::compareTriggers(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2)
+{
+ return compareValues(token1, token2);
+}
+
+bool CompletionComparer::compareViews(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2)
+{
+ return compareValues(token1, token2);
+}
+
+bool CompletionComparer::compareDatabases(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2)
+{
+ if (!helper->parsedQuery || helper->parsedQuery->queryType != SqliteQueryType::Select)
+ return compareValues(token1, token2);
+
+ return compareByContext(token1->value, token2->value, {contextDatabases, parentContextDatabases});
+}
+
+bool CompletionComparer::compareValues(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool handleSystemNames)
+{
+ return compareValues(token1->value, token2->value, handleSystemNames);
+}
+
+bool CompletionComparer::compareValues(const QString &token1, const QString &token2, bool handleSystemNames)
+{
+ //qDebug() << "comparing" << token1 << "and" << token2 << "=" << token1.compare(token2, Qt::CaseInsensitive);
+ if (handleSystemNames)
+ {
+ bool firstIsSystem = token1.toLower().startsWith("sqlite_");
+ bool secondIsSystem = token2.toLower().startsWith("sqlite_");
+ if (firstIsSystem && !secondIsSystem)
+ return false;
+
+ if (!firstIsSystem && secondIsSystem)
+ return true;
+ }
+
+ return token1.compare(token2, Qt::CaseInsensitive) < 0;
+}
+
+bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QStringList &contextValues, bool *ok)
+{
+ return compareByContext(token1, token2, contextValues, false, ok);
+}
+
+bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QList<QStringList> &contextValues, bool *ok)
+{
+ return compareByContext(token1, token2, contextValues, false, ok);
+}
+
+bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QStringList &contextValues, bool handleSystemNames, bool* ok)
+{
+ if (ok)
+ *ok = true;
+
+ bool localOk = false;
+ bool result = compareByContextOnly(token1, token2, contextValues, handleSystemNames, &localOk);
+
+ if (localOk)
+ return result;
+
+ // Otherwise we compare by value.
+ if (ok)
+ *ok = false;
+
+ return compareValues(token1, token2, handleSystemNames);
+}
+
+bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QList<QStringList>& contextValues, bool handleSystemNames, bool* ok)
+{
+ if (ok)
+ *ok = true;
+
+ bool localOk = false;
+ bool result;
+
+ for (const QStringList& ctxValues : contextValues)
+ {
+ result = compareByContextOnly(token1, token2, ctxValues, handleSystemNames, &localOk);
+ if (localOk)
+ return result;
+ }
+
+ // Otherwise we compare by value.
+ if (ok)
+ *ok = false;
+
+ return compareValues(token1, token2, handleSystemNames);
+}
+
+bool CompletionComparer::compareByContextOnly(const QString &token1, const QString &token2, const QStringList &contextValues, bool handleSystemNames, bool *ok)
+{
+ *ok = true;
+
+ bool token1InContext = contextValues.contains(token1);
+ bool token2InContext = contextValues.contains(token2);
+
+ // token1 < token2 is true only if token1 is in context and token2 is not.
+ // This means that token1 will be on the list before token2.
+ if (token1InContext && !token2InContext)
+ return true;
+
+ // If token2 is in context, but token1 is not, then it's definite false.
+ if (!token1InContext && token2InContext)
+ return false;
+
+ if (handleSystemNames)
+ {
+ bool firstIsSystem = token1.toLower().startsWith("sqlite_");
+ bool secondIsSystem = token2.toLower().startsWith("sqlite_");
+ if (firstIsSystem && !secondIsSystem)
+ return false;
+
+ if (!firstIsSystem && secondIsSystem)
+ return true;
+ }
+
+ *ok = false;
+ return false;
+}
+
+bool CompletionComparer::isTokenOnAvailableList(const ExpectedTokenPtr &token)
+{
+ return isTokenOnColumnList(token, helper->selectAvailableColumns);
+}
+
+bool CompletionComparer::isTokenOnParentAvailableList(const ExpectedTokenPtr &token)
+{
+ return isTokenOnColumnList(token, helper->parentSelectAvailableColumns);
+}
+
+bool CompletionComparer::isTokenOnResultColumns(const ExpectedTokenPtr &token)
+{
+ return isTokenOnColumnList(token, resultColumns);
+}
+
+bool CompletionComparer::isTokenOnColumnList(const ExpectedTokenPtr &token, const QList<SelectResolver::Column> &columnList)
+{
+ foreach (SelectResolver::Column column, columnList)
+ {
+ // If column name doesn't match, then it's not this column
+ if (token->value.compare(column.column, Qt::CaseInsensitive) != 0)
+ continue;
+
+ // At this point, column name is matched
+ if (token->prefix.isNull() && token->contextInfo.isNull())
+ {
+ // No prefix, nor context info, just column name.
+ return true;
+ }
+
+ // Table alias or just table name?
+ QString toCompareWithPrefix;
+ if (!column.tableAlias.isNull())
+ toCompareWithPrefix = column.tableAlias;
+ else
+ toCompareWithPrefix = column.table;
+
+ // Do we have actual prefix, or just context information and transparent (null) prefix?
+ QString prefix;
+// if (!token->prefix.isNull())
+// prefix = token->prefix;
+// else
+ prefix = token->contextInfo;
+
+ // Does the table/alias match prefix?
+ if (prefix.compare(column.table, Qt::CaseInsensitive) == 0)
+ return true;
+ }
+ return false;
+}
+