aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@unit193.net>2021-12-17 07:06:30 -0500
committerLibravatarUnit 193 <unit193@unit193.net>2021-12-17 07:06:30 -0500
commit1fdc150116cad39aae5c5da407c3312b47a59e3a (patch)
tree123c79a4d7ad2d45781ba03ce939f7539fb428d8 /SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp
parentfeda8a7db8d1d7c5439aa8f8feef7cc0dd2b59a0 (diff)
New upstream version 3.3.3+dfsg1.upstream/3.3.3+dfsg1
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp')
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp244
1 files changed, 130 insertions, 114 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp b/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp
index f0d00a1..675a6a6 100644
--- a/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp
+++ b/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp
@@ -9,14 +9,14 @@
#include "dbattacher.h"
#include "common/utils.h"
#include "common/utils_sql.h"
+#include "common/compatibility.h"
#include "services/dbmanager.h"
+#include "db/dbsqlite3.h"
#include <QStringList>
#include <QDebug>
QStringList sqlite3Pragmas;
-QStringList sqlite2Pragmas;
QStringList sqlite3Functions;
-QStringList sqlite2Functions;
bool CompletionHelper::enableLemonDebug = false;
@@ -36,56 +36,15 @@ CompletionHelper::CompletionHelper(const QString &sql, quint32 cursorPos, Db* db
void CompletionHelper::init()
{
- sqlite3Pragmas << "auto_vacuum" << "automatic_index" << "busy_timeout" << "cache_size"
- << "case_sensitive_like" << "checkpoint_fullfsync" << "collation_list"
- << "compile_options" << "count_changes" << "data_store_directory"
- << "database_list" << "default_cache_size" << "empty_result_callbacks"
- << "encoding" << "foreign_key_check" << "foreign_key_list" << "foreign_keys"
- << "freelist_count" << "full_column_names" << "fullfsync"
- << "ignore_check_constraints" << "incremental_vacuum" << "index_info"
- << "index_list" << "integrity_check" << "journal_mode" << "journal_size_limit"
- << "legacy_file_format" << "locking_mode" << "max_page_count" << "page_count"
- << "page_size" << "quick_check" << "read_uncommitted" << "recursive_triggers"
- << "reverse_unordered_selects" << "schema_version" << "secure_delete"
- << "short_column_names" << "shrink_memory" << "synchronous" << "table_info"
- << "temp_store" << "temp_store_directory" << "user_version"
- << "wal_autocheckpoint" << "wal_checkpoint" << "writable_schema";
-
- sqlite2Pragmas << "cache_size" << "count_changes" << "database_list" << "default_cache_size"
- << "default_synchronous" << "default_temp_store" << "empty_result_callbacks"
- << "foreign_key_list" << "full_column_names" << "index_info" << "index_list"
- << "integrity_check" << "parser_trace" << "show_datatypes" << "synchronous"
- << "table_info" << "temp_store";
+ Db* db = new DbSqlite3("CompletionHelper::init()", ":memory:", {{DB_PURE_INIT, true}});
+ if (!db->open())
+ qWarning() << "Could not open memory db for initializing function list:" << db->getErrorText();
- sqlite3Functions << "avg(X)" << "count(X)" << "count(*)" << "group_concat(X)"
- << "group_concat(X,Y)" << "max(X)" << "min(X)" << "sum(X)" << "total(X)"
- << "abs(X)" << "changes()" << "char(X1,X2,...,XN)" << "coalesce(X,Y,...)"
- << "glob(X,Y)" << "ifnull(X,Y)" << "instr(X,Y)" << "hex(X)"
- << "last_insert_rowid()" << "length(X)" << "like(X,Y)" << "like(X,Y,Z)"
- << "load_extension(X,Y)" << "lower(X)" << "ltrim(X)" << "ltrim(X,Y)"
- << "max(X,Y,...)" << "min(X,Y,...)" << "nullif(X,Y)" << "quote(X)"
- << "random()" << "randomblob(N)" << "hex(randomblob(16))"
- << "lower(hex(randomblob(16)))" << "replace(X,Y,Z)" << "round(X)"
- << "round(X,Y)" << "rtrim(X)" << "rtrim(X,Y)" << "soundex(X)"
- << "sqlite_compileoption_get(N)" << "sqlite_compileoption_used(X)"
- << "sqlite_source_id()" << "sqlite_version()" << "substr(X,Y,Z)"
- << "substr(X,Y)" << "total_changes()" << "trim(X)" << "trim(X,Y)"
- << "typeof(X)" << "unicode(X)" << "upper(X)" << "zeroblob(N)"
- << "date(timestr,mod,mod,...)" << "time(timestr,mod,mod,...)"
- << "datetime(timestr,mod,mod,...)" << "julianday(timestr,mod,mod,...)"
- << "strftime(format,timestr,mod,mod,...)" << "likelihood(X,Y)"
- << "likely(X)" << "unlikely(X)";
+ initFunctions(db);
+ initPragmas(db);
+ delete db;
- sqlite2Functions << "abs(X)" << "coalesce(X,Y,...)" << "glob(X,Y)" << "ifnull(X,Y)"
- << "last_insert_rowid()" << "length(X)" << "like(X,Y)" << "lower(X)"
- << "max(X,Y,...)" << "min(X,Y,...)" << "nullif(X,Y)" << "random(*)"
- << "round(X,)" << "round(X,Y)" << "soundex(X)" << "sqlite_version(*)"
- << "substr(X,Y,Z)" << "typeof(X)" << "upper(X)" << "avg(X)" << "count(X)"
- << "count(*)" << "max(X)" << "min(X)" << "sum(X)";
-
- sqlite2Pragmas.sort();
sqlite3Pragmas.sort();
- sqlite2Functions.sort();
sqlite3Functions.sort();
}
@@ -121,12 +80,12 @@ CompletionHelper::Results CompletionHelper::getExpectedTokens()
// If asked for completion when being in the middle of keyword or ID,
// then remove that unfinished keyword/ID from sql and put it into
// the final filter - to be used at the end of this method.
- QString finalFilter = QString::null;
+ QString finalFilter = QString();
bool wrappedFilter = false;
adjustedSql = removeStartedToken(adjustedSql, finalFilter, wrappedFilter);
// Parse SQL up to cursor position, get accepted tokens and tokens that were parsed.
- Parser parser(db->getDialect());
+ Parser parser;
TokenList tokens = parser.getNextTokenCandidates(adjustedSql);
TokenList parsedTokens = parser.getParsedTokens();
@@ -193,43 +152,43 @@ QList<ExpectedTokenPtr> CompletionHelper::getExpectedTokens(TokenPtr token)
case Token::CTX_NEW_KW:
{
if (context == Context::CREATE_TRIGGER)
- results += getExpectedToken(ExpectedToken::TABLE, "new", QString::null, tr("New row reference"), 1);
+ results += getExpectedToken(ExpectedToken::TABLE, "new", QString(), tr("New row reference"), 1);
break;
}
case Token::CTX_OLD_KW:
{
if (context == Context::CREATE_TRIGGER)
- results += getExpectedToken(ExpectedToken::TABLE, "old", QString::null, tr("Old row reference"), 1);
+ results += getExpectedToken(ExpectedToken::TABLE, "old", QString(), tr("Old row reference"), 1);
break;
}
case Token::CTX_TABLE_NEW:
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New table name"));
+ results += getExpectedToken(ExpectedToken::NO_VALUE, QString(), QString(), tr("New table name"));
break;
case Token::CTX_INDEX_NEW:
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New index name"));
+ results += getExpectedToken(ExpectedToken::NO_VALUE, QString(), QString(), tr("New index name"));
break;
case Token::CTX_VIEW_NEW:
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New view name"));
+ results += getExpectedToken(ExpectedToken::NO_VALUE, QString(), QString(), tr("New view name"));
break;
case Token::CTX_TRIGGER_NEW:
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New trigger name"));
+ results += getExpectedToken(ExpectedToken::NO_VALUE, QString(), QString(), tr("New trigger name"));
break;
case Token::CTX_ALIAS:
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Table or column alias"));
+ results += getExpectedToken(ExpectedToken::NO_VALUE, QString(), QString(), tr("Table or column alias"));
break;
case Token::CTX_TRANSACTION:
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("transaction name"));
+ results += getExpectedToken(ExpectedToken::NO_VALUE, QString(), QString(), tr("transaction name"));
break;
case Token::CTX_COLUMN_NEW:
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New column name"));
+ results += getExpectedToken(ExpectedToken::NO_VALUE, QString(), QString(), tr("New column name"));
break;
case Token::CTX_COLUMN_TYPE:
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Column data type"));
+ results += getExpectedToken(ExpectedToken::NO_VALUE, QString(), QString(), tr("Column data type"));
break;
case Token::CTX_CONSTRAINT:
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Constraint name"));
+ results += getExpectedToken(ExpectedToken::NO_VALUE, QString(), QString(), tr("Constraint name"));
break;
case Token::CTX_FK_MATCH:
{
@@ -239,10 +198,10 @@ QList<ExpectedTokenPtr> CompletionHelper::getExpectedTokens(TokenPtr token)
break;
}
case Token::CTX_PRAGMA:
- results += getPragmas(db->getDialect());
+ results += getPragmas();
break;
case Token::CTX_ERROR_MESSAGE:
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Error message"));
+ results += getExpectedToken(ExpectedToken::NO_VALUE, QString(), QString(), tr("Error message"));
break;
case Token::CTX_COLUMN:
{
@@ -281,17 +240,7 @@ QList<ExpectedTokenPtr> CompletionHelper::getExpectedTokens(TokenPtr token)
}
case Token::CTX_COLLATION:
{
- if (db->getDialect() == Dialect::Sqlite2)
- {
- // SQLite 2 doesn't really support collation. It has collations
- // in grammar, but doesn't make use of them. There's no list
- // of collations to be suggested.
- results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Collation name"));
- }
- else
- {
- results += getCollations();
- }
+ results += getCollations();
break;
}
case Token::CTX_JOIN_OPTS:
@@ -301,7 +250,7 @@ QList<ExpectedTokenPtr> CompletionHelper::getExpectedTokens(TokenPtr token)
break;
}
case Token::OTHER:
- results += getExpectedToken(ExpectedToken::OTHER, QString::null, QString::null, tr("Any word"));
+ results += getExpectedToken(ExpectedToken::OTHER, QString(), QString(), tr("Any word"));
break;
case Token::STRING:
results += getExpectedToken(ExpectedToken::STRING);
@@ -428,7 +377,7 @@ bool CompletionHelper::validatePreviousIdForGetObjects(QString* dbName)
if (previousId)
{
localDbName = previousId->value;
- QStringList databases = schemaResolver->getDatabases().toList();
+ QStringList databases = schemaResolver->getDatabases().values();
databases += DBLIST->getDbNames();
if (!databases.contains(localDbName, Qt::CaseInsensitive))
return false; // if db is not on the set, then getObjects() would return empty list anyway;
@@ -494,15 +443,8 @@ QList<ExpectedTokenPtr> CompletionHelper::getDatabases()
results += getExpectedToken(ExpectedToken::DATABASE, dbName);
}
- Dialect dialect = db->getDialect();
-
for (Db* otherDb : DBLIST->getValidDbList())
- {
- if (otherDb->getDialect() != dialect)
- continue;
-
results += getExpectedToken(ExpectedToken::DATABASE, otherDb->getName());
- }
return results;
}
@@ -599,7 +541,7 @@ QList<ExpectedTokenPtr> CompletionHelper::getColumnsNoPrefix()
// Getting all tables for main db. If any column repeats in many tables,
// then tables are stored as a list for the same column.
- for (QString table : schemaResolver->getTables(QString::null))
+ for (QString table : schemaResolver->getTables(QString()))
for (QString column : schemaResolver->getTableColumns(table))
columnList[column] += table;
@@ -745,13 +687,7 @@ QList<ExpectedTokenPtr> CompletionHelper::getFunctions(Db* db)
{
// TODO to do later - make function completion more verbose,
// like what are arguments of the function, etc.
- Dialect dialect = db->getDialect();
-
- QStringList functions;
- if (dialect == Dialect::Sqlite2)
- functions = sqlite2Functions;
- else
- functions = sqlite3Functions;
+ QStringList functions = sqlite3Functions;
for (FunctionManager::ScriptFunction* fn : FUNCTIONS->getScriptFunctionsForDatabase(db->getName()))
functions << fn->toString();
@@ -766,16 +702,10 @@ QList<ExpectedTokenPtr> CompletionHelper::getFunctions(Db* db)
return expectedTokens;
}
-QList<ExpectedTokenPtr> CompletionHelper::getPragmas(Dialect dialect)
+QList<ExpectedTokenPtr> CompletionHelper::getPragmas()
{
- QStringList pragmas;
- if (dialect == Dialect::Sqlite2)
- pragmas = sqlite2Pragmas;
- else
- pragmas = sqlite3Pragmas;
-
QList<ExpectedTokenPtr> expectedTokens;
- for (QString pragma : pragmas)
+ for (QString pragma : sqlite3Pragmas)
expectedTokens += getExpectedToken(ExpectedToken::PRAGMA, pragma);
return expectedTokens;
@@ -847,7 +777,7 @@ void CompletionHelper::attachDatabases()
QString query = parsedQuery->detokenize();
- Parser parser(db->getDialect());
+ Parser parser;
if (parser.parse(query, true) && !parser.getQueries().isEmpty())
parsedQuery = parser.getQueries().first();
}
@@ -886,7 +816,7 @@ QString CompletionHelper::removeStartedToken(const QString& adjustedSql, QString
{
QString result = adjustedSql;
- Lexer lexer(db->getDialect());
+ Lexer lexer;
TokenList tokens = lexer.tokenize(adjustedSql);
if (tokens.size() == 0)
return result;
@@ -898,7 +828,7 @@ QString CompletionHelper::removeStartedToken(const QString& adjustedSql, QString
result = Lexer::detokenize(tokens.mid(0, tokens.size()-1));
finalFilter = lastToken->value;
- if (finalFilter.length() > 0 && isWrapperChar(finalFilter[0], db->getDialect()))
+ if (finalFilter.length() > 0 && isWrapperChar(finalFilter[0]))
{
finalFilter = finalFilter.mid(1);
wrappedFilter = true;
@@ -992,8 +922,7 @@ void CompletionHelper::filterOtherId(QList<ExpectedTokenPtr> &resultsSoFar, cons
void CompletionHelper::filterDuplicates(QList<ExpectedTokenPtr>& resultsSoFar)
{
- QSet<ExpectedTokenPtr> set = resultsSoFar.toSet();
- resultsSoFar = set.toList();
+ resultsSoFar = toSet(resultsSoFar).values();
}
void CompletionHelper::applyFilter(QList<ExpectedTokenPtr>& resultsSoFar, const QString& filter)
@@ -1027,15 +956,13 @@ bool CompletionHelper::isFilterType(Token::Type type)
void CompletionHelper::parseFullSql()
{
- Dialect dialect = db->getDialect();
-
QString sql = fullSql;
// Selecting query at cursor position
QString query = getQueryWithPosition(sql, cursorPosition);
// Token list of the query. Also useful, not only parsed query.
- queryTokens = Lexer::tokenize(query, dialect);
+ queryTokens = Lexer::tokenize(query);
queryTokens.trim();
// Completing query
@@ -1043,7 +970,7 @@ void CompletionHelper::parseFullSql()
query += ";";
// Parsing query
- Parser parser(dialect);
+ Parser parser;
parser.setLemonDebug(enableLemonDebug);
if (tryToParse(&parser, query))
return;
@@ -1072,17 +999,15 @@ bool CompletionHelper::tryToParse(Parser* parser, const QString& query)
void CompletionHelper::sort(QList<ExpectedTokenPtr> &resultsSoFar)
{
CompletionComparer comparer(this);
- qSort(resultsSoFar.begin(), resultsSoFar.end(), comparer);
+ std::sort(resultsSoFar.begin(), resultsSoFar.end(), comparer);
}
void CompletionHelper::extractPreviousIdTokens(const TokenList &parsedTokens)
{
- Dialect dialect = db->getDialect();
-
// The previous ID token (if any) is being used in
// getExpectedToken() and it's always the same token,
// so here we find it just once and reuse it.
- previousId = stripObjName(getPreviousDbOrTable(parsedTokens), dialect);
+ previousId = stripObjName(getPreviousDbOrTable(parsedTokens));
// In case of column context we need to know if there was
// up to two ID tokens before. If we had one above,
@@ -1092,7 +1017,7 @@ void CompletionHelper::extractPreviousIdTokens(const TokenList &parsedTokens)
{
int idx = parsedTokens.indexOf(previousId);
TokenList parsedTokensSubSet = parsedTokens.mid(0, idx);
- twoIdsBack = stripObjName(getPreviousDbOrTable(parsedTokensSubSet), dialect);
+ twoIdsBack = stripObjName(getPreviousDbOrTable(parsedTokensSubSet));
}
}
@@ -1317,6 +1242,97 @@ void CompletionHelper::setCreateTriggerTable(const QString& value)
createTriggerTable = value;
}
+void CompletionHelper::initFunctions(Db* db)
+{
+ sqlite3Functions << "avg(X)" << "count(X)" << "count(*)" << "group_concat(X)"
+ << "group_concat(X,Y)" << "max(X)" << "min(X)" << "sum(X)" << "total(X)"
+ << "abs(X)" << "changes()" << "char(X1,X2,...,XN)" << "coalesce(X,Y,...)"
+ << "glob(X,Y)" << "ifnull(X,Y)" << "instr(X,Y)" << "hex(X)" << "iif(X,Y,Z)"
+ << "last_insert_rowid()" << "length(X)" << "like(X,Y)" << "like(X,Y,Z)"
+ << "load_extension(X,Y)" << "lower(X)" << "ltrim(X)" << "ltrim(X,Y)"
+ << "max(X,Y,...)" << "min(X,Y,...)" << "nullif(X,Y)" << "quote(X)"
+ << "random()" << "randomblob(N)" << "hex(randomblob(16))"
+ << "lower(hex(randomblob(16)))" << "replace(X,Y,Z)" << "round(X)"
+ << "round(X,Y)" << "rtrim(X)" << "rtrim(X,Y)" << "soundex(X)"
+ << "sqlite_compileoption_get(N)" << "sqlite_compileoption_used(X)"
+ << "sqlite_source_id()" << "sqlite_version()" << "substr(X,Y,Z)"
+ << "substr(X,Y)" << "total_changes()" << "trim(X)" << "trim(X,Y)"
+ << "typeof(X)" << "unicode(X)" << "upper(X)" << "zeroblob(N)"
+ << "date(timestr,mod,mod,...)" << "time(timestr,mod,mod,...)"
+ << "datetime(timestr,mod,mod,...)" << "julianday(timestr,mod,mod,...)"
+ << "strftime(format,timestr,mod,mod,...)" << "likelihood(X,Y)"
+ << "likely(X)" << "unlikely(X)" "row_number()" << "rank()"
+ << "dense_rank()" << "percent_rank()" << "cume_dist()" << "ntile(N)"
+ << "lag(expr)" << "lag(expr, offset)" << "lag(expr, offset, default)"
+ << "lead(expr)" << "lead(expr, offset)" << "lead(expr, offset, default)"
+ << "first_value(expr)" << "last_value(expr)" << "nth_value(expr, N)"
+ << "substring(X,Y,Z)" << "substring(X,Y)";
+
+ if (!db->isOpen())
+ return;
+
+ // Parse what we already have
+ QSet<QString> handledSignatures;
+ static_qstring(sigTpl, "%1_%2");
+ for (const QString& fn : sqlite3Functions)
+ {
+ int argStart = fn.lastIndexOf("(");
+ int argEnd = fn.lastIndexOf(")");
+ QString fnName = fn.left(argStart);
+ QString args = fn.mid(argStart + 1, argEnd - argStart - 1);
+ QStringList argList = args.split(",");
+
+ int argCount = argList.size();;
+ if (args.trimmed().isEmpty())
+ argCount = 0;
+ else if (argList.last() == "...")
+ argCount = -1;
+
+ handledSignatures << sigTpl.arg(fnName, QString::number(argCount));
+ }
+
+ // Find what is missing and add it
+ static_qstring(funTpl, "%1(%2)");
+ static const QStringList argSymbols = {"X", "Y", "Z", "A", "B", "C", "D", "E", "F", "G", "H", "I"};
+ static const int argSymbolCnt = argSymbols.size();
+
+ SqlQueryPtr res = db->exec("PRAGMA function_list;");
+ while (res->hasNext())
+ {
+ SqlResultsRowPtr row = res->next();
+ QVariant nargsVar = row->value("narg");
+ QString fnName = row->value("name").toString();
+ QString sig = sigTpl.arg(fnName, nargsVar.toString());
+ if (!handledSignatures.contains(sig))
+ {
+ int nargs = nargsVar.toInt();
+ QStringList args;
+ if (nargs == -1)
+ args << "...";
+
+ for (int i = 0; i < nargs; i++)
+ args << argSymbols[i % argSymbolCnt];
+
+ sqlite3Functions << funTpl.arg(fnName, args.join(","));
+ handledSignatures << sig;
+ }
+ }
+}
+
+void CompletionHelper::initPragmas(Db* db)
+{
+ if (!db->isOpen())
+ return;
+
+ SqlQueryPtr res = db->exec("PRAGMA pragma_list;");
+ while (res->hasNext())
+ {
+ SqlResultsRowPtr row = res->next();
+ QString name = row->value("name").toString();
+ sqlite3Pragmas << name;
+ }
+}
+
DbAttacher* CompletionHelper::getDbAttacher() const
{
return dbAttacher;