mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-01-17 01:09:36 -06:00
Simplify interface and implementation of SqliteTableModel
This simplifies the usage and the implementation of the SqliteTableModel a bit by making the two operating modes it supports (manual query and interactive query) more obvious in its public interface as well as simplifying the control flow in the private implementation.
This commit is contained in:
@@ -61,18 +61,6 @@ public:
|
||||
/// to remove already-loaded rows from both ends.
|
||||
void smallestNonAvailableRange (size_t & row_begin, size_t & row_end) const;
|
||||
|
||||
/// \returns whether this cache object is marked as initialised
|
||||
bool initialised() const
|
||||
{
|
||||
return is_initialised;
|
||||
}
|
||||
|
||||
/// mark the cache object as initialized. This is reset by the clear() function
|
||||
void setInitialised()
|
||||
{
|
||||
is_initialised = true;
|
||||
}
|
||||
|
||||
private:
|
||||
/// a single segment containing contiguous entries
|
||||
struct Segment
|
||||
@@ -89,9 +77,6 @@ private:
|
||||
using Segments = std::vector<Segment>;
|
||||
Segments segments;
|
||||
|
||||
// Set to true when the cache is first initialised no matter whether it contains any data or represents an empty table
|
||||
bool is_initialised;
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
/// \returns first segment that definitely cannot contain 'pos',
|
||||
@@ -127,7 +112,6 @@ private:
|
||||
|
||||
template <typename T>
|
||||
RowCache<T>::RowCache ()
|
||||
: is_initialised(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -250,7 +234,6 @@ void RowCache<T>::erase (size_t pos)
|
||||
template <typename T>
|
||||
void RowCache<T>::clear ()
|
||||
{
|
||||
is_initialised = false;
|
||||
segments.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ RowLoader::RowLoader (std::function<std::shared_ptr<sqlite3>(void)> db_getter_,
|
||||
, cache_mutex(cache_mutex_), cache_data(cache_data_)
|
||||
, query()
|
||||
, countQuery()
|
||||
, first_chunk_loaded(false)
|
||||
, num_tasks(0)
|
||||
, pDb(nullptr)
|
||||
, stop_requested(false)
|
||||
@@ -39,6 +40,7 @@ void RowLoader::setQuery (const QString& new_query, const QString& newCountQuery
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m);
|
||||
query = new_query;
|
||||
first_chunk_loaded = false;
|
||||
if (newCountQuery.isEmpty())
|
||||
// If it is a normal query - hopefully starting with SELECT - just do a COUNT on it and return the results
|
||||
countQuery = QString("SELECT COUNT(*) FROM (%1);").arg(rtrimChar(query, ';'));
|
||||
@@ -255,9 +257,9 @@ void RowLoader::process (Task & t)
|
||||
// - this is the first batch of data we load for this query
|
||||
// - we got exactly the number of rows back we queried (which indicates there might be more rows)
|
||||
// If there is no need to query the row count this means the number of rows we just got is the total row count.
|
||||
if(!cache_data.initialised())
|
||||
if(!first_chunk_loaded)
|
||||
{
|
||||
cache_data.setInitialised();
|
||||
first_chunk_loaded = true;
|
||||
if(row == t.row_end)
|
||||
triggerRowCountDetermination(t.token);
|
||||
else
|
||||
|
||||
@@ -81,6 +81,8 @@ private:
|
||||
|
||||
mutable std::future<void> row_counter;
|
||||
|
||||
bool first_chunk_loaded;
|
||||
|
||||
size_t num_tasks;
|
||||
std::shared_ptr<sqlite3> pDb; //< exclusive access while held...
|
||||
|
||||
|
||||
@@ -107,12 +107,7 @@ void SqliteTableModel::reset()
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
// We first clear the cache here in order to unset the cache initialised flag,
|
||||
// then call the clearCache function to deal with the clean up
|
||||
// TODO This whole resetting and clearing should be refactored
|
||||
m_cache.clear();
|
||||
clearCache();
|
||||
|
||||
m_sQuery.clear();
|
||||
m_query.clear();
|
||||
m_table_of_query.reset();
|
||||
@@ -133,9 +128,6 @@ void SqliteTableModel::setQuery(const sqlb::Query& query)
|
||||
m_query = query;
|
||||
m_table_of_query = m_db.getObjectByName<sqlb::Table>(query.table());
|
||||
|
||||
// The first column is the rowid column and therefore is always of type integer
|
||||
m_vDataTypes.emplace_back(SQLITE_INTEGER);
|
||||
|
||||
// Get the data types of all other columns as well as the column names
|
||||
// Set the row id columns
|
||||
if(m_table_of_query && m_table_of_query->fields.size()) // It is a table and parsing was OK
|
||||
@@ -143,6 +135,7 @@ void SqliteTableModel::setQuery(const sqlb::Query& query)
|
||||
sqlb::StringVector rowids = m_table_of_query->rowidColumns();
|
||||
m_query.setRowIdColumns(rowids);
|
||||
|
||||
m_vDataTypes.emplace_back(SQLITE_INTEGER); // TODO This is not necessarily true for tables without ROWID or with multiple PKs
|
||||
m_headers.push_back(sqlb::joinStringVector(rowids, ","));
|
||||
|
||||
// Store field names and affinity data types
|
||||
@@ -154,44 +147,31 @@ void SqliteTableModel::setQuery(const sqlb::Query& query)
|
||||
} else {
|
||||
// If for one reason or another (either it's a view or we couldn't parse the table statement) we couldn't get the field
|
||||
// information we retrieve it from SQLite using an extra query.
|
||||
// NOTE: It would be nice to eventually get rid of this piece here. As soon as the grammar parser is good enough...
|
||||
|
||||
std::string sColumnQuery = "SELECT * FROM " + query.table().toString() + ";";
|
||||
if(m_query.rowIdColumns().empty())
|
||||
m_query.setRowIdColumn("_rowid_");
|
||||
m_headers.emplace_back("_rowid_");
|
||||
auto columns = getColumns(nullptr, sColumnQuery, m_vDataTypes);
|
||||
m_headers.insert(m_headers.end(), columns.begin(), columns.end());
|
||||
|
||||
getColumnNames("SELECT _rowid_,* FROM " + query.table().toString());
|
||||
}
|
||||
|
||||
// Tell the query object about the column names
|
||||
m_query.setColumNames(m_headers);
|
||||
|
||||
// Apply new query and update view
|
||||
buildQuery();
|
||||
updateAndRunQuery();
|
||||
}
|
||||
|
||||
void SqliteTableModel::setQuery(const QString& sQuery, const QString& sCountQuery, bool clearHeaders)
|
||||
void SqliteTableModel::setQuery(const QString& sQuery)
|
||||
{
|
||||
// clear
|
||||
if(clearHeaders)
|
||||
reset();
|
||||
else
|
||||
clearCache();
|
||||
|
||||
if(!m_db.isOpen())
|
||||
return;
|
||||
// Reset
|
||||
reset();
|
||||
|
||||
m_sQuery = sQuery.trimmed();
|
||||
removeCommentsFromQuery(m_sQuery);
|
||||
|
||||
worker->setQuery(m_sQuery, sCountQuery);
|
||||
getColumnNames(sQuery.toStdString());
|
||||
|
||||
if(clearHeaders)
|
||||
{
|
||||
auto columns = getColumns(worker->getDb(), sQuery.toStdString(), m_vDataTypes);
|
||||
m_headers.insert(m_headers.end(), columns.begin(), columns.end());
|
||||
}
|
||||
worker->setQuery(m_sQuery, QString());
|
||||
|
||||
// now fetch the first entries
|
||||
triggerCacheLoad(static_cast<int>(m_chunkSize / 2) - 1);
|
||||
@@ -606,7 +586,7 @@ void SqliteTableModel::sort(const std::vector<sqlb::SortedColumn>& columns)
|
||||
|
||||
// Set the new query (but only if a table has already been set
|
||||
if(!m_query.table().isEmpty())
|
||||
buildQuery();
|
||||
updateAndRunQuery();
|
||||
}
|
||||
|
||||
SqliteTableModel::Row SqliteTableModel::makeDefaultCacheEntry () const
|
||||
@@ -728,9 +708,19 @@ QModelIndex SqliteTableModel::dittoRecord(int old_row)
|
||||
return index(new_row, static_cast<int>(firstEditedColumn));
|
||||
}
|
||||
|
||||
void SqliteTableModel::buildQuery()
|
||||
void SqliteTableModel::updateAndRunQuery()
|
||||
{
|
||||
setQuery(QString::fromStdString(m_query.buildQuery(true)), QString::fromStdString(m_query.buildCountQuery()), false);
|
||||
clearCache();
|
||||
|
||||
// Update the query
|
||||
m_sQuery = QString::fromStdString(m_query.buildQuery(true));
|
||||
QString sCountQuery = QString::fromStdString(m_query.buildCountQuery());
|
||||
worker->setQuery(m_sQuery, sCountQuery);
|
||||
|
||||
// now fetch the first entries
|
||||
triggerCacheLoad(static_cast<int>(m_chunkSize / 2) - 1);
|
||||
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void SqliteTableModel::removeCommentsFromQuery(QString& query)
|
||||
@@ -797,28 +787,24 @@ void SqliteTableModel::removeCommentsFromQuery(QString& query)
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> SqliteTableModel::getColumns(std::shared_ptr<sqlite3> pDb, const std::string& sQuery, std::vector<int>& fieldsTypes) const
|
||||
void SqliteTableModel::getColumnNames(const std::string& sQuery)
|
||||
{
|
||||
if(!pDb)
|
||||
pDb = m_db.get(tr("retrieving list of columns"));
|
||||
auto pDb = m_db.get(tr("retrieving list of columns"));
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
std::vector<std::string> listColumns;
|
||||
if(sqlite3_prepare_v2(pDb.get(), sQuery.c_str(), static_cast<int>(sQuery.size()), &stmt, nullptr) == SQLITE_OK)
|
||||
{
|
||||
int columns = sqlite3_column_count(stmt);
|
||||
for(int i = 0; i < columns; ++i)
|
||||
{
|
||||
listColumns.push_back(sqlite3_column_name(stmt, i));
|
||||
fieldsTypes.push_back(sqlite3_column_type(stmt, i));
|
||||
m_headers.push_back(sqlite3_column_name(stmt, i));
|
||||
m_vDataTypes.push_back(sqlite3_column_type(stmt, i));
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return listColumns;
|
||||
}
|
||||
|
||||
void addCondFormatToMap(std::map<size_t, std::vector<CondFormat>>& mCondFormats, size_t column, const CondFormat& condFormat)
|
||||
static void addCondFormatToMap(std::map<size_t, std::vector<CondFormat>>& mCondFormats, size_t column, const CondFormat& condFormat)
|
||||
{
|
||||
// If the condition is already present in the vector, update that entry and respect the order, since two entries with the same
|
||||
// condition do not make sense.
|
||||
@@ -864,7 +850,7 @@ void SqliteTableModel::updateFilter(size_t column, const QString& value)
|
||||
m_query.where()[column] = whereClause;
|
||||
|
||||
// Build the new query
|
||||
buildQuery();
|
||||
updateAndRunQuery();
|
||||
}
|
||||
|
||||
void SqliteTableModel::updateGlobalFilter(const std::vector<QString>& values)
|
||||
@@ -875,7 +861,7 @@ void SqliteTableModel::updateGlobalFilter(const std::vector<QString>& values)
|
||||
m_query.setGlobalWhere(filters);
|
||||
|
||||
// Build the new query
|
||||
buildQuery();
|
||||
updateAndRunQuery();
|
||||
}
|
||||
|
||||
void SqliteTableModel::clearCache()
|
||||
@@ -893,13 +879,7 @@ void SqliteTableModel::clearCache()
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
// We want to clear the cache contents here but keep the initialised flag.
|
||||
// This is a bit hacky but the easiest way to achieve this. In the long term
|
||||
// we might want to change this whole concept of clearing and resetting.
|
||||
const bool cache_initialised = m_cache.initialised();
|
||||
m_cache.clear();
|
||||
if(cache_initialised)
|
||||
m_cache.setInitialised();
|
||||
|
||||
m_currentRowCount = 0;
|
||||
m_rowCountAvailable = RowCount::Unknown;
|
||||
@@ -971,7 +951,7 @@ void SqliteTableModel::setPseudoPk(std::vector<std::string> pseudoPk)
|
||||
if(m_headers.size())
|
||||
m_headers[0] = sqlb::joinStringVector(pseudoPk, ",");
|
||||
|
||||
buildQuery();
|
||||
updateAndRunQuery();
|
||||
}
|
||||
|
||||
bool SqliteTableModel::hasPseudoPk() const
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
QModelIndex dittoRecord(int old_row);
|
||||
|
||||
/// configure for browsing results of specified query
|
||||
void setQuery(const QString& sQuery, const QString& sCountQuery = QString(), bool clearHeaders = true);
|
||||
void setQuery(const QString& sQuery);
|
||||
|
||||
std::string query() const { return m_sQuery.toStdString(); }
|
||||
std::string customQuery(bool withRowid) const { return m_query.buildQuery(withRowid); }
|
||||
@@ -165,10 +165,9 @@ private:
|
||||
void handleFinishedFetch(int life_id, unsigned int fetched_row_begin, unsigned int fetched_row_end);
|
||||
void handleRowCountComplete(int life_id, int num_rows);
|
||||
|
||||
void buildQuery();
|
||||
void updateAndRunQuery();
|
||||
|
||||
/// \param pDb connection to query; if null, obtains it from 'm_db'.
|
||||
std::vector<std::string> getColumns(std::shared_ptr<sqlite3> pDb, const std::string& sQuery, std::vector<int>& fieldsTypes) const;
|
||||
void getColumnNames(const std::string& sQuery);
|
||||
|
||||
QByteArray encode(const QByteArray& str) const;
|
||||
QByteArray decode(const QByteArray& str) const;
|
||||
|
||||
Reference in New Issue
Block a user