This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin.git


The following commit(s) were added to refs/heads/master by this push:
     new 9e2b767  perf: fast-path KeyMatch2/3/4/5 for plain keys and "*"
9e2b767 is described below

commit 9e2b76713fc4d19d8d49c41dcba336696f88ce3c
Author: Yang Luo <[email protected]>
AuthorDate: Tue Aug 4 15:39:36 2026 +0800

    perf: fast-path KeyMatch2/3/4/5 for plain keys and "*"
---
 util/builtin_operators.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/util/builtin_operators.go b/util/builtin_operators.go
index 27687f3..c7861f8 100644
--- a/util/builtin_operators.go
+++ b/util/builtin_operators.go
@@ -44,6 +44,46 @@ var (
        reCacheMu   = sync.RWMutex{}
 )
 
+// regexpMetaChars is exactly the set of characters that regexp.QuoteMeta 
escapes.
+// It is spelled out instead of calling QuoteMeta so that keyMatchShortcut 
never
+// allocates; TestRegexpMetaChars guards the two against drifting apart.
+const regexpMetaChars = `\.+*?()|[]{}^$`
+
+// keyMatchShortcut answers KeyMatch2/3/4/5 for the two cases that need no 
regexp
+// work at all. ok reports whether matched is the final answer; when it is 
false
+// the caller falls through to its unchanged rewrite-and-match path.
+//
+// The two cases are:
+//
+//  1. key2 == "*", the bare wildcard used as the "all domains" key. The regexp
+//     path reaches the same answer via "^*$", which only works by accident 
(see
+//     issue #330); making it explicit is both faster and less fragile.
+//
+//  2. key2 is plain, i.e. it holds no regexp metacharacter and none of the
+//     matcher's own pattern characters (extraChars, ":" for KeyMatch2's :param
+//     dialect; the "{}" and "*" of the other matchers are already 
metacharacters).
+//     Then "^"+key2+"$" can only match key2 itself, so key1 == key2 is the 
answer.
+//
+// Case 2 has to stay conservative because RegexMatch embeds key2 unescaped: a
+// stray metacharacter makes key2 behave as a regexp, so "acme.com" does match
+// "acmeXcom", and key1 == key2 is not sufficient either ("^a+b$" does not 
match
+// "a+b"). Any such key2 keeps taking the regexp path.
+//
+// This matters because a DomainMatchingFunc is called O(N*D) times while 
building
+// role links, and real deployments mostly hold concrete domains ("team/123") 
that
+// pay for the full pipeline only to come back false.
+func keyMatchShortcut(key1 string, key2 string, extraChars string) (matched 
bool, ok bool) {
+       if key2 == "*" {
+               return true, true
+       }
+
+       if !strings.ContainsAny(key2, regexpMetaChars) && 
!strings.ContainsAny(key2, extraChars) {
+               return key1 == key2, true
+       }
+
+       return false, false
+}
+
 func mustCompileOrGet(key string) *regexp.Regexp {
        reCacheMu.RLock()
        re, ok := reCache[key]
@@ -140,6 +180,10 @@ func KeyGetFunc(args ...interface{}) (interface{}, error) {
 // KeyMatch2 determines whether key1 matches the pattern of key2 (similar to 
RESTful path), key2 can contain a *.
 // For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource".
 func KeyMatch2(key1 string, key2 string) bool {
+       if matched, ok := keyMatchShortcut(key1, key2, ":"); ok {
+               return matched
+       }
+
        key2 = strings.Replace(key2, "/*", "/.*", -1)
 
        key2 = keyMatch2Re.ReplaceAllString(key2, "$1[^/]+$2")
@@ -197,6 +241,10 @@ func KeyGet2Func(args ...interface{}) (interface{}, error) 
{
 // KeyMatch3 determines whether key1 matches the pattern of key2 (similar to 
RESTful path), key2 can contain a *.
 // For example, "/foo/bar" matches "/foo/*", "/resource1" matches 
"/{resource}".
 func KeyMatch3(key1 string, key2 string) bool {
+       if matched, ok := keyMatchShortcut(key1, key2, ""); ok {
+               return matched
+       }
+
        key2 = strings.Replace(key2, "/*", "/.*", -1)
        key2 = keyMatch3Re.ReplaceAllString(key2, "$1[^/]+$2")
 
@@ -256,6 +304,10 @@ func KeyGet3Func(args ...interface{}) (interface{}, error) 
{
 // "/parent/123/child/456" does not match "/parent/{id}/child/{id}"
 // But KeyMatch3 will match both.
 func KeyMatch4(key1 string, key2 string) bool {
+       if matched, ok := keyMatchShortcut(key1, key2, ""); ok {
+               return matched
+       }
+
        key2 = strings.Replace(key2, "/*", "/.*", -1)
 
        tokens := []string{}
@@ -315,6 +367,12 @@ func KeyMatch5(key1 string, key2 string) bool {
                key1 = key1[:i]
        }
 
+       // After the query string is stripped, so that key1 is compared in the 
same
+       // shape the regexp path would have matched.
+       if matched, ok := keyMatchShortcut(key1, key2, ""); ok {
+               return matched
+       }
+
        key2 = strings.Replace(key2, "/*", "/.*", -1)
        key2 = keyMatch5Re.ReplaceAllString(key2, "$1[^/]+$2")
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to