This is an automated email from the ASF dual-hosted git repository. Cole-Greer pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit f9fd3e00786ce52b84a3b73de674cebf2e54f3f8 Merge: ee969851f5 fe2abf2628 Author: Cole Greer <[email protected]> AuthorDate: Wed Jul 22 17:09:56 2026 -0700 Merge branch '3.8-dev' CHANGELOG.asciidoc | 5 ++++ docs/src/upgrade/release-3.7.x.asciidoc | 9 +++++++ .../optimization/ProductiveByStrategy.java | 3 +++ .../Strategy/Optimization/ProductiveByStrategy.cs | 2 ++ gremlin-go/driver/strategies.go | 7 ++++- gremlin-go/driver/strategies_test.go | 31 ++++++++++++++++++++++ .../lib/process/traversal-strategy.ts | 4 +++ .../python/gremlin_python/process/strategies.py | 7 +++++ .../python/gremlin_python/process/traversal.py | 2 +- 9 files changed, 68 insertions(+), 2 deletions(-) diff --cc gremlin-go/driver/strategies.go index b5501aac46,88c1785f12..39ce4f17dc --- a/gremlin-go/driver/strategies.go +++ b/gremlin-go/driver/strategies.go @@@ -88,10 -98,10 +88,10 @@@ func PartitionStrategy(config Partition if config.WritePartition != "" { configMap["writePartition"] = config.WritePartition } - if len(config.ReadPartitions.ToSlice()) != 0 { + if config.ReadPartitions != nil && len(config.ReadPartitions.ToSlice()) != 0 { configMap["readPartitions"] = config.ReadPartitions } - return &traversalStrategy{name: decorationNamespace + "PartitionStrategy", configuration: configMap} + return &traversalStrategy{name: "PartitionStrategy", configuration: configMap} } // PartitionStrategyConfig provides configuration options for PartitionStrategy. diff --cc gremlin-go/driver/strategies_test.go index 3ccb532fc1,d09e59add2..cde18a8517 --- a/gremlin-go/driver/strategies_test.go +++ b/gremlin-go/driver/strategies_test.go @@@ -420,28 -427,35 +420,59 @@@ func TestStrategy(t *testing.T) assert.Equal(t, int32(6), val) }) + t.Run("Test GremlinLang generation for simple custom strategies", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + + customStrategy := NewTraversalStrategy("CustomSingletonStrategy", nil) + gl := g.WithStrategies(customStrategy).gremlinLang + assert.True(t, strings.Contains(gl.GetGremlin(), "withStrategies(CustomSingletonStrategy)")) + }) + + t.Run("Test GremlinLang generation for config custom strategies", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + + customStrategy := NewTraversalStrategy("CustomConfigurableStrategy", + map[string]interface{}{"stringKey": "string value", "intKey": 5, "booleanKey": true}) + gl := g.WithStrategies(customStrategy).gremlinLang + // Note that config map doesn't guarantee order, so assert individually + assert.True(t, strings.Contains(gl.GetGremlin(), + "withStrategies(new CustomConfigurableStrategy(")) + assert.True(t, strings.Contains(gl.GetGremlin(), + "stringKey:\"string value\"")) + assert.True(t, strings.Contains(gl.GetGremlin(), + "intKey:5")) + assert.True(t, strings.Contains(gl.GetGremlin(), + "booleanKey:true")) + }) } + + func Test_StrategyConfig_partitionNilReadPartitions(t *testing.T) { + t.Run("Test PartitionStrategy with nil ReadPartitions does not panic", func(t *testing.T) { + config := PartitionStrategyConfig{ + PartitionKey: "partition", + WritePartition: "write", + // ReadPartitions intentionally left unset (nil interface). + } + var strategy TraversalStrategy + assert.NotPanics(t, func() { + strategy = PartitionStrategy(config) + }) + assert.NotNil(t, strategy) + configMap := strategy.(*traversalStrategy).configuration + _, ok := configMap["readPartitions"] + assert.False(t, ok) + }) + + t.Run("Test PartitionStrategy with non-empty ReadPartitions sets readPartitions", func(t *testing.T) { + config := PartitionStrategyConfig{ + PartitionKey: "partition", + WritePartition: "write", + ReadPartitions: NewSimpleSet("read"), + } + strategy := PartitionStrategy(config) + assert.NotNil(t, strategy) + configMap := strategy.(*traversalStrategy).configuration + _, ok := configMap["readPartitions"] + assert.True(t, ok) + }) + } diff --cc gremlin-js/gremlin-javascript/lib/process/traversal-strategy.ts index ab73ce98e8,0000000000..0fdf0d8872 mode 100644,000000..100644 --- a/gremlin-js/gremlin-javascript/lib/process/traversal-strategy.ts +++ b/gremlin-js/gremlin-javascript/lib/process/traversal-strategy.ts @@@ -1,368 -1,0 +1,372 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/** + * @author Jorge Bay Gondra + */ + +import type { RemoteConnection } from '../driver/remote-connection.js'; +import { Traversal } from './traversal.js'; + +export class TraversalStrategies { + readonly strategies: TraversalStrategy[]; + + /** + * Creates a new instance of TraversalStrategies. + * @param {TraversalStrategies} [parent] The parent strategies from where to clone the values from. + */ + constructor(parent?: TraversalStrategies) { + if (parent) { + // Clone the strategies + this.strategies = [...parent.strategies]; + } else { + this.strategies = []; + } + } + + /** @param {TraversalStrategy} strategy */ + addStrategy(strategy: TraversalStrategy) { + this.strategies.push(strategy); + } + + /** @param {TraversalStrategy} strategy */ + removeStrategy(strategy: TraversalStrategy) { + const idx = this.strategies.findIndex((s) => s.strategyName === strategy.strategyName); + if (idx !== -1) { + return this.strategies.splice(idx, 1)[0]; + } + + return undefined; + } + + /** + * @param {Traversal} traversal + * @returns {Promise} + */ + applyStrategies(traversal: Traversal) { + // Apply all strategies serially + return this.strategies.reduce( + (promise, strategy) => promise.then(() => strategy.apply(traversal)), + Promise.resolve(), + ); + } +} + +export type TraversalStrategyConfiguration = any; + +export abstract class TraversalStrategy { + connection?: RemoteConnection; + public strategyName: string; + + /** + * @param {TraversalStrategyConfiguration} configuration for the strategy + */ + constructor( + public configuration: TraversalStrategyConfiguration = {}, + ) { + this.strategyName = this.constructor.name; + } + + /** + * @abstract + * @param {Traversal} traversal + * @returns {Promise} + */ + async apply(traversal: Traversal): Promise<void> {} +} + +export class ConnectiveStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class ElementIdStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class HaltedTraverserStrategy extends TraversalStrategy { + /** + * @param {String} haltedTraverserFactory full qualified class name in Java of a `HaltedTraverserFactory` implementation + */ + constructor({haltedTraverserFactory = ""}) { + super({haltedTraverserFactory: haltedTraverserFactory}); + } +} + +export class MessagePassingReductionStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class OptionsStrategy extends TraversalStrategy { + constructor(options: TraversalStrategyConfiguration) { + super(options); + } +} + +export class PartitionStrategy extends TraversalStrategy { + /** + * @param options + * @param options.partitionKey - name of the property key to partition by + * @param options.writePartition - the value of the currently write partition + * @param options.readPartitions - list of strings representing the partitions to include for reads + * @param options.includeMetaProperties - determines if meta-properties should be included in partitioning defaulting to false + */ + constructor({partitionKey, writePartition, readPartitions, includeMetaProperties}: {partitionKey?: string, writePartition?: string, readPartitions?: string[], includeMetaProperties?: boolean} = {}) { + const config: Record<string, any> = {}; + if (partitionKey !== undefined) config.partitionKey = partitionKey; + if (writePartition !== undefined) config.writePartition = writePartition; + if (readPartitions !== undefined) config.readPartitions = readPartitions; + if (includeMetaProperties !== undefined) config.includeMetaProperties = includeMetaProperties; + super(config); + } +} + +export class ProfileStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class SubgraphStrategy extends TraversalStrategy { + /** + * @param options + * @param options.vertices - traversal to filter vertices + * @param options.edges - traversal to filter edges + * @param options.vertexProperties - traversal to filter vertex properties + * @param options.checkAdjacentVertices - enables the strategy to apply the `vertices` filter to the adjacent vertices of an edge. + */ + constructor({vertices, edges, vertexProperties, checkAdjacentVertices}: {vertices?: any, edges?: any, vertexProperties?: any, checkAdjacentVertices?: boolean} = {}) { + const config: Record<string, any> = {}; + if (vertices !== undefined) config.vertices = vertices instanceof Traversal ? vertices.gremlinLang : vertices; + if (edges !== undefined) config.edges = edges instanceof Traversal ? edges.gremlinLang : edges; + if (vertexProperties !== undefined) config.vertexProperties = vertexProperties instanceof Traversal ? vertexProperties.gremlinLang : vertexProperties; + if (checkAdjacentVertices !== undefined) config.checkAdjacentVertices = checkAdjacentVertices; + super(config); + } +} + ++/** ++ * @deprecated As of release 3.7.7, not replaced. This strategy was added as a temporary way to mimic pre-3.5.0 ++ * null processing behavior. ++ */ +export class ProductiveByStrategy extends TraversalStrategy { + /** + * @param options + * @param options.productiveKeys - set of keys that will always be productive + */ + constructor({productiveKeys = []} = {}) { + super({productiveKeys}); + } +} + +export class ReferenceElementStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class VertexProgramStrategy extends TraversalStrategy { + constructor(options: TraversalStrategyConfiguration) { + super(options); + } +} + +export class MatchAlgorithmStrategy extends TraversalStrategy { + /** + * @param matchAlgorithm + */ + constructor({matchAlgorithm = ""}) { + super({matchAlgorithm: matchAlgorithm}); + } +} + +export class ComputerFinalizationStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class AdjacentToIncidentStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class FilterRankingStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class ByModulatorOptimizationStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class IdentityRemovalStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class IncidentToAdjacentStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class InlineFilterStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class LazyBarrierStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class MatchPredicateStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class OrderLimitStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class PathProcessorStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class PathRetractionStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class CountStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class RepeatUnrollStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class GraphFilterStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class EarlyLimitStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class ComputerVerificationStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class LambdaRestrictionStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class ReadOnlyStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class EdgeLabelVerificationStrategy extends TraversalStrategy { + /** + * @param options + * @param options.logWarnings - determines if warnings should be written to the logger when verification fails + * @param options.throwException - determines if exceptions should be thrown when verifications fails + */ + constructor({logWarnings = false, throwException = false} = {}) { + super({ + logWarnings: logWarnings, + throwException: throwException, + }); + } +} + +export class ReservedKeysVerificationStrategy extends TraversalStrategy { + /** + * @param options + * @param options.logWarnings - determines if warnings should be written to the logger when verification fails + * @param options.throwException - determines if exceptions should be thrown when verifications fails + * @param options.keys - the list of reserved keys to verify + */ + constructor({ logWarnings = false, throwException = false, keys = ['id', 'label'] } = {}) { + super({ + logWarnings: logWarnings, + throwException: throwException, + keys: keys, + }); + } +} + +export class VertexProgramRestrictionStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export class StandardVerificationStrategy extends TraversalStrategy { + constructor() { + super(); + } +} + +export type SeedStrategyOptions = { seed: number }; + +export class SeedStrategy extends TraversalStrategy { + /** + * @param {SeedStrategyOptions} [options] + * @param {number} [options.seed] the seed to provide to the random number generator for the traversal + */ + constructor(options: SeedStrategyOptions) { + super({ + seed: options.seed, + }); + } +}
