From 9e23b1e6c37f6115742458591ad85fad151df055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoshioka=20Tsuneo=20=28=E5=90=89=E5=B2=A1=20=E6=81=92?= =?UTF-8?q?=E5=A4=AB=29?= Date: Sun, 28 Sep 2025 01:33:14 +0900 Subject: [PATCH 1/4] Advanced query search syntax for multi byte search --- redash/models/__init__.py | 45 +++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/redash/models/__init__.py b/redash/models/__init__.py index 7cc551b659..d331960996 100644 --- a/redash/models/__init__.py +++ b/redash/models/__init__.py @@ -2,6 +2,7 @@ import datetime import logging import numbers +import re import time import pytz @@ -644,6 +645,43 @@ def outdated_queries(cls): return list(outdated_queries.values()) + @classmethod + def _do_multi_byte_search(cls, all_queries, term, limit=None): + # term examples: + # - word + # - name:word + # - query:word + # - "multiple words" + # - name:"multiple words" + # - word1 word2 word3 + # - word1 "multiple word" query:"select foo" + tokens = re.findall(r'(?:([^:\s]+):)?(?:"([^"]+)"|(\S+))', term) + conditions = [] + for token in tokens: + key = None + if token[0]: + key = token[0] + + if token[1]: + value = token[1] + else: + value = token[2] + + pattern = f"%{value}%" + + if key == "id" and value.isdigit(): + conditions.append(cls.id.equal(int(value))) + elif key == "name": + conditions.append(cls.name.ilike(pattern)) + elif key == "query": + conditions.append(cls.query_text.ilike(pattern)) + elif key == "description": + conditions.append(cls.description.ilike(pattern)) + else: + conditions.append(or_(cls.name.ilike(pattern), cls.description.ilike(pattern))) + + return all_queries.filter(and_(*conditions)).order_by(Query.id).limit(limit) + @classmethod def search( cls, @@ -664,12 +702,7 @@ def search( if multi_byte_search: # Since tsvector doesn't work well with CJK languages, use `ilike` too - pattern = "%{}%".format(term) - return ( - all_queries.filter(or_(cls.name.ilike(pattern), cls.description.ilike(pattern))) - .order_by(Query.id) - .limit(limit) - ) + return cls._do_multi_byte_search(all_queries, term, limit) # sort the result using the weight as defined in the search vector column return all_queries.search(term, sort=True).limit(limit) From e0cee569cf6e18936413c695ef864b67d282d03d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoshioka=20Tsuneo=20=28=E5=90=89=E5=B2=A1=20=E6=81=92?= =?UTF-8?q?=E5=A4=AB=29?= Date: Thu, 2 Oct 2025 01:20:26 +0900 Subject: [PATCH 2/4] Advanced search for my queries --- redash/models/__init__.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/redash/models/__init__.py b/redash/models/__init__.py index d331960996..7f0518cf4d 100644 --- a/redash/models/__init__.py +++ b/redash/models/__init__.py @@ -711,13 +711,7 @@ def search( def search_by_user(cls, term, user, limit=None, multi_byte_search=False): if multi_byte_search: # Since tsvector doesn't work well with CJK languages, use `ilike` too - pattern = "%{}%".format(term) - return ( - cls.by_user(user) - .filter(or_(cls.name.ilike(pattern), cls.description.ilike(pattern))) - .order_by(Query.id) - .limit(limit) - ) + return cls._do_multi_byte_search(cls.by_user(user), term, limit) return cls.by_user(user).search(term, sort=True).limit(limit) From 43148ba6ac30b5346a392f5747acf42601e521ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoshioka=20Tsuneo=20=28=E5=90=89=E5=B2=A1=20=E6=81=92?= =?UTF-8?q?=E5=A4=AB=29?= Date: Thu, 9 Oct 2025 00:30:13 +0900 Subject: [PATCH 3/4] Add advanced query seearch tooltip --- client/app/pages/queries-list/QueriesList.jsx | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/client/app/pages/queries-list/QueriesList.jsx b/client/app/pages/queries-list/QueriesList.jsx index aa9f4e8484..17642f9e64 100644 --- a/client/app/pages/queries-list/QueriesList.jsx +++ b/client/app/pages/queries-list/QueriesList.jsx @@ -27,6 +27,23 @@ import routes from "@/services/routes"; import QueriesListEmptyState from "./QueriesListEmptyState"; import "./queries-list.css"; +import QuestionCircleFilledIcon from "@ant-design/icons/QuestionCircleFilled"; +import Tooltip from "@/components/Tooltip"; + +const searchQueryTooltipTitle = `Search Syntax: +- term + => find queries containing 'term' in the query name or description +- term1 term2 + => find queries containing both term1 and term2 +- "exact phrase" + => find queries containing the exact phrase +- query:term + => find queries containing 'term' in the query text +- term1 query:term2 + => find queries containing 'term' in the query name/description and 'term2' in the query text +- query:"exact phrase" + => find queries containing the exact phrase in the query text +`; const sidebarMenu = [ { @@ -145,6 +162,17 @@ function QueriesList({ controller }) { /> + {clientConfig.multiByteSearchEnabled && ( +
+ + + +
+ )} Date: Wed, 15 Oct 2025 22:44:48 +0900 Subject: [PATCH 4/4] Revert "Add advanced query seearch tooltip" This reverts commit 43148ba6ac30b5346a392f5747acf42601e521ab. --- client/app/pages/queries-list/QueriesList.jsx | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/client/app/pages/queries-list/QueriesList.jsx b/client/app/pages/queries-list/QueriesList.jsx index 17642f9e64..aa9f4e8484 100644 --- a/client/app/pages/queries-list/QueriesList.jsx +++ b/client/app/pages/queries-list/QueriesList.jsx @@ -27,23 +27,6 @@ import routes from "@/services/routes"; import QueriesListEmptyState from "./QueriesListEmptyState"; import "./queries-list.css"; -import QuestionCircleFilledIcon from "@ant-design/icons/QuestionCircleFilled"; -import Tooltip from "@/components/Tooltip"; - -const searchQueryTooltipTitle = `Search Syntax: -- term - => find queries containing 'term' in the query name or description -- term1 term2 - => find queries containing both term1 and term2 -- "exact phrase" - => find queries containing the exact phrase -- query:term - => find queries containing 'term' in the query text -- term1 query:term2 - => find queries containing 'term' in the query name/description and 'term2' in the query text -- query:"exact phrase" - => find queries containing the exact phrase in the query text -`; const sidebarMenu = [ { @@ -162,17 +145,6 @@ function QueriesList({ controller }) { /> - {clientConfig.multiByteSearchEnabled && ( -
- - - -
- )}