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 22:20:12 UTC

[tinkerpop] branch TINKERPOP-2652 updated (70e8245 -> 7a4e2bb)

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 70e8245  Add Text.Regex text predicate
     new 7a4e2bb  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   (70e8245)
            \
             N -- N -- N   refs/heads/TINKERPOP-2652 (7a4e2bb)

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:
 docs/src/upgrade/release-3.6.x.asciidoc | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

[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 7a4e2bb7428c5ad69fc0f92258bade1c5a430eea
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 ++++-
 docs/src/upgrade/release-3.6.x.asciidoc            | 24 +++++++++++-
 .../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 ++++++++++++++-
 17 files changed, 210 insertions(+), 4 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/docs/src/upgrade/release-3.6.x.asciidoc b/docs/src/upgrade/release-3.6.x.asciidoc
index 5789d22..6ce0275 100644
--- a/docs/src/upgrade/release-3.6.x.asciidoc
+++ b/docs/src/upgrade/release-3.6.x.asciidoc
@@ -391,6 +391,28 @@ valid key, the appropriate change would be to propagate `null` explicitly as wit
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2635[TINKERPOP-2635]
 
+==== TextP enhancements
+
+Support for regular expressions was added to the  `TextP` text predicates.
+
+Given the following vertices:
+
+[source,text]
+----
+gremlin> g.addV('City').property('name','Dallas')
+==>v[2]
+gremlin> g.addV('City').property('name','Darwin')
+==>v[4]
+----
+
+Cities with names that start with `Dar` or `Dal` can be found using a regular expression such as:
+
+[source,text]
+----
+gremlin> g.V().has('City','name',TextP.regex('^Da[r|l]')).values('name')
+==>Dallas
+==>Darwin
+----
 
 ==== gremlin-annotations
 
@@ -588,4 +610,4 @@ with TinkerGraph operations where mixed `T.id` types is a feature. Graph provide
 requirement if they wish, but it is no longer enforced by TinkerPop and the `Graph.idArgsMustBeEitherIdOrElement` has
 been removed so providers will need to construct their own exception.
 
-See: link:https://issues.apache.org/jira/browse/TINKERPOP-2507[TINKERPOP-2507]
\ No newline at end of file
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2507[TINKERPOP-2507]
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 |
+