You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by kr...@apache.org on 2022/01/07 21:57:49 UTC

[tinkerpop] branch TINKERPOP-2652 updated (77056d8 -> 70e8245)

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

krlawrence pushed a change to branch TINKERPOP-2652
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git.


 discard 77056d8  Add TextP.regex to the CHANGELOG
 discard b70c40b  Add documentation for TextP.regex
 discard 3c40990  Add dotnet Gherkin test
 discard dc28f43  More improvements to regex Gherkin tests
 discard 9dcc3e5  Initial work on regex Gherkin scenarios
 discard 36ace17  Add regex to .Net and Javascript GLVs
 discard 7237b00  Continue adding regex predicates to Python client
 discard efb57e4  Regex tests
 discard 3790207  Add actual mappings for regex when used via Antlr
 discard 6832a6b  Add base class visitor methods for regex via grammar
 discard f920fcf  Add redex predicates to the Antlr grammar
 discard eeb584a  Initial changes for regex support. More to follow
     new 70e8245  Add Text.Regex text predicate

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (77056d8)
            \
             N -- N -- N   refs/heads/TINKERPOP-2652 (70e8245)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:

[tinkerpop] 01/01: Add Text.Regex text predicate

Posted by kr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

krlawrence pushed a commit to branch TINKERPOP-2652
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 70e8245821a132b977f8f19c415942f2a9fa4d14
Author: Kelvin Lawrence <gf...@yahoo.com>
AuthorDate: Thu Nov 18 13:47:40 2021 -0600

    Add Text.Regex text predicate
---
 CHANGELOG.asciidoc                                 |  1 +
 docs/src/reference/the-traversal.asciidoc          |  9 ++++-
 .../language/grammar/GremlinBaseVisitor.java       | 10 +++++
 .../grammar/TraversalPredicateVisitor.java         | 10 +++++
 .../tinkerpop/gremlin/process/traversal/Text.java  | 44 ++++++++++++++++++++++
 .../tinkerpop/gremlin/process/traversal/TextP.java | 18 +++++++++
 .../grammar/TraversalPredicateVisitorTest.java     |  3 ++
 .../tinkerpop/gremlin/process/traversal/PTest.java | 14 +++++++
 .../src/Gremlin.Net/Process/Traversal/TextP.cs     |  9 +++++
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |  2 +
 .../gremlin-javascript/lib/process/traversal.js    |  9 +++++
 .../gremlin-javascript/test/cucumber/gremlin.js    |  2 +
 gremlin-language/src/main/antlr4/Gremlin.g4        | 10 +++++
 .../python/gremlin_python/process/traversal.py     | 17 +++++++++
 gremlin-python/src/main/python/radish/gremlin.py   |  2 +
 gremlin-test/features/filter/Has.feature           | 30 ++++++++++++++-
 16 files changed, 187 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index d919c0d..9aee9d8 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -23,6 +23,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 [[release-3-6-0]]
 === TinkerPop 3.6.0 (Release Date: NOT OFFICIALLY RELEASED YET)
 
+* Added `TextP.regex` and `TextP.notRegex`.
 * Changed TinkerGraph to allow identifiers to be heterogeneous when filtering.
 * Prevented values of `T` to `property()` from being `null`.
 * Added `fail()` step.
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index 98f2e8d..7dd001b 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -4077,7 +4077,8 @@ A `P` is a predicate of the form `Function<Object,Boolean>`. That is, given some
 the release of TinkerPop 3.4.0, Gremlin also supports simple text predicates, which only work on `String` values. The `TextP`
 text predicates extend the `P` predicates, but are specialized in that they are of the form `Function<String,Boolean>`.
 The provided predicates are outlined in the table below and are used in various steps such as <<has-step,`has()`>>-step,
-<<where-step,`where()`>>-step, <<is-step,`is()`>>-step, etc.
+<<where-step,`where()`>>-step, <<is-step,`is()`>>-step, etc. Two new additional `TextP` predicate members were added in the
+TinkerPop 3.6.0 release that allow working with regular expressions. These are `TextP.regex` and `TextP.notRegex`
 
 [width="100%",cols="3,15",options="header"]
 |=========================================================
@@ -4099,8 +4100,12 @@ The provided predicates are outlined in the table below and are used in various
 | `TextP.notStartingWith(string)` | Does the incoming `String` not start with the provided `String`?
 | `TextP.notEndingWith(string)` | Does the incoming `String` not end with the provided `String`?
 | `TextP.notContaining(string)` | Does the incoming `String` not contain the provided `String`?
+| `TextP.regex(string)` | Does the incoming `String` match the regular expression in the provided `String`?
+| `TextP.notRegex(string)` | Does the incoming `String` fail to match the regular expression in the provided `String`?
 |=========================================================
-
+Note that the TinkerPop reference implementation uses the Java `Pattern` and `Matcher` classes for it regular expression
+engine. Other implementations may decide to use a different regular expression engine. It's a good idea to check
+the documentation for the implementation you are using to verify the allowed regular expression syntax.
 [gremlin-groovy]
 ----
 eq(2)
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
index fd48b79..b25418b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
@@ -1050,6 +1050,16 @@ public class GremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
 	public T visitTraversalPredicate_notEndingWith(final GremlinParser.TraversalPredicate_notEndingWithContext ctx) {
 		notImplemented(ctx); return null;
 	}
+
+	@Override
+	public T visitTraversalPredicate_regex(final GremlinParser.TraversalPredicate_regexContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalPredicate_notRegex(final GremlinParser.TraversalPredicate_notRegexContext ctx) {
+		notImplemented(ctx); return null;
+	}
 	/**
 	 * {@inheritDoc}
 	 */
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitor.java
index 608c20d..d46fc49 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitor.java
@@ -264,4 +264,14 @@ public class TraversalPredicateVisitor extends GremlinBaseVisitor<P> {
     public P visitTraversalPredicate_notStartingWith(final GremlinParser.TraversalPredicate_notStartingWithContext ctx) {
         return TextP.notStartingWith(GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()));
     }
+
+    @Override
+    public P visitTraversalPredicate_regex(final GremlinParser.TraversalPredicate_regexContext ctx) {
+        return TextP.regex(GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()));
+    }
+
+    @Override
+    public P visitTraversalPredicate_notRegex(final GremlinParser.TraversalPredicate_notRegexContext ctx) {
+        return TextP.notRegex(GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()));
+    }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
index 9c25825..91fcd7d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
@@ -19,6 +19,8 @@
 package org.apache.tinkerpop.gremlin.process.traversal;
 
 import java.util.function.BiPredicate;
+import java.util.regex.Pattern; 
+import java.util.regex.Matcher; 
 
 /**
  * {@link Text} is a {@link java.util.function.BiPredicate} that determines whether the first string starts with, starts
@@ -30,6 +32,48 @@ import java.util.function.BiPredicate;
 public enum Text implements BiPredicate<String, String> {
 
     /**
+     * Evaluates if the first string has a regex match with the second (pattern).
+     *
+     * @since 3.6.0
+     */
+    regex {
+        @Override
+        public boolean test(final String value, final String regex) {
+            Pattern pattern = Pattern.compile(regex);
+            Matcher matcher = pattern.matcher(value);
+            return matcher.find();   
+        }
+
+        /**
+         * The negative of {@code regex} is {@link #notRegex}.
+         */
+        @Override
+        public Text negate() {
+            return notRegex;
+        }
+    },
+    /**
+     * Evaluates if the first string does not have a regex match with the second (pattern).
+     *
+     * @since 3.6.0
+     */
+    notRegex {
+        @Override
+        public boolean test(final String value, final String regex) {
+	    Pattern pattern = Pattern.compile(regex);
+	    Matcher matcher = pattern.matcher(value);
+	    return !matcher.find();   
+        }
+
+        /**
+         * The negative of {@code notRegex} is {@link #regex}.
+         */
+        @Override
+        public Text negate() {
+            return regex;
+        }
+    },
+    /**
      * Evaluates if the first string starts with the second.
      *
      * @since 3.4.0
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java
index 2c28853..5b72521 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java
@@ -106,4 +106,22 @@ public class TextP extends P<String> {
     public static TextP notContaining(final String value) {
         return new TextP(Text.notContaining, value);
     }
+    
+    /**           
+     * Determines if String has a match with the given REGEX pattern. 
+     *
+     * @since 3.6.0
+     */
+    public static TextP regex(final String value) {
+        return new TextP(Text.regex, value);
+    }
+
+    /**           
+     * Determines if String has no match with the given REGEX pattern. 
+     *
+     * @since 3.6.0
+     */
+    public static TextP notRegex(final String value) {
+        return new TextP(Text.notRegex, value);
+    }
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java
index 7254388..4bb7f30 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java
@@ -90,6 +90,9 @@ public class TraversalPredicateVisitorTest {
                 {"TextP.endingWith('hakuna')", TextP.endingWith("hakuna")},
                 {"TextP.notEndingWith('hakuna')", TextP.notEndingWith("hakuna")},
                 {"TextP.notStartingWith('hakuna')", TextP.notStartingWith("hakuna")},
+                {"TextP.regex('^h')", TextP.regex("^h")},
+                {"TextP.notRegex('^h')", TextP.notRegex("^h")},
+                {"TextP.regex('^h').negate()", TextP.regex("^h").negate()},
         });
     }
 
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
index e58e8be..d29e666 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
@@ -158,6 +158,20 @@ public class PTest {
                     {TextP.containing("o").and(P.gte("j")).and(TextP.endingWith("ko")), "josh", false},
                     {TextP.containing("o").and(P.gte("j").and(TextP.endingWith("ko"))), "marko", true},
                     {TextP.containing("o").and(P.gte("j").and(TextP.endingWith("ko"))), "josh", false},
+                    {TextP.regex("^D"), "Dallas Fort Worth", true},
+                    {TextP.regex("^d"), "Dallas Fort Worth", false},
+                    {TextP.regex("^Da"), "Dallas Forth Worth", true},
+                    {TextP.regex("^da"), "Dallas Forth Worth", false},
+                    {TextP.regex("^x"), "Dallas Fort Worth", false},
+                    {TextP.regex("Dal[l|x]as"), "Dallas Fort Worth", true},
+                    {TextP.regex("Dal[f|x]as"), "Dallas Fort Worth", false},
+                    {TextP.regex("[a-zA-Z]+ Fort"), "Dallas Fort Worth", true},
+                    {TextP.regex("[1-9]{3}"), "123-ABC-456", true},
+                    {TextP.regex("[1-9]{3}-[A-Z]{3}-[1-9]{3}"), "123-ABC-456", true},
+                    {TextP.regex("[1-9]{3}-[a-z]{3}-[1-9]{3}"), "123-ABC-456", false},
+                    {TextP.regex("(?i)[1-9]{3}-[a-z]{3}-[1-9]{3}"), "123-ABC-456", true},
+                    {TextP.regex("(?i)abc"), "123-ABC-456", true},
+                    {TextP.regex("(?i)[a-b]{3}-[1-9]{3}-[a-z]{3}"), "123-ABC-456", false},
             }));
         }
 
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/TextP.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/TextP.cs
index a77f206..a106fb0 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/TextP.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/TextP.cs
@@ -75,6 +75,15 @@ namespace Gremlin.Net.Process.Traversal
             return new TextP("startingWith", value);
         }
 
+        public static TextP Regex(string value)
+        {
+            return new TextP("regex", value);
+        }
+
+        public static TextP NotRegex(string value)
+        {
+            return new TextP("notRegex", value);
+        }
 
         private static T[] ToGenericArray<T>(ICollection<T> collection)
         {
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index ea6bf4d..504f786 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -215,6 +215,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_V_hasXname_not_containingXarkXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name",TextP.NotContaining("ark"))}}, 
                {"g_V_hasXname_not_startingWithXmarXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name",TextP.NotStartingWith("mar"))}}, 
                {"g_V_hasXname_not_endingWithXasXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name",TextP.NotEndingWith("as"))}}, 
+               {"g_V_hasXname_regexXrMarXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name",TextP.Regex("^mar"))}}, 
+               {"g_V_hasXname_notRegexXrMarXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name",TextP.NotRegex("^mar"))}}, 
                {"g_V_hasXp_neqXvXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("p",P.Neq("v"))}}, 
                {"g_V_hasXage_gtX18X_andXltX30XXorXgtx35XXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("age",P.Gt(18).And(P.Lt(30)).Or(P.Gt(35)))}}, 
                {"g_V_hasXage_gtX18X_andXltX30XXorXltx35XXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("age",P.Gt(18).And(P.Lt(30)).And(P.Lt(35)))}}, 
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
index f72c3f9..a7fb9ac 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
@@ -427,6 +427,15 @@ class TextP {
     return createTextP('startingWith', args);
   }
 
+  /** @param {...Object} args */
+  static regex(...args) {
+    return createTextP('regex', args);
+  }
+
+  /** @param {...Object} args */
+  static notRegex(...args) {
+    return createTextP('notRegex', args);
+  }
 }
 
 function createTextP(operator, args) {
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index 52bb2c7..3c73a4e 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
@@ -203,6 +203,8 @@ const gremlins = {
     g_V_hasXname_not_containingXarkXX: [function({g}) { return g.V().has("name",TextP.notContaining("ark")) }], 
     g_V_hasXname_not_startingWithXmarXX: [function({g}) { return g.V().has("name",TextP.notStartingWith("mar")) }], 
     g_V_hasXname_not_endingWithXasXX: [function({g}) { return g.V().has("name",TextP.notEndingWith("as")) }], 
+    g_V_hasXname_regexXrMarXX: [function({g}) { return g.V().has("name",TextP.regex("^mar")) }], 
+    g_V_hasXname_notRegexXrMarXX: [function({g}) { return g.V().has("name",TextP.notRegex("^mar")) }], 
     g_V_hasXp_neqXvXX: [function({g}) { return g.V().has("p",P.neq("v")) }], 
     g_V_hasXage_gtX18X_andXltX30XXorXgtx35XXX: [function({g}) { return g.V().has("age",P.gt(18).and(P.lt(30)).or(P.gt(35))) }], 
     g_V_hasXage_gtX18X_andXltX30XXorXltx35XXX: [function({g}) { return g.V().has("age",P.gt(18).and(P.lt(30)).and(P.lt(35))) }], 
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index e285634..941cdd1 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -905,6 +905,8 @@ traversalPredicate
     | traversalPredicate_notEndingWith
     | traversalPredicate_containing
     | traversalPredicate_notContaining
+    | traversalPredicate_regex
+    | traversalPredicate_notRegex
     | traversalPredicate DOT 'and' LPAREN traversalPredicate RPAREN
     | traversalPredicate DOT 'or' LPAREN traversalPredicate RPAREN
     | traversalPredicate DOT 'negate' LPAREN RPAREN
@@ -1019,6 +1021,14 @@ traversalPredicate_notEndingWith
     : ('TextP.notEndingWith' | 'notEndingWith') LPAREN stringLiteral RPAREN
     ;
 
+traversalPredicate_regex
+    : ('TextP.regex' | 'regex') LPAREN stringLiteral RPAREN
+    ;
+
+traversalPredicate_notRegex
+    : ('TextP.notRegex' | 'notRegex') LPAREN stringLiteral RPAREN
+    ;
+
 traversalTerminalMethod_explain
     : 'explain' LPAREN RPAREN
     ;
diff --git a/gremlin-python/src/main/python/gremlin_python/process/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
index f7bd31d..67ea377 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
@@ -384,6 +384,14 @@ class TextP(P):
     def startingWith(*args):
         return TextP("startingWith", *args)
 
+    @staticmethod
+    def regex(*args):
+        return TextP("regex", *args)
+
+    @staticmethod
+    def notRegex(*args):
+        return TextP("notRegex", *args)
+
     def __eq__(self, other):
         return isinstance(other, self.__class__) and self.operator == other.operator and self.value == other.value and self.other == other.other
 
@@ -414,6 +422,11 @@ def notStartingWith(*args):
 def startingWith(*args):
     return TextP.startingWith(*args)
 
+def regex(*args):
+    return TextP.regex(*args)
+
+def notRegex(*args):
+    return TextP.notRegex(*args)
 
 statics.add_static('containing', containing)
 
@@ -427,6 +440,10 @@ statics.add_static('notStartingWith', notStartingWith)
 
 statics.add_static('startingWith', startingWith)
 
+statics.add_static('regex', regex)
+
+statics.add_static('notRegex', notRegex)
+
 
 
 
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 850ee7e..0dcd3e4 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -188,6 +188,8 @@ world.gremlins = {
     'g_V_hasXname_not_containingXarkXX': [(lambda g:g.V().has('name',TextP.notContaining('ark')))], 
     'g_V_hasXname_not_startingWithXmarXX': [(lambda g:g.V().has('name',TextP.notStartingWith('mar')))], 
     'g_V_hasXname_not_endingWithXasXX': [(lambda g:g.V().has('name',TextP.notEndingWith('as')))], 
+    'g_V_hasXname_regexXrMarXX': [(lambda g:g.V().has('name',TextP.regex('^mar')))], 
+    'g_V_hasXname_notRegexXrMarXX': [(lambda g:g.V().has('name',TextP.notRegex('^mar')))], 
     'g_V_hasXp_neqXvXX': [(lambda g:g.V().has('p',P.neq('v')))], 
     'g_V_hasXage_gtX18X_andXltX30XXorXgtx35XXX': [(lambda g:g.V().has('age',P.gt(18).and_(P.lt(30)).or_(P.gt(35))))], 
     'g_V_hasXage_gtX18X_andXltX30XXorXltx35XXX': [(lambda g:g.V().has('age',P.gt(18).and_(P.lt(30)).and_(P.lt(35))))], 
diff --git a/gremlin-test/features/filter/Has.feature b/gremlin-test/features/filter/Has.feature
index bbb9ed4..8739556 100644
--- a/gremlin-test/features/filter/Has.feature
+++ b/gremlin-test/features/filter/Has.feature
@@ -514,6 +514,33 @@ Feature: Step - has()
       | v[ripple] |
       | v[peter] |
 
+ Scenario: g_V_hasXname_regexXrMarXX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().has("name", TextP.regex("^mar"))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[marko] |
+
+ Scenario: g_V_hasXname_notRegexXrMarXX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().has("name", TextP.notRegex("^mar"))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[josh] |
+      | v[vadas] |
+      | v[lop] |
+      | v[ripple] |
+      | v[peter] |  
+
+
   Scenario: g_V_hasXp_neqXvXX
     Given the modern graph
     And the traversal of
@@ -675,4 +702,5 @@ Feature: Step - has()
     When iterated to list
     Then the result should be unordered
       | result |
-      | josh |
\ No newline at end of file
+      | josh |
+