diff options
| author | 2021-12-17 07:06:30 -0500 | |
|---|---|---|
| committer | 2021-12-17 07:06:30 -0500 | |
| commit | 1fdc150116cad39aae5c5da407c3312b47a59e3a (patch) | |
| tree | 123c79a4d7ad2d45781ba03ce939f7539fb428d8 /SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp | |
| parent | feda8a7db8d1d7c5439aa8f8feef7cc0dd2b59a0 (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.cpp | 244 |
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; |
