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,
 +    });
 +  }
 +}

Reply via email to