Skip to content

Commit

Permalink
determine if pattern is starts-with or contains (#1070)
Browse files Browse the repository at this point in the history
Adds support to PatternMatcher to check if a pattern is
a strict starts-with or contains check. This can be useful
when mapping to some data stores that have optimizations
for those operations that will not work with arbitrary
regex. Also adds a method to get a contained string that
can be used as an initial filter.
  • Loading branch information
brharrington authored Aug 8, 2023
1 parent 2b2e651 commit 334a51a
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2022 Netflix, Inc.
* Copyright 2014-2023 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -54,6 +54,16 @@ default String prefix() {
return null;
}

/**
* Returns a fixed string that is contained within matching results for the pattern if one
* is available. This can be used with indexed data to help select a subset of values that
* are possible matches. If the pattern does not have a fixed sub-string, then null will be
* returned.
*/
default String containedString() {
return null;
}

/**
* The minimum possible length of a matching string. This can be used as a quick check
* to see if there is any way a given string could match.
Expand Down Expand Up @@ -92,6 +102,24 @@ default boolean neverMatches() {
return false;
}

/**
* Returns true if this matcher is equivalent to performing a starts with check on the
* prefix. This can be useful when mapping to storage that may have optimized prefix
* matching operators.
*/
default boolean isPrefixMatcher() {
return false;
}

/**
* Returns true if this matcher is equivalent to checking if a string contains a string.
* This can be useful when mapping to storage that may have optimized contains matching
* operators.
*/
default boolean isContainsMatcher() {
return false;
}

/**
* Returns a new matcher that matches the same pattern only ignoring the case of the input
* string. Note, character classes will be matched as is and must explicitly include both
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2022 Netflix, Inc.
* Copyright 2014-2023 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -52,6 +52,16 @@ Matcher next() {
return next;
}

@Override
public String containedString() {
return pattern;
}

@Override
public boolean isContainsMatcher() {
return next == TrueMatcher.INSTANCE;
}

private int indexOfIgnoreCase(String str, int offset) {
final int length = pattern.length();
final int end = (str.length() - length) + 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2022 Netflix, Inc.
* Copyright 2014-2023 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -76,6 +76,11 @@ public String prefix() {
return matchers[0].prefix();
}

@Override
public String containedString() {
return matchers[0].containedString();
}

@Override
public int minLength() {
return minLength;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 Netflix, Inc.
* Copyright 2014-2023 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -57,6 +57,21 @@ public String prefix() {
return pattern;
}

@Override
public String containedString() {
return pattern;
}

@Override
public boolean isPrefixMatcher() {
return true;
}

@Override
public boolean isContainsMatcher() {
return true;
}

@Override
public int minLength() {
return pattern.length();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2014-2023 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.netflix.spectator.impl.matcher;

import com.netflix.spectator.impl.PatternMatcher;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class ContainsTest {

private PatternMatcher re(String pattern) {
return PatternMatcher.compile(pattern);
}

private void assertStartsWith(String pattern) {
PatternMatcher m = re(pattern);
Assertions.assertTrue(m.isPrefixMatcher(), pattern);
Assertions.assertTrue(m.isContainsMatcher(), pattern);
}

private void assertContainsOnly(String pattern) {
PatternMatcher m = re(pattern);
Assertions.assertFalse(m.isPrefixMatcher(), pattern);
Assertions.assertTrue(m.isContainsMatcher(), pattern);
}

@Test
public void startsWith() {
assertStartsWith("^abc");
assertStartsWith("^abc[.]def");
assertStartsWith("^abc\\.def");
}

@Test
public void notStartsWith() {
Assertions.assertFalse(re("abc").isPrefixMatcher());
Assertions.assertFalse(re("ab[cd]").isPrefixMatcher());
Assertions.assertFalse(re("^abc.def").isPrefixMatcher());
}

@Test
public void contains() {
assertContainsOnly("abc");
assertContainsOnly(".*abc");
assertContainsOnly("abc\\.def");
}

@Test
public void notContains() {
Assertions.assertFalse(re("ab[cd]").isContainsMatcher());
Assertions.assertFalse(re("abc.def").isContainsMatcher());
}

@Test
public void containedString() {
Assertions.assertEquals("abc", re("abc").containedString());
Assertions.assertEquals("abc", re(".*abc").containedString());
Assertions.assertEquals("ab", re(".*ab[cd]").containedString());
Assertions.assertEquals("abc", re("abc.def").containedString());
Assertions.assertEquals("abc.def", re("abc\\.def").containedString());
Assertions.assertEquals("abc", re("^abc.def").containedString());
Assertions.assertEquals("abc.def", re("^abc\\.def").containedString());
}
}

0 comments on commit 334a51a

Please sign in to comment.