Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Deprecated `gremlin_python.process.__.has_key_` in favor of `gremlin_python.process.__.has_key`.
* Added `gremlin.spark.outputRepartition` configuration to customize the partitioning of HDFS files from `OutputRDD`.
* Allowed `mergeV()` and `mergeE()` to supply `null` in `Map` values.
* Change signature of `hasId(P<Object>)` and `hasValue(P<Object>)` to `hasId(P<?>)` and `hasValue(P<?>)`.
* Improved error message for when `emit()` is used without `repeat()`.
* Changed `PythonTranslator` to generate snake case step naming instead of camel case.

[[release-3-7-3]]
=== TinkerPop 3.7.3 (October 23, 2024)
Expand Down
82 changes: 79 additions & 3 deletions docs/src/reference/the-traversal.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -938,9 +938,10 @@ on a step-by-step level and thus, as discussed in their respective section of th
* <<aggregate-step, `aggregate()`>>: aggregate all objects into a set but only store their `by()`-modulated values.
* <<cyclicpath-step, `cyclicPath()`>>: filter if the traverser's path is cyclic given `by()`-modulation.
* <<dedup-step, `dedup()`>>: dedup on the results of a `by()`-modulation.
* <<format-step, `format()`>>: transform a traverser provided to the step by way of the `by()` modulator before it is processed by it.
* <<group-step, `group()`>>: create group keys and values according to `by()`-modulation.
* <<groupcount-step,`groupCount()`>>: count those groups where the group keys are the result of `by()`-modulation.
* <<math-step, `math()`>>: transform a traverser provided to the step by way of the `by()` modulator before it processed by it.
* <<math-step, `math()`>>: transform a traverser provided to the step by way of the `by()` modulator before it is processed by it.
* <<order-step, `order()`>>: order the objects by the results of a `by()`-modulation.
* <<path-step, `path()`>>: get the path of the traverser where each path element is `by()`-modulated.
* <<project-step, `project()`>>: project a map of results given various `by()`-modulations off the current object.
Expand All @@ -966,7 +967,8 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#by(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`by(Traversal)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#by(org.apache.tinkerpop.gremlin.process.traversal.Traversal,java.util.Comparator)++[`by(Traversal,Comparator)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/T.html++[`T`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Order.html++[`Order`]
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Order.html++[`Order`],
<<a-note-on-maps>>

[[call-step]]
=== Call Step
Expand Down Expand Up @@ -4282,7 +4284,8 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#select(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`select(Traversal)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#select(org.apache.tinkerpop.gremlin.process.traversal.Pop,org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`select(Pop,Traversal)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/Column.html++[`Column`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Pop.html++[`Pop`]
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Pop.html++[`Pop`],
<<a-note-on-maps>>

[[shortestpath-step]]
=== ShortestPath step
Expand Down Expand Up @@ -5242,6 +5245,79 @@ g.V().as('a').both().both().as('b').count()
g.V().as('a').both().both().as('b').where('a',neq('b')).count()
----

[[a-note-on-maps]]
== A Note on Maps

Many steps in Gremlin return `Map`-based results. Commonly used steps like <<project-step, `project()`>>,
<<group-step, 'group()'>>, and <<select-step, `select()`>> are just some examples of steps that fall into this category.
When working with `Map` results there are a couple of important things to know.

First, it is important to recognize that there is a bit of a difference in behavior that occurs when using
<<unfold-step,unfold()>> on a `Map` in embedded contexts versus remote contexts. In embedded contexts, an unfolded `Map`
becomes its composite `Map.Entry` objects as is typical in Java. The following example demonstrates the basic name/value
pairs that returned:

[groovy,modern]
----
g.V().valueMap('name','age').unfold()
----

In remote contexts, an unfolded `Map` becomes `Map.Entry` on the server as in the embedded case, but is returned to the
application as a `Map` with one entry. The slight difference in notation in Gremlin Console is shown in the following
remote example:

[source,text]
----
gremlin> g.V().valueMap('name','age').unfold()
==>[name:[marko]]
==>[age:[29]]
==>[name:[vadas]]
==>[age:[27]]
==>[name:[lop]]
==>[name:[josh]]
==>[age:[32]]
==>[name:[ripple]]
==>[name:[peter]]
==>[age:[35]]
----

The primary reason for this difference lies in the fact that Gremlin Language Variants, like Python and Go, do not have
a native `Map.Entry` concept that can be used. The most universal data structure across programming languages is the
`Map` itself. It is important to note that this transformation from `Map.Entry` to `Map` only applies to results
received on the client-side. In other words, if a step was to follow `unfold()` in the prior example, it would be
dealing with `Map.Entry` and not a `Map`, so Gremlin semantics should remain consistent on the server side.

The second issues to consider with steps that return a `Map` is that access keys on a `Map` is not always as consistent
as expected. The issue is best demonstrated in some examples:

[source,text]
----
// note that elements can be grouped by(id), but that same pattern can't be applied to get
// a T.id in a Map
gremlin> g.V().hasLabel('person').both().group().by(id)
==>[1:[v[1],v[1]],2:[v[2]],3:[v[3],v[3],v[3]],4:[v[4]],5:[v[5]]]
gremlin> g.V().hasLabel('person').both().elementMap().group().by(id)
TokenTraversal support of java.util.LinkedHashMap does not allow selection by id
Type ':help' or ':h' for help.
Display stack trace? [yN]

// note that select() can't be used if the key is a non-string
gremlin> g.V().hasLabel('person').both().group().by('age').select(32)
No signature of method: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal.select() is applicable for argument types: (Integer) values: [32]
Possible solutions: reset(), collect(), sleep(long), collect(groovy.lang.Closure), inject(groovy.lang.Closure), split(groovy.lang.Closure)
Type ':help' or ':h' for help.
Display stack trace? [yN]
----

While this problem might be solved in future versions, the workaround for both cases is to use
<<constant-step,constant()>> as shown in the following example:

[groovy,modern]
----
g.V().hasLabel('person').both().group().by(constant(id))
g.V().hasLabel('person').both().group().by('age').select(constant(32))
----

[[a-note-on-barrier-steps]]
== A Note on Barrier Steps

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ public Object visitFloatLiteral(final GremlinParser.FloatLiteralContext ctx) {
// parse D/d suffix as Double
return new Double(floatLiteral);
} else {
return new BigDecimal(floatLiteral);
return new Double(floatLiteral);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2381,7 +2381,7 @@ public default GraphTraversal<S, E> hasLabel(final P<String> predicate) {
*/
public default GraphTraversal<S, E> hasId(final Object id, final Object... otherIds) {
if (id instanceof P) {
return this.hasId((P) id);
return this.hasId((P<?>) id);
}
else {
this.asAdmin().getBytecode().addStep(Symbols.hasId, id, otherIds);
Expand Down Expand Up @@ -2424,7 +2424,7 @@ public default GraphTraversal<S, E> hasId(final Object id, final Object... other
* @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#has-step" target="_blank">Reference Documentation - Has Step</a>
* @since 3.2.4
*/
public default GraphTraversal<S, E> hasId(final P<Object> predicate) {
public default GraphTraversal<S, E> hasId(final P<?> predicate) {
if (null == predicate)
return hasId((Object) null);

Expand Down Expand Up @@ -2485,7 +2485,7 @@ public default GraphTraversal<S, E> hasKey(final P<String> predicate) {
*/
public default GraphTraversal<S, E> hasValue(final Object value, final Object... otherValues) {
if (value instanceof P)
return this.hasValue((P) value);
return this.hasValue((P<?>) value);
else {
this.asAdmin().getBytecode().addStep(Symbols.hasValue, value, otherValues);
final List<Object> values = new ArrayList<>();
Expand Down Expand Up @@ -2519,7 +2519,7 @@ public default GraphTraversal<S, E> hasValue(final Object value, final Object...
* @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#has-step" target="_blank">Reference Documentation - Has Step</a>
* @since 3.2.4
*/
public default GraphTraversal<S, E> hasValue(final P<Object> predicate) {
public default GraphTraversal<S, E> hasValue(final P<?> predicate) {
// if calling hasValue(null), the likely use the caller is going for is not a "no predicate" but a eq(null)
if (null == predicate) {
return hasValue((String) null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -993,7 +993,7 @@ public static <A> GraphTraversal<A, A> hasId(final Object id, Object... otherIds
/**
* @see GraphTraversal#hasId(P)
*/
public static <A> GraphTraversal<A, A> hasId(final P<Object> predicate) {
public static <A> GraphTraversal<A, A> hasId(final P<?> predicate) {
return __.<A>start().hasId(predicate);
}

Expand Down Expand Up @@ -1021,7 +1021,7 @@ public static <A> GraphTraversal<A, A> hasValue(final Object value, Object... va
/**
* @see GraphTraversal#hasValue(P)
*/
public static <A> GraphTraversal<A, A> hasValue(final P<Object> predicate) {
public static <A> GraphTraversal<A, A> hasValue(final P<?> predicate) {
return __.<A>start().hasValue(predicate);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package org.apache.tinkerpop.gremlin.process.traversal.translator;

import org.apache.commons.configuration2.ConfigurationConverter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.P;
Expand Down Expand Up @@ -361,7 +362,7 @@
// for the first/last P there is no parent to close
if (i > 0 && i < list.size() - 1) script.append(")");

// add teh connector for all but last P
// add the connector for all but last P
if (i < list.size() - 1) {
script.append(".").append(connector).append("(");
}
Expand Down Expand Up @@ -435,18 +436,13 @@
static final class SymbolHelper {

private final static Map<String, String> TO_PYTHON_MAP = new HashMap<>();
private final static Map<String, String> FROM_PYTHON_MAP = new HashMap<>();

static {
TO_PYTHON_MAP.put("global", "global_");
TO_PYTHON_MAP.put("all", "all_");
TO_PYTHON_MAP.put("and", "and_");
TO_PYTHON_MAP.put("any", "any_");
TO_PYTHON_MAP.put("as", "as_");
TO_PYTHON_MAP.put("asString", "as_string");
TO_PYTHON_MAP.put("asDate", "as_date");
TO_PYTHON_MAP.put("dateAdd", "date_add");
TO_PYTHON_MAP.put("dateDiff", "date_diff");
TO_PYTHON_MAP.put("filter", "filter_");
TO_PYTHON_MAP.put("format", "format_");
TO_PYTHON_MAP.put("from", "from_");
Expand All @@ -455,38 +451,32 @@
TO_PYTHON_MAP.put("is", "is_");
TO_PYTHON_MAP.put("list", "list_");
TO_PYTHON_MAP.put("max", "max_");
TO_PYTHON_MAP.put("mergeE", "merge_e");
TO_PYTHON_MAP.put("mergeV", "merge_v");
TO_PYTHON_MAP.put("inV", "in_v");
TO_PYTHON_MAP.put("outV", "out_v");
TO_PYTHON_MAP.put("onCreate", "on_create");
TO_PYTHON_MAP.put("onMatch", "on_match");
TO_PYTHON_MAP.put("min", "min_");
TO_PYTHON_MAP.put("or", "or_");
TO_PYTHON_MAP.put("not", "not_");
TO_PYTHON_MAP.put("range", "range_");
TO_PYTHON_MAP.put("set", "set_");
TO_PYTHON_MAP.put("sum", "sum_");
TO_PYTHON_MAP.put("toLower", "to_lower");
TO_PYTHON_MAP.put("toUpper", "to_upper");
TO_PYTHON_MAP.put("with", "with_");
//
TO_PYTHON_MAP.forEach((k, v) -> FROM_PYTHON_MAP.put(v, k));
}

private SymbolHelper() {
// static methods only, do not instantiate
}

public static String toPython(final String symbol) {
// at some point we will want a camel to snake case converter here. for now the only step that needs
// this conversion is mergeE/V related as the rest still continue use in their deprecated forms.
return TO_PYTHON_MAP.getOrDefault(symbol, symbol);
return TO_PYTHON_MAP.getOrDefault(symbol, convertCamelCaseToSnakeCase(symbol));
}

public static String toJava(final String symbol) {
return FROM_PYTHON_MAP.getOrDefault(symbol, symbol);
}
public static String convertCamelCaseToSnakeCase(final String camelCase) {
if (StringUtils.isBlank(camelCase))
return camelCase;

Check warning on line 473 in gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java

View check run for this annotation

Codecov / codecov/patch

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java#L473

Added line #L473 was not covered by tests

// skip if this is a class/enum indicated by the first letter being upper case
if (Character.isUpperCase(camelCase.charAt(0)))
return camelCase;

return camelCase.replaceAll("([A-Z])", "_$1").toLowerCase();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -405,11 +405,12 @@ public static class ValidFloatLiteralTest {
@Parameterized.Parameters()
public static Iterable<Object[]> generateTestParameters() {
return Arrays.asList(new Object[][]{
{"1.1", "1.1", "java.math.BigDecimal"},
{"-0.1", "-0.1", "java.math.BigDecimal"},
{"1.0E+12", "1.0E12", "java.math.BigDecimal"},
{"-0.1E-12", "-0.1E-12", "java.math.BigDecimal"},
{"1E12", "1E12", "java.math.BigDecimal"},
// default
{"1.1", "1.1", "java.lang.Double"},
{"-0.1", "-0.1", "java.lang.Double"},
{"1.0E+12", "1.0E12", "java.lang.Double"},
{"-0.1E-12", "-0.1E-12", "java.lang.Double"},
{"1E12", "1E12", "java.lang.Double"},
// float
{"1.1f", "1.1", "java.lang.Float"},
{"-0.1F", "-0.1", "java.lang.Float"},
Expand Down Expand Up @@ -603,7 +604,7 @@ public void shouldParseGenericLiteralCollection() {
assertEquals(10, genericLiterals.get(1));

// verify 3rd element
assertEquals(new BigDecimal(14.5), genericLiterals.get(2));
assertEquals(new Double(14.5), genericLiterals.get(2));

// verify 4th element
assertEquals("hello", genericLiterals.get(3));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static Iterable<Object[]> generateTestParameters() {
{"gt(1.0D)", P.gt(1d)},
{"gte(1L)", P.gte(1L)},
{"inside(100, 200)", P.inside(100, 200)},
{"outside(1E11, 2e-11)", P.outside(new BigDecimal("1E11"), new BigDecimal("2e-11"))},
{"outside(1E11, 2e-11)", P.outside(new Double("1E11"), new Double("2e-11"))},
{"between(\"a\", \"e\")", P.between("a", "e")},
{"within([\"a\", \"e\"])", P.within(Arrays.asList("a", "e"))},
{"within()", P.within()},
Expand All @@ -73,7 +73,7 @@ public static Iterable<Object[]> generateTestParameters() {
{"P.gt(1.0D)", P.gt(1d)},
{"P.gte(1L)", P.gte(1L)},
{"P.inside(100, 200)", P.inside(100, 200)},
{"P.outside(1E11, 2e-11)", P.outside(new BigDecimal("1E11"), new BigDecimal("2e-11"))},
{"P.outside(1E11, 2e-11)", P.outside(new Double("1E11"), new Double("2e-11"))},
{"P.between(\"a\", \"e\")", P.between("a", "e")},
{"P.within([\"a\", \"e\"])", P.within(Arrays.asList("a", "e"))},
{"P.within()", P.within()},
Expand Down
Loading
Loading