From 29e4f183b7cb765d5fbad9f947ccf8b50fa46132 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 16 Jul 2021 16:22:41 +0300 Subject: [PATCH] Add support for regex in NS separators Fix #3669 --- src/app/models/connectionconf.cpp | 10 ++++ src/app/models/connectionconf.h | 5 ++ src/app/models/treeoperations.cpp | 5 +- src/app/models/treeoperations.h | 2 +- .../connections-tree/items/keyitem.cpp | 14 ++++-- .../connections-tree/items/namespaceitem.cpp | 22 +++++---- .../connections-tree/items/namespaceitem.h | 3 +- .../connections-tree/keysrendering.cpp | 47 ++++++++++++++----- src/modules/connections-tree/keysrendering.h | 8 ++-- src/modules/connections-tree/operations.h | 2 +- src/qml/ConnectionSettignsDialog.qml | 26 ++++++---- 11 files changed, 101 insertions(+), 43 deletions(-) diff --git a/src/app/models/connectionconf.cpp b/src/app/models/connectionconf.cpp index 1fd7507f8..c2bef3aa0 100644 --- a/src/app/models/connectionconf.cpp +++ b/src/app/models/connectionconf.cpp @@ -39,6 +39,16 @@ void ServerConfig::setNamespaceSeparator(QString ns) return setParam("namespace_separator", ns); } +bool ServerConfig::namespaceSeparatorIsRegex() const +{ + return param("namespace_separator_is_regex", false); +} + +void ServerConfig::setNamespaceSeparatorIsRegex(bool v) +{ + return setParam("namespace_separator_is_regex", v); +} + uint ServerConfig::databaseScanLimit() const { return param("db_scan_limit", DEFAULT_DB_SCAN_LIMIT); diff --git a/src/app/models/connectionconf.h b/src/app/models/connectionconf.h index 2b50f94fd..059450d68 100644 --- a/src/app/models/connectionconf.h +++ b/src/app/models/connectionconf.h @@ -32,6 +32,7 @@ class ServerConfig : public RedisClient::ConnectionConfig /* Advanced settings */ Q_PROPERTY(QString keysPattern READ keysPattern WRITE setKeysPattern) Q_PROPERTY(QString namespaceSeparator READ namespaceSeparator WRITE setNamespaceSeparator) + Q_PROPERTY(bool namespaceSeparatorIsRegex READ namespaceSeparatorIsRegex WRITE setNamespaceSeparatorIsRegex) Q_PROPERTY(uint executeTimeout READ executeTimeout WRITE setExecutionTimeout) Q_PROPERTY(uint connectionTimeout READ connectionTimeout WRITE setConnectionTimeout) Q_PROPERTY(bool overrideClusterHost READ overrideClusterHost WRITE setClusterHostOverride) @@ -59,6 +60,10 @@ class ServerConfig : public RedisClient::ConnectionConfig QString namespaceSeparator() const; void setNamespaceSeparator(QString); + bool namespaceSeparatorIsRegex() const; + void setNamespaceSeparatorIsRegex(bool v); + + bool luaKeysLoading() const; void setLuaKeysLoading(bool); uint databaseScanLimit() const; diff --git a/src/app/models/treeoperations.cpp b/src/app/models/treeoperations.cpp index b4d85323c..a55742129 100644 --- a/src/app/models/treeoperations.cpp +++ b/src/app/models/treeoperations.cpp @@ -236,8 +236,9 @@ void TreeOperations::resetConnection() { QtConcurrent::run([oldConnection]() { oldConnection->disconnect(); }); } -QString TreeOperations::getNamespaceSeparator() { - return m_config.namespaceSeparator(); +QRegExp TreeOperations::getNamespaceSeparator() { + return QRegExp(m_config.namespaceSeparator(), Qt::CaseSensitive, + m_config.namespaceSeparatorIsRegex()? QRegExp::RegExp : QRegExp::FixedString); } QString TreeOperations::defaultFilter() { return m_config.keysPattern(); } diff --git a/src/app/models/treeoperations.h b/src/app/models/treeoperations.h index 4548cad44..e5c886513 100644 --- a/src/app/models/treeoperations.h +++ b/src/app/models/treeoperations.h @@ -36,7 +36,7 @@ class TreeOperations : public QObject, void resetConnection() override; - QString getNamespaceSeparator() override; + QRegExp getNamespaceSeparator() override; QString defaultFilter() override; diff --git a/src/modules/connections-tree/items/keyitem.cpp b/src/modules/connections-tree/items/keyitem.cpp index 732d6fcdb..b645a255f 100644 --- a/src/modules/connections-tree/items/keyitem.cpp +++ b/src/modules/connections-tree/items/keyitem.cpp @@ -39,9 +39,17 @@ QString KeyItem::getDisplayName() const { m_shortRendering) { auto parent = parentTreeItemToNs(m_parent); - title = printableString(getFullPath().mid( - parent->getFullPath().size() + - parent->operations()->getNamespaceSeparator().size())); + auto nsRx = parent->operations()->getNamespaceSeparator(); + int searchFrom = parent->getFullPath().size() > 0 ? parent->getFullPath().size() - 1 : 0; + int nsRxPos = QString::fromUtf8(getFullPath()).indexOf(nsRx, searchFrom); + + int nsSize = 0; + + if (nsRxPos >= 0) { + nsSize = nsRx.matchedLength(); + } + + title = printableString(getFullPath().mid(parent->getFullPath().size() + nsSize)); } else { title = printableString(getFullPath(), true); } diff --git a/src/modules/connections-tree/items/namespaceitem.cpp b/src/modules/connections-tree/items/namespaceitem.cpp index 00c33c2db..b610e0a1e 100644 --- a/src/modules/connections-tree/items/namespaceitem.cpp +++ b/src/modules/connections-tree/items/namespaceitem.cpp @@ -15,9 +15,10 @@ using namespace ConnectionsTree; NamespaceItem::NamespaceItem(const QByteArray &fullPath, QSharedPointer operations, QWeakPointer parent, Model &model, - uint dbIndex, QRegExp filter) + uint dbIndex, QRegExp filter, QString lastNsSeparator) : AbstractNamespaceItem(model, parent, operations, dbIndex, filter), m_fullPath(fullPath), + m_lastNsSeparator(lastNsSeparator), m_removed(false) {} QString NamespaceItem::getDisplayName() const { @@ -33,10 +34,11 @@ QString NamespaceItem::getDisplayName() const { } QByteArray NamespaceItem::getName() const { - qsizetype pos = m_fullPath.lastIndexOf(m_operations->getNamespaceSeparator()); + auto rx = m_operations->getNamespaceSeparator(); + qsizetype pos = QString::fromUtf8(m_fullPath).lastIndexOf(rx); if (pos >= 0) { - return m_fullPath.mid(pos + m_operations->getNamespaceSeparator().size()); + return m_fullPath.mid(pos + rx.matchedLength()); } else { return m_fullPath; } @@ -71,18 +73,18 @@ void NamespaceItem::load() { return renderRawKeys(rawKeys, m_filter, onKeysRendered, true, false); } - QString nsFilter = QString("%1%2*") - .arg(QString::fromUtf8(m_fullPath)) - .arg(m_operations->getNamespaceSeparator()); + QString nsFilter = QString("%1%2*") + .arg(QString::fromUtf8(m_fullPath)) + .arg(m_lastNsSeparator); if (!m_filter.isEmpty()) { if (m_filter.pattern().startsWith(nsFilter.chopped(1))) { nsFilter = m_filter.pattern(); } else { nsFilter = QString("%1%2%3") - .arg(QString::fromUtf8(m_fullPath)) - .arg(m_operations->getNamespaceSeparator()) - .arg(m_filter.pattern()); + .arg(QString::fromUtf8(m_fullPath)) + .arg(m_lastNsSeparator) + .arg(m_filter.pattern()); } } @@ -134,7 +136,7 @@ QHash> NamespaceItem::eventHandlers() { }, QString("%1%2") .arg(QString::fromUtf8(getFullPath())) - .arg(m_operations->getNamespaceSeparator())); + .arg(m_lastNsSeparator)); }); events.insert("reload", [this]() { reload(); }); diff --git a/src/modules/connections-tree/items/namespaceitem.h b/src/modules/connections-tree/items/namespaceitem.h index ed2cf3da1..48faf0011 100644 --- a/src/modules/connections-tree/items/namespaceitem.h +++ b/src/modules/connections-tree/items/namespaceitem.h @@ -15,7 +15,7 @@ class NamespaceItem : public AbstractNamespaceItem { NamespaceItem(const QByteArray& fullPath, QSharedPointer operations, QWeakPointer parent, Model& model, uint dbIndex, - QRegExp filter); + QRegExp filter, QString lastNsSeparator); QString getDisplayName() const override; @@ -38,6 +38,7 @@ class NamespaceItem : public AbstractNamespaceItem { private: QByteArray m_fullPath; + QString m_lastNsSeparator; bool m_removed; }; } // namespace ConnectionsTree diff --git a/src/modules/connections-tree/keysrendering.cpp b/src/modules/connections-tree/keysrendering.cpp index 7937bee21..9bdf423bd 100644 --- a/src/modules/connections-tree/keysrendering.cpp +++ b/src/modules/connections-tree/keysrendering.cpp @@ -35,8 +35,20 @@ void KeysTreeRenderer::renderKeys(QSharedPointer operations, int unprocessedPartStart = 0; if (parent->getFullPath().size() > 0 || parent->type() == "namespace") { - unprocessedPartStart = - parent->getFullPath().size() + settings.nsSeparator.length(); + int nsLength = 0; + + if (keys.size() > 0) { + QString firstKey = QString::fromUtf8(keys[0]); + int res = firstKey.indexOf(settings.nsSeparator, parent->getFullPath().size()); + + qDebug() << "NSs regex pos:" << res; + + nsLength = settings.nsSeparator.matchedLength(); + } + + unprocessedPartStart = + parent->getFullPath().size() + nsLength; + } auto rootItem = resolveRootItem(parent); @@ -68,10 +80,12 @@ void KeysTreeRenderer::renderKeys(QSharedPointer operations, auto isBulkInsert = [settings, preRenderedKeysSet, unprocessedPartStart]( const QByteArray ¤t, const QByteArray &next) { + QString currentKey = QString::fromUtf8(current); + QString nextKey = QString::fromUtf8(current); return (settings.appendNewItems && - current.indexOf(settings.nsSeparator, unprocessedPartStart) == -1 && + currentKey.indexOf(settings.nsSeparator, unprocessedPartStart) == -1 && !next.isEmpty() && - next.indexOf(settings.nsSeparator, unprocessedPartStart) == -1 && + nextKey.indexOf(settings.nsSeparator, unprocessedPartStart) == -1 && !preRenderedKeysSet.contains(next)); }; @@ -130,7 +144,7 @@ void KeysTreeRenderer::renderKeys(QSharedPointer operations, parent->showLoadingError("Not enough memory to render all keys"); break; } - } + } if (preRenderedKeysToBeRemoved.size() > 0) { QList> obsoleteKeys; @@ -161,10 +175,17 @@ void KeysTreeRenderer::renderLazily(QSharedPointer root, QWeakPointer currentParent = parent.staticCast().toWeakRef(); - int indexOfNaspaceSeparator = - (settings.nsSeparator.isEmpty()) - ? -1 - : notProcessedKeyPart.indexOf(settings.nsSeparator); + int indexOfNaspaceSeparator = -1; + auto nsSeparator = settings.nsSeparator; + int nsSeparatorLength = nsSeparator.pattern().size(); + + if (!nsSeparator.isEmpty() && nsSeparator.patternSyntax() == QRegExp::RegExp) { + QString keyPart = QString::fromUtf8(notProcessedKeyPart); + indexOfNaspaceSeparator = keyPart.indexOf(nsSeparator); + + qDebug() << "NSs regex pos:" << indexOfNaspaceSeparator << nsSeparator.cap(); + nsSeparatorLength = nsSeparator.matchedLength(); + } if (indexOfNaspaceSeparator == -1) { if (parent->getAllChilds().size() >= settings.renderLimit) { @@ -199,7 +220,8 @@ void KeysTreeRenderer::renderLazily(QSharedPointer root, QByteArray namespaceFullPath = fullKey.mid(0, nsPos); // Single namespaced key - if (nextKey.isEmpty() || nextKey.indexOf(namespaceFullPath) == -1) { +if (nsSeparator.patternSyntax() != QRegExp::RegExp + && (nextKey.isEmpty() || nextKey.indexOf(namespaceFullPath) == -1)) { QSharedPointer newKey(new KeyItem(fullKey, currentParent, parent->model(), settings.shortKeysRendering)); @@ -209,7 +231,7 @@ void KeysTreeRenderer::renderLazily(QSharedPointer root, namespaceItem = QSharedPointer( new NamespaceItem(namespaceFullPath, m_operations, currentParent, - parent->model(), settings.dbIndex, settings.filter)); + parent->model(), settings.dbIndex, settings.filter, nsSeparator.cap())); if (expandedNamespaces.contains(namespaceFullPath)) { namespaceItem->setExpanded(true); @@ -219,8 +241,7 @@ void KeysTreeRenderer::renderLazily(QSharedPointer root, } renderLazily(root, namespaceItem, - notProcessedKeyPart.mid(indexOfNaspaceSeparator + - settings.nsSeparator.length()), + notProcessedKeyPart.mid(indexOfNaspaceSeparator + nsSeparatorLength), fullKey, m_operations, settings, expandedNamespaces, level + 1, nextKey); } diff --git a/src/modules/connections-tree/keysrendering.h b/src/modules/connections-tree/keysrendering.h index 3bdda485f..69c6bfb44 100644 --- a/src/modules/connections-tree/keysrendering.h +++ b/src/modules/connections-tree/keysrendering.h @@ -18,9 +18,9 @@ namespace ConnectionsTree { public: struct RenderingSettigns { QRegExp filter; - QString nsSeparator; - uint dbIndex; - uint renderLimit; + QRegExp nsSeparator; + uint dbIndex; + uint renderLimit; bool appendNewItems; bool checkPreRenderedItems; bool shortKeysRendering; @@ -31,7 +31,7 @@ namespace ConnectionsTree { RedisClient::Connection::RawKeysList keys, QSharedPointer parent, RenderingSettigns settings, - const QSet &expandedNamespaces); + const QSet &expandedNamespaces); private: static void renderLazily(QSharedPointer root, diff --git a/src/modules/connections-tree/operations.h b/src/modules/connections-tree/operations.h index ba2fdcea4..de5a237db 100644 --- a/src/modules/connections-tree/operations.h +++ b/src/modules/connections-tree/operations.h @@ -58,7 +58,7 @@ class Operations { * @brief getNamespaceSeparator * @return */ - virtual QString getNamespaceSeparator() = 0; + virtual QRegExp getNamespaceSeparator() = 0; virtual QString defaultFilter() = 0; diff --git a/src/qml/ConnectionSettignsDialog.qml b/src/qml/ConnectionSettignsDialog.qml index b400d791a..a9825f514 100644 --- a/src/qml/ConnectionSettignsDialog.qml +++ b/src/qml/ConnectionSettignsDialog.qml @@ -761,14 +761,24 @@ Dialog { BetterLabel { text: qsTranslate("RDM","Namespace Separator:") } - BetterTextField - { - id: namespaceSeparator - Layout.fillWidth: true - objectName: "rdm_advanced_settings_namespace_separator_field" - placeholderText: qsTranslate("RDM","Separator used for namespace extraction from keys") - text: root.settings ? root.settings.namespaceSeparator : "" - onTextChanged: if (root.settings) { root.settings.namespaceSeparator = text } + RowLayout { + BetterTextField + { + id: namespaceSeparator + Layout.fillWidth: true + objectName: "rdm_advanced_settings_namespace_separator_field" + placeholderText: qsTranslate("RDM","Separator used for namespace extraction from keys") + text: root.settings ? root.settings.namespaceSeparator : "" + onTextChanged: if (root.settings) { root.settings.namespaceSeparator = text } + } + + BetterCheckbox { + id: nsRegex + Layout.fillWidth: true + checked: root.settings ? root.settings.namespaceSeparatorIsRegex : false + onCheckedChanged: if (root.settings) { root.settings.namespaceSeparatorIsRegex = checked } + text: qsTranslate("RDM","Regex") + } } SettingsGroupTitle {