This is an automated email from the ASF dual-hosted git repository. Cole-Greer pushed a commit to branch GraphSchema in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 3f3b1eb06b7f661a2abb629caf2fd508781faf11 Author: Cole Greer <[email protected]> AuthorDate: Tue Sep 15 15:21:24 2026 -0400 Add GraphSchema prototype implementation and machine transfer documentation Core Schema Interfaces (gremlin-core): - Add GraphSchema, VertexType, EdgeType, PropertyDefinition, EdgeRole interfaces - GraphSchema extends Graph, making schema itself a traversable graph - Type definitions are vertices in the schema graph (VertexType, EdgeType extend Vertex) - PropertyDefinition vertices connected via 'hasProperty' edges - EdgeType vertices connected to VertexType via 'from'/'to' edges with EdgeRole abstraction TinkerGraph Reference Implementation: - TinkerGraphSchema extends TinkerGraph, implements GraphSchema - TinkerVertexType, TinkerEdgeType, TinkerPropertyDefinition implementations - TinkerGraph.schema() returns schema instance - Feature flag: supportsSchema() = true - Tests: TinkerGraphSchemaTest, TinkerGraphSchemaTraversalTest Schema Traversal Steps (prototype): - GraphTraversalSource: addVType(), addEType() source spawn steps - GraphTraversal: propertyType(), constraint() mid-traversal steps - Step implementations: AddVertexTypeStartStep, AddEdgeTypeStartStep, AddPropertyTypeStep, AddConstraintStep (and placeholder variants) - Gremlin language symbols added Graph API Extensions: - Graph.schema() default method (throws UnsupportedOperationException) - Graph.Features.supportsSchema() feature flag - Graph.Exceptions.schemaNotSupported() exception Transfer Documentation: - GRAPHSCHEMA_HANDOFF.md: Comprehensive feature documentation with complete beads plan (18 tasks), design decisions, dependency graph, implementation roadmap - GRAPHSCHEMA_TRANSFER.md: Step-by-step checklist for transferring work to new machine Status: Prototype/exploration code - demonstrates core concepts but needs review and refactoring before production use. Schema enforcement, transactions, GLV support, and additional features deferred to future work per beads plan. --- GRAPHSCHEMA_HANDOFF.md | 682 +++++++++++++++++++++ GRAPHSCHEMA_TRANSFER.md | 193 ++++++ .../traversal/dsl/graph/GraphTraversal.java | 38 ++ .../traversal/dsl/graph/GraphTraversalSource.java | 32 + .../gremlin/process/traversal/dsl/graph/__.java | 16 + .../traversal/step/map/AddEdgeTypeStartStep.java | 117 ++++ .../step/map/AddEdgeTypeStartStepPlaceholder.java | 95 +++ .../traversal/step/map/AddPropertyTypeStep.java | 81 +++ .../step/map/AddPropertyTypeStepPlaceholder.java | 71 +++ .../traversal/step/map/AddVertexTypeStartStep.java | 81 +++ .../map/AddVertexTypeStartStepPlaceholder.java | 73 +++ .../step/sideEffect/AddConstraintStep.java | 73 +++ .../sideEffect/AddConstraintStepPlaceholder.java | 73 +++ .../tinkerpop/gremlin/structure/EdgeRole.java | 78 +++ .../tinkerpop/gremlin/structure/EdgeType.java | 122 ++++ .../apache/tinkerpop/gremlin/structure/Graph.java | 24 + .../tinkerpop/gremlin/structure/GraphSchema.java | 118 ++++ .../gremlin/structure/PropertyDefinition.java | 35 ++ .../tinkerpop/gremlin/structure/VertexType.java | 47 ++ .../tinkergraph/structure/TinkerEdgeType.java | 66 ++ .../gremlin/tinkergraph/structure/TinkerGraph.java | 23 + .../tinkergraph/structure/TinkerGraphSchema.java | 117 ++++ .../structure/TinkerPropertyDefinition.java | 32 + .../tinkergraph/structure/TinkerVertexType.java | 55 ++ .../structure/TinkerGraphSchemaTest.java | 371 +++++++++++ .../structure/TinkerGraphSchemaTraversalTest.java | 169 +++++ 26 files changed, 2882 insertions(+) diff --git a/GRAPHSCHEMA_HANDOFF.md b/GRAPHSCHEMA_HANDOFF.md new file mode 100644 index 0000000000..db93078a8c --- /dev/null +++ b/GRAPHSCHEMA_HANDOFF.md @@ -0,0 +1,682 @@ +# GraphSchema Feature Handoff Document + +**Branch:** `GraphSchema` +**Original Session:** 138 days ago, 442 messages +**Status:** Prototype/Planning phase - NOT production-ready code +**Beads Plan ID:** `tinkerpop-big` (Graph Schema Interfaces epic) +**Date Created:** 2026-09-15 + +--- + +## Purpose + +This document transfers the GraphSchema feature planning and prototype work to a new development machine. The code in this branch represents early proof-of-concept work exploring the design, not finished implementation. The beads plan below captures the full scope and detailed design decisions. + +--- + +## Executive Summary + +GraphSchema introduces a meta-graph system where schema definitions (vertex types, edge types, property definitions) are themselves vertices in a schema graph, traversable with Gremlin. Key design principle: `GraphSchema extends Graph`, making schema manipulation familiar to providers and enabling Gremlin-based schema introspection and manipulation. + +**Core Design Decisions:** +- Schema is a graph (`GraphSchema extends Graph`) +- All type definitions are vertices (`VertexType`, `EdgeType`, `PropertyDefinition` all extend `Vertex`) +- Even edge types are vertices (connected to endpoint `VertexType` vertices via `from`/`to` edges) +- Constraints are regular properties on type vertices +- New Gremlin steps (`addVType`, `addEType`, `propertyType`, `constraint`, etc.) in core, not DSL +- Cross-graph introspection: `type()` (data→schema) and `instances()` (schema→data) +- Schema enforcement, transactions, and dynamic updates deferred to later phases + +--- + +## Current State of This Branch + +### What Exists (Prototype Code) + +The branch contains early proof-of-concept implementations exploring the core structure: + +**Core Interfaces (gremlin-core):** +- `GraphSchema` interface (extends `Graph`) +- `VertexType` interface (extends `Vertex`) +- `EdgeType` interface (extends `Vertex`) with `EdgeRole` concept +- `PropertyDefinition` interface (extends `Vertex`) +- `Graph.schema()` default method +- Well-known edge label constants in `GraphSchema.Tokens` + +**TinkerGraph Implementation (tinkergraph-gremlin):** +- `TinkerGraphSchema` (extends `TinkerGraph` implements `GraphSchema`) +- `TinkerVertexType` (extends `TinkerVertex`) +- `TinkerEdgeType` (extends `TinkerVertex`) +- `TinkerPropertyDefinition` (extends `TinkerVertex`) +- Basic tests in `TinkerGraphSchemaTest.java` and `TinkerGraphSchemaTraversalTest.java` + +**Traversal Steps (partial):** +- Step placeholders exist: `AddVertexTypeStartStep`, `AddEdgeTypeStartStep`, `AddPropertyTypeStep` +- These are exploration code, not complete implementations + +### What Does NOT Exist Yet + +- Complete traversal step implementations (`addVType`, `addEType`, `propertyType`, `constraint`, `from`, `to`) +- Schema traversal restriction strategy (allowlist-based step validation) +- Cross-graph introspection steps (`type()`, `instances()`) +- Convenience methods on interfaces (see bead tinkerpop-big.12) +- `EdgeRole` full implementation +- GLV support (Python, JavaScript, .NET, Go) +- Schema serialization/IO +- Schema enforcement +- Transaction semantics +- Dynamic schema updates +- Documentation + +### Prototype Status Warning + +**This code is prototype/exploratory work, not production-ready.** The beads plan (below) represents the authoritative design and implementation roadmap. Code in this branch may: +- Not match final API decisions +- Have incomplete implementations +- Lack proper error handling, validation, thread safety +- Deviate from the detailed beads plan in places + +When resuming work, review the beads plan first, then assess which prototype code is salvageable vs. needs clean reimplementation. + +--- + +## Beads Plan Structure + +The GraphSchema feature is tracked in beads as epic `tinkerpop-big` with 17 child beads. Below is the dependency-ordered implementation plan with all design details. + +### Epic: tinkerpop-big - Graph Schema Interfaces + +**Status:** Open +**Priority:** 1 +**Owner:** [email protected] +**Created:** 2026-04-02 + +TinkerPop graph schema system: define vertex types, edge types, and property definitions as a meta-graph traversable with Gremlin. + +#### Core Design Decisions + +1. **Schema-as-Graph:** + - `GraphSchema extends Graph` — schema is itself a graph, familiar for providers, traversable via `schema.traversal()` + +2. **Type Definitions as Vertices:** + - All type definitions are vertices in the schema meta-graph + - `VertexType`, `EdgeType`, and `PropertyDefinition` all extend `Vertex` + - Even edge types are represented as vertices, connected to their endpoint `VertexType` vertices via `from`/`to` edges + +3. **Property Definitions:** + - Property definitions are independent vertices in the schema graph + - Connected to parent type vertices via edges (e.g. `hasProperty`) + - Each type gets its own `PropertyDefinition` vertices — no sharing across types + +4. **Constraints:** + - Constraints are regular properties on type vertices (`PropertyDefinition`, `VertexType`, `EdgeType`) + - Keyed by string via `constraint(key, value)` + - Constraints on `EdgeType` support cases like edge multiplicity + - TinkerPop standardizes core constraint keys (`type`, `required`, `unique`, etc.) for interoperability + - Provider-specific constraints use namespaced key convention (e.g. `tinkergraph:notNull`) + +5. **Property Data Types:** + - Property data types are just another constraint (e.g. `constraint("type", String.class)`) + - Java `Class` is a placeholder for a future richer TinkerPop type system + +6. **Gremlin Integration:** + - New schema steps (`addVType`, `addEType`, `propertyType`, `constraint`, `type`, `instances`) go into core `GraphTraversal`/`GraphTraversalSource` (not a separate DSL) + - A `TraversalStrategy` restricts schema traversals to a safe step subset (allowlist-based) + +7. **Cross-Graph Introspection:** + - `type()` step: data→schema (e.g. `g.V().hasLabel("person").type()`) + - `instances()` step: schema→data (e.g. `schema.traversal().vertexType("person").instances()`) + - Enable round-trip traversals + +8. **Deferred Scope:** + - Schema enforcement (write-time validation) + - Transaction semantics + - IO/serialization (initially) + - Dynamic schema updates from data writes + +--- + +### Priority 1 Beads (Core Implementation) + +#### tinkerpop-big.1: Core Schema Interfaces (gremlin-core) + +**Status:** In Progress +**Priority:** 1 +**Type:** Feature +**Dependencies:** None (root task) + +Define the core schema interfaces in `gremlin-core` (`org.apache.tinkerpop.gremlin.structure`). + +**Scope:** +- `GraphSchema` interface (extends `Graph`) with `vertexTypes(String... labels)` method +- `VertexType` interface (extends `Vertex`) — represents a vertex type definition in the schema graph +- `PropertyDefinition` interface (extends `Vertex`) — represents a property definition vertex, connected to its parent type via a `hasProperty` edge. Constraints are regular properties on this vertex. +- `Graph.schema()` default method that throws `UnsupportedOperationException` +- Define well-known edge labels for schema relationships: `hasProperty` (and later `from`/`to` for edge types) + +**Design Notes:** +- `GraphSchema extends Graph` so the schema is traversable with Gremlin and familiar for providers to implement +- Property definitions are independent vertices (not shared across types) +- Constraints are regular properties on type vertices, provider-specific + +**Additional Context:** +Schema interfaces may be parameterized by a type parameter `<T>` for property value types, allowing providers to plug in their own type system. A proper type system is out of scope for this initial work. (dev@ April 2026, Josh/Cole) + +--- + +#### tinkerpop-big.2: TinkerGraph Schema Implementation + +**Status:** In Progress +**Priority:** 1 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.1 + +Reference implementation of the schema interfaces for TinkerGraph. + +**Scope:** +- `TinkerGraphSchema` extends `TinkerGraph` implements `GraphSchema` +- `TinkerVertexType` extends `TinkerVertex` implements `VertexType` +- `TinkerPropertyDefinition` extends `TinkerVertex` implements `PropertyDefinition` — property defs as vertices with constraints as regular properties, connected to parent type via `hasProperty` edge +- `TinkerGraph.schema()` returns the schema instance +- Recursion guard: `initSchema()` override in `TinkerGraphSchema` to prevent schema-of-schema +- Tests: create vertex types, add property definitions as vertices, query constraints, schema graph traversability + +**Notes:** +Existing PoC code in the branch can be refactored to match the new design (property defs as vertices instead of meta-properties) + +--- + +#### tinkerpop-big.3: Edge Types + +**Status:** In Progress +**Priority:** 1 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.2 + +Add edge type support to the schema system. + +**Scope:** +- `EdgeType` interface (extends `Vertex`) in gremlin-core — represents an edge type definition in the schema graph +- `from`/`to` edges from `EdgeType` vertices to `VertexType` vertices to express connectivity constraints +- `EdgeRole` class in the structure API — represents the (EdgeType, VertexType, Direction) triple, mapped to a `from` or `to` edge in the schema graph. Each EdgeRole can carry its own constraints (e.g. multiplicity, uniqueness, ordering) as properties on the from/to edge. +- `GraphSchema.edgeTypes(String... labels)` method +- `TinkerEdgeType` implementation in tinkergraph-gremlin +- Tests: create edge types, connect to vertex types via from/to, add property definitions to edge types, add constraints to EdgeRoles, query + +**Schema Graph Example:** +``` +(knows:edgeType) --from[multiplicity:0..*]--> (person:vertexType) +(knows:edgeType) --to[multiplicity:0..*]--> (person:vertexType) +(knows:edgeType) --hasProperty--> (weight:propertyDefinition) +(created:edgeType) --from--> (person:vertexType) +(created:edgeType) --to--> (software:vertexType) +``` + +**Design Notes:** +- `EdgeRole` is an in-memory object representing the triplet, not a vertex in the schema graph. The actual data lives on the from/to edges between EdgeType and VertexType vertices. +- Distinct from/to connections for the same EdgeType can have independent constraints (e.g. A loves[0..1] B vs A loves[0..*] C). +- This concept originated from Pieter's Sqlg experience on the dev@ mailing list (April 2026). + +--- + +#### tinkerpop-big.4: Schema Traversal Steps + +**Status:** In Progress +**Priority:** 1 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.2 + +Add dedicated Gremlin steps for schema manipulation in core `GraphTraversal`/`GraphTraversalSource`. + +**Scope:** +- `addVType(label)` — source spawn step on `GraphTraversalSource`, creates a `VertexType` vertex in the schema graph +- `addEType(label)` — source spawn step, creates an `EdgeType` vertex in the schema graph +- `from(label)` / `to(label)` — when following `addEType`, creates `from`/`to` edges in the schema graph connecting the `EdgeType` to the referenced `VertexType` vertices. Mirrors the `addE().from().to()` pattern from the data graph, but takes vertex type labels (strings) instead of vertex references. +- `propertyType(name)` — step that creates a `PropertyDefinition` vertex and connects it to the current type vertex via `hasProperty` edge +- `constraint(key, value)` — step that adds a constraint property to the current type vertex +- Tests: step execution against schema graph, fluent chaining + +**Example Usage:** + +```java +schema.traversal().addVType("person") + .propertyType("name").constraint("type", String.class).constraint("required", true).constraint("unique", true) + .propertyType("age").constraint("type", Integer.class) +``` + +```java +schema.traversal().addEType("knows") + .from("person").to("person") + .propertyType("weight").constraint("type", Double.class) +``` + +--- + +#### tinkerpop-big.12: GraphSchema Convenience Methods + +**Status:** In Progress +**Priority:** 1 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.3 + +Add direct convenience methods to `GraphSchema`, `VertexType`, and `EdgeType` interfaces for compact, non-traversal access and mutation. The schema-as-graph model is the source of truth, but traversing it for simple lookups is too expensive for hot paths like strategy optimization. + +**Scope:** + +**GraphSchema methods:** +- `vertexTypes()` → `Collection<VertexType>` +- `vertexType(String label)` → `Optional<VertexType>` +- `edgeTypes()` → `Collection<EdgeType>` +- `edgeType(String label)` → `Optional<EdgeType>` +- `addVertexType(String label)` → `VertexType` (mirrors `Graph.addVertex()` pattern) +- `addEdgeType(String label)` → `EdgeType` + +**VertexType / EdgeType methods:** +- `propertyTypes()` → `Collection<PropertyDefinition>` + +**EdgeType methods:** +- `fromVertexTypes()` → `Collection<VertexType>` +- `toVertexTypes()` → `Collection<VertexType>` +- `edgeRoles()` → `Collection<EdgeRole>` — returns all EdgeRoles for this edge type +- `edgeRole(VertexType, Direction)` → `Optional<EdgeRole>` — lookup a specific EdgeRole + +**TinkerGraph implementation:** +- Implement all convenience methods in `TinkerGraphSchema`, `TinkerVertexType`, `TinkerEdgeType` + +**Design Notes:** +- Returned objects are the same schema graph vertices (`VertexType`, `EdgeType`), not separate POJOs +- Read accessors return live views backed by the schema graph +- Uses `Optional` for single-type lookups (modernizing away from null returns) +- Callers are responsible for caching compact views if needed +- `addVertexType`/`addEdgeType` create the vertex in the schema graph and return it — no detached vertex construction +- EdgeRole convenience methods provide access to the (EdgeType, VertexType, Direction) triples and their per-connection constraints (multiplicity, uniqueness, ordering), replacing the earlier "unpaired collections" model + +--- + +### Priority 2 Beads (Important but Not Minimal) + +#### tinkerpop-big.5: Schema Restriction TraversalStrategy + +**Status:** Open +**Priority:** 2 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.4 + +TraversalStrategy that restricts schema traversals to a safe subset of Gremlin steps. + +**Scope:** +- Allowlist-based `TraversalStrategy` that validates steps in schema graph traversals +- Permit basic CRUD-oriented steps: `addV`/`addE` type steps, `has`, `property`, `drop`, `V`, `E`, `values`, `properties`, schema-specific steps +- Block OLAP steps: `withComputer`, `pageRank`, `shortestPath` +- Block control flow: `repeat`, `branch`, `choose` +- Block side-effects: `aggregate`, `store`, `sack`, `subgraph` +- Strategy should be automatically registered when traversing a `GraphSchema` instance +- Tests: allowed steps pass, banned steps throw + +**TODO:** +Carefully audit and finalize the banned/allowed step list — the lists above are a starting point, not final + +--- + +#### tinkerpop-big.6: Cross-Graph Introspection Steps + +**Status:** Open +**Priority:** 2 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.2 + +Gremlin steps for cross-graph traversal between data graph and schema graph. + +**Scope:** +- `type()` step — from a data graph traversal, jump to the element's type definition in the schema graph. E.g. `g.V().hasLabel("person").type()` returns the `VertexType` vertex for "person". +- `instances()` step — from a schema graph traversal, jump to all matching elements in the data graph. E.g. `schema.traversal().vertexType("person").instances()` returns all person vertices. +- Round-trip support: `g.V().has("person", "name", "marko").type().instances()` returns all person vertices. +- Tests: both directions, round-trip traversals + +**Design Notes:** +- `type()` needs access to the schema graph via the `Graph` reference the traversal already holds +- `instances()` needs access to the data graph — the schema already has a reference to its parent graph since `GraphSchema` is obtained from `Graph.schema()` + +--- + +#### tinkerpop-big.10: GLV Support for Schema Steps + +**Status:** Open +**Priority:** 2 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.4, tinkerpop-big.6 + +Add Gremlin Language Variant support for the schema system. + +**Scope:** + +**Schema data classes:** +Each GLV (Python, JavaScript, .NET, Go) needs parallel classes to the 4 core Java interfaces, following the same pattern as existing `Vertex` and `Edge` classes. These are data containers representing schema objects returned from the server: +- `GraphSchema` — holds collections of `VertexType` and `EdgeType` +- `VertexType` — label + collection of `PropertyDefinition` +- `EdgeType` — label + `from`/`to` `VertexType` references (same pattern as `Edge.outV`/`Edge.inV`) + collection of `PropertyDefinition` +- `PropertyDefinition` — name, type, and full constraints map + +**Schema step methods:** +The new traversal steps (`addVType`, `addEType`, `propertyType`, `constraint`, `type`, `instances`) added to each GLV like any other standard Gremlin step. + +--- + +#### tinkerpop-big.11: Multi-Label Vertex Implications for Schema + +**Status:** Open +**Priority:** 2 +**Type:** Task + +Investigate and address implications of multi-label vertices for the schema system. + +**Questions to Resolve:** +- Some providers support vertices with multiple labels. If a vertex has labels "person" and "employee", what does `type()` return? Both VertexType vertices? +- Should `VertexType` model single labels or label-sets? +- How do property definitions compose when a vertex matches multiple types? +- Does schema enforcement (bead 7) need to validate against all matching types or just one? +- Should the schema system remain label-based (matching TinkerGraph's single-label model) with multi-label as a provider extension, or should it be first-class? + +**Notes:** +- TinkerGraph only supports single-label vertices, so the reference implementation won't exercise this +- This may only need a design decision and documentation rather than code changes, depending on the outcome + +--- + +#### tinkerpop-big.13: Instance Count Methods for Type Vertices + +**Status:** Open +**Priority:** 2 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.12 + +Add methods to `VertexType` and `EdgeType` interfaces to retrieve the count of data graph instances for each type. + +**Scope:** +- `VertexType.instanceCount()` → `long` +- `EdgeType.instanceCount()` → `long` +- TinkerGraph default implementation: maintain an atomic counter incremented/decremented on vertex/edge add/remove +- Providers are free to choose their own implementation strategy (incremental counters, live queries, approximate counts, etc.) +- Default implementation in the interface can throw `UnsupportedOperationException` + +**Design Notes:** +- Method approach chosen over property-on-type-vertex to keep the schema graph clean (definitional, not statistical) and give providers full implementation flexibility +- Approximate counts are likely acceptable and preferable for performance in most cases. However, TinkerPop should not stand in the way of providers that prefer exact counts, and should ensure that appropriate hooks are in place in reference implementations so that providers can maintain exact counts if they so desire. +- Transactional implications need additional consideration. Maintaining accurate counts across concurrent writes, rollbacks, and transaction isolation levels adds significant complexity. This interacts with the broader schema transactions question (tinkerpop-big.8) and should be addressed alongside it. +- Future consideration: providers could optionally materialize the count as a property on the type vertex as well, enabling Gremlin traversability (e.g., sorting types by instance count). Not in scope for this bead but worth revisiting if use cases emerge +- Incremental maintenance on writes should tie into bead 9 (Dynamic Schema Updates from Data Writes) for providers that opt into write-path schema integration + +--- + +### Priority 3 Beads (Future/Deferred) + +#### tinkerpop-big.7: Schema Validation + +**Status:** Open +**Priority:** 3 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.4 + +PLACEHOLDER — Deferred. + +Write-time validation of data graph mutations against the schema. When enabled, operations like `addVertex("person")` would validate that "person" is a defined vertex type and that required properties/constraints are satisfied. + +This is a significant feature that touches the write path of every provider. Design and scope TBD. + +--- + +#### tinkerpop-big.8: Schema Transactions + +**Status:** Open +**Priority:** 3 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.4 + +WORK IN PROGRESS — Needs further discussion and agreement before implementation. + +Transaction semantics for schema graphs. + +**Open Questions:** +- Should schema mutations be transactional? How does this interact with the data graph's transaction system? +- Can schema and data mutations participate in the same transaction, or are they always separate? +- What should the default behavior be for providers that don't explicitly implement schema transaction support? + +**Discussion Context (dev@ April 2026):** +The following points emerged from the mailing list thread but have NOT been formally agreed upon as design decisions: +- Pieter noted that backends vary widely: some support transactions for both schema and data, some only for data, and some (e.g. MariaDB) silently commit the current transaction when a schema change occurs. His position is that TinkerPop should not impose synchronization — providers manage their own transaction semantics. +- Cole's initial thinking is that TinkerGraph (reference impl) would sync begins, commits, and rollbacks across data and schema graphs for simplicity, while providers remain free to diverge. +- Pieter questioned whether TinkerPop needs to coordinate anything at all, since schema persistence is fundamentally the provider's responsibility. + +These points need further consideration and broader community input before settling on a design. + +--- + +#### tinkerpop-big.9: Dynamic Schema Updates from Data Writes + +**Status:** Open +**Priority:** 3 +**Type:** Feature +**Dependencies:** Blocks on tinkerpop-big.4 + +PLACEHOLDER — Deferred. + +System for Gremlin write steps (e.g. `addV` with a new label, or adding a property with a new key) to automatically update the schema graph when providers opt into this behavior. + +This enables schema-on-write / schema evolution patterns where the schema stays in sync with the data without requiring explicit schema management. Providers should be able to opt in or out of this behavior. + +Design and scope TBD. + +--- + +#### tinkerpop-big.14: Schema Type Upsert Steps + +**Status:** Open +**Priority:** 3 +**Type:** Feature + +PLACEHOLDER — Design TBD. + +Upsert semantics for schema type definitions (create-if-not-exists, update-if-exists). The data graph has `mergeV()`/`mergeE()` for this pattern — need to decide whether to extend that pattern (`mergeVType()`/`mergeEType()`), make `addVType()`/`addEType()` idempotent, or take a different approach. + +This likely does not need to be included in a minimal viable product. Consider deferring until the feature is demanded by real usage patterns. + +--- + +#### tinkerpop-big.15: Schema Extraction Step + +**Status:** Open +**Priority:** 3 +**Type:** Feature + +PLACEHOLDER — Design TBD. + +A Gremlin step to return a full or partial schema from a traversal, potentially extending the subgraph() pattern. + +--- + +#### tinkerpop-big.16: Schema Serialization + +**Status:** Open +**Priority:** 3 +**Type:** Feature + +Serialization format and IO for schema graphs. + +A custom compact JSON representation is preferred over reusing GraphSON/GraphBinary. The schema graph has a well-known, fixed structure (vertex types, edge types, property definitions, from/to edges, hasProperty edges) that can be serialized much more compactly than a generic graph format. A dedicated JSON format would be more readable, easier to version control, and simpler for providers and users to work with. + +**Scope:** +- `GraphSchema.store(OutputStream)` — serialize the schema to a compact JSON representation +- `GraphSchema.load(InputStream)` — deserialize a schema from JSON and merge it into the current schema graph +- TinkerGraph reference implementation of both methods +- Schema file IO should be replicated across all GLVs + +**Open Questions:** +- What does the compact JSON structure look like — flat list of types, or nested (types containing their property types)? +- Should the format support round-tripping provider-specific constraints? +- How should schemas be merged during `load()` — handling type name collisions, conflicting property types, differing constraints? The intent is to leave providers extension points to customize merging behaviour as desired. + +--- + +#### tinkerpop-big.17: Schema Lock/Unlock + +**Status:** Open +**Priority:** 3 +**Type:** Feature + +Schema lock/unlock mechanism to prevent unintended schema mutations on graphs with a well-defined schema. + +**Scope:** +- `GraphSchema.lock()` — globally lock the schema; all schema mutations are rejected until unlocked +- `GraphSchema.unlock()` — globally unlock the schema +- `graph.tx().unlock()` — transaction-scoped unlock; allows schema mutations within the current transaction only. Automatically re-locks on `commit()` or `rollback()` if the schema itself is locked. + +**Design Notes:** +- Lock/unlock lives on the structure API (GraphSchema), not in the Gremlin language or remote APIs. Can be added to Gremlin later if demand arises. +- This concept originated from Pieter's suggestion on the dev@ mailing list (April 2026). Cole agreed with the approach. + +--- + +## Dependency Graph + +``` +tinkerpop-big (epic) +├── tinkerpop-big.1 (Core Schema Interfaces) +│ ├── tinkerpop-big.2 (TinkerGraph Implementation) +│ │ ├── tinkerpop-big.3 (Edge Types) +│ │ │ └── tinkerpop-big.12 (Convenience Methods) +│ │ │ └── tinkerpop-big.13 (Instance Count) +│ │ ├── tinkerpop-big.4 (Schema Traversal Steps) +│ │ │ ├── tinkerpop-big.5 (Restriction Strategy) +│ │ │ ├── tinkerpop-big.7 (Schema Validation) [Priority 3] +│ │ │ ├── tinkerpop-big.8 (Transactions) [Priority 3] +│ │ │ ├── tinkerpop-big.9 (Dynamic Updates) [Priority 3] +│ │ │ └── tinkerpop-big.10 (GLV Support) +│ │ └── tinkerpop-big.6 (Cross-Graph Introspection) +│ │ └── tinkerpop-big.10 (GLV Support) +│ └── tinkerpop-big.11 (Multi-Label Investigation) +├── tinkerpop-big.14 (Upsert Steps) [Priority 3] +├── tinkerpop-big.15 (Schema Extraction) [Priority 3] +├── tinkerpop-big.16 (Serialization) [Priority 3] +└── tinkerpop-big.17 (Lock/Unlock) [Priority 3] +``` + +--- + +## Files in This Branch + +### Core Interfaces (gremlin-core) +- `gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphSchema.java` +- `gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexType.java` +- `gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/EdgeType.java` +- `gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyDefinition.java` + +### TinkerGraph Implementation +- `tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchema.java` +- `tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexType.java` +- `tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdgeType.java` +- `tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerPropertyDefinition.java` + +### Traversal Steps (Prototype/Exploration) +- `gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTypeStartStep.java` +- `gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTypeStartStep.java` +- `gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddPropertyTypeStep.java` +- Plus corresponding placeholder classes + +### Tests +- `tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchemaTest.java` +- `tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchemaTraversalTest.java` + +--- + +## Key Naming Note + +The beads plan references `PropertyType` but the PoC code uses `PropertyDefinition`. This is because `PropertyType` is already an enum in gremlin-core (for distinguishing `Property` vs value). During implementation, the decision was made to use `PropertyDefinition` to avoid name collision. The beads plan text above has NOT been updated to reflect this — when reading the plan, mentally substitute `PropertyDefinition` for `PropertyType`. + +--- + +## Design Context and History + +### Origin +The GraphSchema feature design emerged from mailing list discussions in April 2026, particularly informed by: +- Pieter's experience with Sqlg schema system +- Josh and Cole's discussions about type systems and parameterization +- Community input on schema enforcement, transactions, and multi-label vertices + +### Key Design Influences + +**Schema-as-Graph:** +The core insight is that if schema definitions are themselves graph elements, providers already know how to implement them (extend their existing vertex/edge implementations). This also makes schema introspection natural via Gremlin traversals. + +**EdgeRole Concept:** +Pieter introduced the EdgeRole idea from Sqlg — the notion that an edge type's connection to a vertex type (via `from` or `to`) can carry its own constraints (multiplicity, uniqueness, ordering). This is why EdgeType vertices connect to VertexType vertices via edges, not just references. + +**Deferred Complexity:** +Schema enforcement, transactions, and dynamic updates were deliberately deferred. The initial scope focuses on schema definition and introspection only. Write-time validation and transactional semantics add significant complexity and require broader community agreement. + +--- + +## Building and Testing + +From repository root: + +```bash +# Full build +mvn clean install + +# Build just gremlin-core +mvn clean install -pl gremlin-core -am + +# Build gremlin-core + tinkergraph +mvn clean install -pl gremlin-core,tinkergraph-gremlin -am + +# Run just TinkerGraph schema tests +mvn test -pl tinkergraph-gremlin -Dtest=TinkerGraphSchemaTest,TinkerGraphSchemaTraversalTest +``` + +--- + +## Next Steps for Resuming Work + +1. **Review beads plan above** — this is the authoritative source of design decisions, not the PoC code +2. **Assess PoC code quality** — determine which prototype code is salvageable vs. needs clean reimplementation +3. **Start with Priority 1 beads in dependency order:** + - Complete bead 1 (Core Interfaces) — interfaces mostly done, may need refinement + - Complete bead 2 (TinkerGraph Implementation) — PoC exists, likely needs cleanup + - Complete bead 3 (Edge Types) — EdgeRole design is complex, needs careful implementation + - Complete bead 4 (Schema Traversal Steps) — step placeholders exist, need full implementation + - Complete bead 12 (Convenience Methods) — critical for usability +4. **Update beads** as design evolves — use `bd update <id>` to capture new decisions +5. **Add tests incrementally** — TDD approach recommended for complex areas like EdgeRole +6. **Defer Priority 3 beads** until Priority 1/2 are complete and validated + +--- + +## Additional Resources + +- **Beads CLI:** Use `bd show <id>` to view individual bead details, `bd list` to see all beads +- **Mailing List:** Search [email protected] archives for April 2026 "schema" threads +- **Original Session Transcript:** If available, located in Kiro history (138 days ago, 442 messages) — may have additional design rationale not captured in beads + +--- + +## Questions and Unknowns + +When resuming work, the following questions may need resolution: + +1. **Final step API:** The beads describe the step semantics, but exact method signatures on `GraphTraversal`/`GraphTraversalSource` may need bikeshedding (parameter order, overloads, etc.) + +2. **EdgeRole implementation details:** EdgeRole is described as an in-memory object backed by from/to edges in the schema graph. The exact lifecycle (when created, cached, invalidated) needs careful design. + +3. **Constraint key standardization:** Which constraint keys does TinkerPop standardize (`type`, `required`, `unique`, `cardinality`, `multiplicity`, `ordering`)? Which are provider-specific? This needs documentation and possibly a `Constraints` constants class. + +4. **Multi-label resolution:** Bead 11 raises multi-label questions but doesn't answer them. This needs community input before finalizing. + +5. **Transaction semantics:** Bead 8 outlines open questions on schema transactions. Pieter and Cole have differing views. This needs dev@ discussion and consensus. + +6. **Minimal viable product scope:** Which beads constitute an MVP that could be proposed to the community? Likely Priority 1 beads plus bead 5 (restriction strategy) and bead 6 (cross-graph introspection). + +--- + +## End of Handoff Document + +This document captures the current state of the GraphSchema feature as of 2026-09-15. The branch contains early prototype code exploring the design. The beads plan above is the authoritative roadmap for implementation. When resuming work on a new machine, review this document and the beads plan, then assess which prototype code is salvageable before proceeding with implementation. diff --git a/GRAPHSCHEMA_TRANSFER.md b/GRAPHSCHEMA_TRANSFER.md new file mode 100644 index 0000000000..b7e53b471b --- /dev/null +++ b/GRAPHSCHEMA_TRANSFER.md @@ -0,0 +1,193 @@ +# GraphSchema Branch Transfer Checklist + +This checklist guides the transfer of GraphSchema work to a new development machine. + +## 1. Repository Transfer + +### On Current Machine (completed) +- [x] Handoff document created: `GRAPHSCHEMA_HANDOFF.md` +- [ ] Commit handoff document to GraphSchema branch +- [ ] Push GraphSchema branch to remote + +```bash +git add GRAPHSCHEMA_HANDOFF.md +git commit -m "Add GraphSchema handoff documentation" +git push origin GraphSchema +``` + +### On New Machine +- [ ] Clone repository: `git clone <repo-url>` +- [ ] Checkout GraphSchema branch: `git checkout GraphSchema` +- [ ] Read `GRAPHSCHEMA_HANDOFF.md` thoroughly +- [ ] Verify Java 11 or 17 is installed +- [ ] Verify Maven is installed +- [ ] Run initial build: `mvn clean install` + +## 2. Beads Transfer + +The beads database contains the authoritative plan. Two options: + +### Option A: Export/Import (Recommended for cross-machine transfer) + +**On Current Machine:** +```bash +# Export the GraphSchema epic and all children to JSON +bd export tinkerpop-big --recursive > ~/graphschema-beads-export.json + +# Transfer this file to new machine via: +# - Cloud storage (Dropbox, Google Drive, etc.) +# - USB drive +# - Email to self +# - SCP/rsync if machines are networked +``` + +**On New Machine:** +```bash +# Ensure beads is installed and dolt server is running +bd dolt start + +# Import the beads +bd import ~/graphschema-beads-export.json + +# Verify import +bd show tinkerpop-big +bd list --filter "id~tinkerpop-big" +``` + +### Option B: Direct Dolt Sync (If using shared Dolt remote) + +If using a shared Dolt remote configured in `~/.beads/shared-server/`: +```bash +# On new machine, beads will sync from the shared remote automatically +bd dolt start +bd list +``` + +## 3. Development Environment Setup + +### On New Machine + +1. **Install Java 11/17:** + - macOS: `brew install openjdk@17` + - Linux: Package manager (e.g., `apt install openjdk-17-jdk`) + - Verify: `java -version` + +2. **Install Maven:** + - macOS: `brew install maven` + - Linux: Package manager or download from apache.org + - Verify: `mvn -version` + +3. **Install Beads (if not already installed):** + ```bash + # Installation method depends on your beads setup + # Consult beads documentation or previous setup notes + ``` + +4. **Configure Git:** + ```bash + git config user.name "Your Name" + git config user.email "[email protected]" + ``` + +5. **Build TinkerPop:** + ```bash + cd tinkerpop + mvn clean install + # First build will download dependencies, may take 10-15 minutes + ``` + +## 4. Verify Transfer + +### Check Code is Present +```bash +# On new machine, in tinkerpop repository: +git branch --show-current +# Should output: GraphSchema + +ls -la gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/ | grep -i schema +# Should show: GraphSchema.java, VertexType.java, EdgeType.java, PropertyDefinition.java + +ls -la tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/ | grep -i schema +# Should show: TinkerGraphSchema.java, TinkerVertexType.java, etc. +``` + +### Check Beads are Present +```bash +bd show tinkerpop-big +# Should display the epic with description + +bd list --filter "parent=tinkerpop-big" +# Should show 17 child beads + +bd deps tinkerpop-big +# Should show dependency graph +``` + +### Run Tests +```bash +cd tinkerpop +mvn test -pl tinkergraph-gremlin -Dtest=TinkerGraphSchemaTest +# Tests should compile and run (may fail if prototype is incomplete) +``` + +## 5. Resume Work + +Once transfer is verified: + +1. **Read handoff document:** Review `GRAPHSCHEMA_HANDOFF.md` in full +2. **Review beads plan:** Use `bd show <id>` to review each Priority 1 bead +3. **Assess prototype code:** Determine what's salvageable vs. needs rewrite +4. **Start implementation:** Begin with `tinkerpop-big.1` and work through dependencies + +## 6. Ongoing Sync (Optional) + +If continuing to work across multiple machines: + +### Using Git +- Commit regularly to feature branches +- Push to remote repository +- Pull on other machine + +### Using Beads +If using shared Dolt remote: +```bash +# Changes automatically sync via beads +bd status # Check sync status +``` + +If using export/import: +```bash +# Export after making bead updates +bd export tinkerpop-big --recursive > ~/graphschema-beads-$(date +%Y%m%d).json +# Transfer file and import on other machine +``` + +## Common Issues + +### Maven Build Fails +- Ensure Java 11 or 17 (not 8, not 21+) +- Clear Maven cache: `rm -rf ~/.m2/repository` +- Re-run: `mvn clean install` + +### Beads Dolt Server Won't Start +- Check if port 3308 is in use: `lsof -i :3308` +- Check logs: `cat ~/.beads/shared-server/dolt-server.log` +- Kill existing process: `bd dolt stop` then `bd dolt start` + +### Tests Fail +- Expected if prototype is incomplete +- Focus on getting code to compile first +- Tests can be fixed during implementation + +### Beads Import Fails +- Ensure dolt server is running: `bd dolt status` +- Check export file is valid JSON: `jq . < export.json` +- Try importing individual beads if recursive import fails + +## Questions? + +Refer to: +- `GRAPHSCHEMA_HANDOFF.md` - Comprehensive feature documentation +- `AGENTS.md` - TinkerPop development guidelines +- `CONTRIBUTING.md` - Contribution guidelines +- `.kiro/skills/tinkerpop-dev/SKILL.md` - Development environment details diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java index c42859f7a5..118a174f84 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java @@ -55,6 +55,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.filter.TailGlobalStep import org.apache.tinkerpop.gremlin.process.traversal.step.map.RangeLocalStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.TailLocalStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStepPlaceholder; +import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddPropertyTypeStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.CallStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStepPlaceholder; @@ -64,6 +65,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeStepContract import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.PropertiesHolder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStepPlaceholder; +import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddConstraintStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStepContract; import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStepContract; @@ -214,6 +216,7 @@ import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Element; import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.GraphSchema; import org.apache.tinkerpop.gremlin.structure.Property; import org.apache.tinkerpop.gremlin.structure.PropertyType; import org.apache.tinkerpop.gremlin.structure.T; @@ -3825,6 +3828,37 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { } return this; } + + ///////////////////// SCHEMA STEPS ///////////////////// + + /** + * Creates a {@link PropertyDefinition} vertex in the schema graph and connects it to the current type vertex + * via a {@link GraphSchema.Tokens#HAS_PROPERTY} edge. The traverser shifts to the new property definition. + * If the current traverser is already a {@link PropertyDefinition}, the step walks back to the parent type vertex. + * + * @param name the property name + * @return the traversal with the {@link AddPropertyTypeStepPlaceholder} added + * @since 4.0.0 + */ + public default GraphTraversal<S, E> propertyType(final String name) { + this.asAdmin().getGremlinLang().addStep(Symbols.propertyType, name); + return this.asAdmin().addStep((Step) new AddPropertyTypeStepPlaceholder(this.asAdmin(), name)); + } + + /** + * Adds a constraint property to the current schema type vertex. The traverser remains on the same vertex, + * enabling chaining of multiple constraints. + * + * @param key the constraint key + * @param value the constraint value + * @return the traversal with the {@link AddConstraintStepPlaceholder} added + * @since 4.0.0 + */ + public default GraphTraversal<S, E> constraint(final String key, final Object value) { + this.asAdmin().getGremlinLang().addStep(Symbols.constraint, key, value); + return this.asAdmin().addStep((Step) new AddConstraintStepPlaceholder(this.asAdmin(), key, value)); + } + ///////////////////// BRANCH STEPS ///////////////////// /** @@ -4768,6 +4802,10 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { public static final String tree = "tree"; public static final String addV = "addV"; public static final String addE = "addE"; + public static final String addVType = "addVType"; + public static final String addEType = "addEType"; + public static final String propertyType = "propertyType"; + public static final String constraint = "constraint"; public static final String mergeV = "mergeV"; public static final String mergeE = "mergeE"; public static final String from = "from"; diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java index 684d9e8609..ed4dad8578 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java @@ -30,7 +30,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.branch.UnionStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStartStepPlaceholder; +import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeTypeStartStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStepPlaceholder; +import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTypeStartStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.CallStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.CallStepPlaceholder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep; @@ -399,6 +401,36 @@ public class GraphTraversalSource implements TraversalSource { return traversal.addStep(new AddEdgeStartStepPlaceholder(traversal, label)); } + /** + * Spawns a {@link GraphTraversal} by adding a vertex type to the schema graph with the specified label. + * + * @param label the vertex type label + * @return the traversal with the {@link AddVertexTypeStartStepPlaceholder} added + * @since 4.0.0 + */ + public GraphTraversal<Vertex, Vertex> addVType(final String label) { + if (null == label) throw new IllegalArgumentException("label cannot be null"); + final GraphTraversalSource clone = this.clone(); + clone.gremlinLang.addStep(GraphTraversal.Symbols.addVType, label); + final GraphTraversal.Admin<Vertex, Vertex> traversal = new DefaultGraphTraversal<>(clone); + return traversal.addStep(new AddVertexTypeStartStepPlaceholder(traversal, label)); + } + + /** + * Spawns a {@link GraphTraversal} by adding an edge type to the schema graph with the specified label. + * + * @param label the edge type label + * @return the traversal with the {@link AddEdgeTypeStartStepPlaceholder} added + * @since 4.0.0 + */ + public GraphTraversal<Vertex, Vertex> addEType(final String label) { + if (null == label) throw new IllegalArgumentException("label cannot be null"); + final GraphTraversalSource clone = this.clone(); + clone.gremlinLang.addStep(GraphTraversal.Symbols.addEType, label); + final GraphTraversal.Admin<Vertex, Vertex> traversal = new DefaultGraphTraversal<>(clone); + return traversal.addStep(new AddEdgeTypeStartStepPlaceholder(traversal, label)); + } + /** * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Vertex} using a * {@code Map} as an argument. The {@code Map} represents search criteria and will match each of the supplied diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java index 01986b4534..56ab64d623 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java @@ -1558,6 +1558,22 @@ public class __ { return __.<A>start().property(value); } + ///////////////////// SCHEMA STEPS ///////////////////// + + /** + * @see GraphTraversal#propertyType(String) + */ + public static <A> GraphTraversal<A, A> propertyType(final String name) { + return __.<A>start().propertyType(name); + } + + /** + * @see GraphTraversal#constraint(String, Object) + */ + public static <A> GraphTraversal<A, A> constraint(final String key, final Object value) { + return __.<A>start().constraint(key, value); + } + ///////////////////// BRANCH STEPS ///////////////////// /** diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTypeStartStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTypeStartStep.java new file mode 100644 index 0000000000..98fd40eef8 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTypeStartStep.java @@ -0,0 +1,117 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.process.traversal.step.map; + +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.FromToModulating; +import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.CallbackRegistry; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.ListCallbackRegistry; +import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException; +import org.apache.tinkerpop.gremlin.structure.EdgeType; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.GraphSchema; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.VertexType; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Creates an {@link EdgeType} in the schema graph and optionally connects it to source/target + * {@link VertexType} vertices via {@code from}/{@code to} edges. This is the concrete step resolved + * from {@link AddEdgeTypeStartStepPlaceholder}. + * + * @see org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource#addEType(String) + */ +public class AddEdgeTypeStartStep extends AbstractStep<Vertex, Vertex> + implements FromToModulating, Mutating<Event.VertexAddedEvent> { + + private final String label; + private boolean first = true; + private final List<String> fromLabels = new ArrayList<>(); + private final List<String> toLabels = new ArrayList<>(); + private CallbackRegistry<Event.VertexAddedEvent> callbackRegistry; + + public AddEdgeTypeStartStep(final Traversal.Admin traversal, final String label) { + super(traversal); + this.label = label; + } + + @Override + public void addFrom(final String fromLabel) { + this.fromLabels.add(fromLabel); + } + + @Override + public void addTo(final String toLabel) { + this.toLabels.add(toLabel); + } + + @Override + protected Traverser.Admin<Vertex> processNextStart() throws NoSuchElementException { + if (this.first) { + this.first = false; + final Graph graph = this.getTraversal().getGraph().get(); + if (!(graph instanceof GraphSchema)) + throw new IllegalStateException("addEType() requires a schema graph"); + final GraphSchema schema = (GraphSchema) graph; + final EdgeType et = schema.addEdgeType(this.label); + for (final String fromLabel : this.fromLabels) { + final VertexType vt = schema.vertexType(fromLabel).orElseThrow( + () -> new IllegalStateException("VertexType '" + fromLabel + "' not found in schema")); + ((Vertex) et).addEdge(GraphSchema.Tokens.FROM, (Vertex) vt); + } + for (final String toLabel : this.toLabels) { + final VertexType vt = schema.vertexType(toLabel).orElseThrow( + () -> new IllegalStateException("VertexType '" + toLabel + "' not found in schema")); + ((Vertex) et).addEdge(GraphSchema.Tokens.TO, (Vertex) vt); + } + return this.getTraversal().getTraverserGenerator().generate((Vertex) et, this, 1L); + } + throw FastNoSuchElementException.instance(); + } + + @Override + public CallbackRegistry<Event.VertexAddedEvent> getMutatingCallbackRegistry() { + if (null == this.callbackRegistry) this.callbackRegistry = new ListCallbackRegistry<>(); + return this.callbackRegistry; + } + + public String getLabel() { + return this.label; + } + + public List<String> getFromLabels() { + return this.fromLabels; + } + + public List<String> getToLabels() { + return this.toLabels; + } + + @Override + public int hashCode() { + return super.hashCode() ^ (null == this.label ? 0 : this.label.hashCode()); + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTypeStartStepPlaceholder.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTypeStartStepPlaceholder.java new file mode 100644 index 0000000000..fd3bcb76cf --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTypeStartStepPlaceholder.java @@ -0,0 +1,95 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.process.traversal.step.map; + +import org.apache.tinkerpop.gremlin.process.traversal.Step; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.FromToModulating; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValueHolder; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep; +import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Placeholder for {@link AddEdgeTypeStartStep} that participates in the GValue reduction strategy. + * Implements {@link FromToModulating} to support {@code from(label)} and {@code to(label)} modulation. + */ +public class AddEdgeTypeStartStepPlaceholder extends AbstractStep<Vertex, Vertex> + implements GValueHolder<Vertex, Vertex>, FromToModulating { + + private final String label; + private final List<String> fromLabels = new ArrayList<>(); + private final List<String> toLabels = new ArrayList<>(); + + public AddEdgeTypeStartStepPlaceholder(final Traversal.Admin traversal, final String label) { + super(traversal); + this.label = label; + } + + @Override + public void addFrom(final String fromLabel) { + this.fromLabels.add(fromLabel); + } + + @Override + public void addTo(final String toLabel) { + this.toLabels.add(toLabel); + } + + @Override + protected Traverser.Admin<Vertex> processNextStart() throws NoSuchElementException { + throw new IllegalStateException("Placeholder step is not executable"); + } + + @Override + public Step<Vertex, Vertex> asConcreteStep() { + final AddEdgeTypeStartStep step = new AddEdgeTypeStartStep(this.getTraversal(), this.label); + for (final String from : this.fromLabels) { + step.addFrom(from); + } + for (final String to : this.toLabels) { + step.addTo(to); + } + TraversalHelper.copyLabels(this, step, false); + return step; + } + + @Override + public boolean isParameterized() { + return false; + } + + @Override + public void updateVariable(final String name, final Object value) { + // no GValue parameters + } + + @Override + public Collection<GValue<?>> getGValues() { + return Collections.emptyList(); + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddPropertyTypeStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddPropertyTypeStep.java new file mode 100644 index 0000000000..c36dbf5d15 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddPropertyTypeStep.java @@ -0,0 +1,81 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.process.traversal.step.map; + +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.CallbackRegistry; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.ListCallbackRegistry; +import org.apache.tinkerpop.gremlin.structure.Direction; +import org.apache.tinkerpop.gremlin.structure.GraphSchema; +import org.apache.tinkerpop.gremlin.structure.PropertyDefinition; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +/** + * Creates a {@link PropertyDefinition} vertex in the schema graph and connects it to the current type vertex + * via a {@link GraphSchema.Tokens#HAS_PROPERTY} edge. The traverser shifts to the new property definition. + * <p/> + * If the current traverser is a {@link PropertyDefinition} (from a previous {@code propertyType()} call), + * this step walks back to the parent type vertex before creating the new property definition. + * + * @see org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal#propertyType(String) + */ +public class AddPropertyTypeStep extends ScalarMapStep<Vertex, Vertex> + implements Mutating<Event.VertexAddedEvent> { + + private final String name; + private CallbackRegistry<Event.VertexAddedEvent> callbackRegistry; + + public AddPropertyTypeStep(final Traversal.Admin traversal, final String name) { + super(traversal); + this.name = name; + } + + @Override + protected Vertex map(final Traverser.Admin<Vertex> traverser) { + final Vertex current = traverser.get(); + final Vertex typeVertex; + if (current instanceof PropertyDefinition) { + typeVertex = current.edges(Direction.IN, GraphSchema.Tokens.HAS_PROPERTY).next().outVertex(); + } else { + typeVertex = current; + } + final GraphSchema schema = (GraphSchema) this.getTraversal().getGraph().get(); + final PropertyDefinition pd = schema.addPropertyDefinition(this.name); + typeVertex.addEdge(GraphSchema.Tokens.HAS_PROPERTY, (Vertex) pd); + return pd; + } + + @Override + public CallbackRegistry<Event.VertexAddedEvent> getMutatingCallbackRegistry() { + if (null == this.callbackRegistry) this.callbackRegistry = new ListCallbackRegistry<>(); + return this.callbackRegistry; + } + + public String getName() { + return this.name; + } + + @Override + public int hashCode() { + return super.hashCode() ^ (null == this.name ? 0 : this.name.hashCode()); + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddPropertyTypeStepPlaceholder.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddPropertyTypeStepPlaceholder.java new file mode 100644 index 0000000000..917dd8154d --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddPropertyTypeStepPlaceholder.java @@ -0,0 +1,71 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.process.traversal.step.map; + +import org.apache.tinkerpop.gremlin.process.traversal.Step; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValueHolder; +import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import java.util.Collection; +import java.util.Collections; + +/** + * Placeholder for {@link AddPropertyTypeStep} that participates in the GValue reduction strategy. + */ +public class AddPropertyTypeStepPlaceholder extends ScalarMapStep<Vertex, Vertex> + implements GValueHolder<Vertex, Vertex> { + + private final String name; + + public AddPropertyTypeStepPlaceholder(final Traversal.Admin traversal, final String name) { + super(traversal); + this.name = name; + } + + @Override + protected Vertex map(final Traverser.Admin<Vertex> traverser) { + throw new IllegalStateException("Placeholder step is not executable"); + } + + @Override + public Step<Vertex, Vertex> asConcreteStep() { + final AddPropertyTypeStep step = new AddPropertyTypeStep(this.getTraversal(), this.name); + TraversalHelper.copyLabels(this, step, false); + return step; + } + + @Override + public boolean isParameterized() { + return false; + } + + @Override + public void updateVariable(final String name, final Object value) { + // no GValue parameters + } + + @Override + public Collection<GValue<?>> getGValues() { + return Collections.emptyList(); + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTypeStartStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTypeStartStep.java new file mode 100644 index 0000000000..160bd7e369 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTypeStartStep.java @@ -0,0 +1,81 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.process.traversal.step.map; + +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.CallbackRegistry; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.ListCallbackRegistry; +import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.GraphSchema; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.VertexType; + +import java.util.NoSuchElementException; + +/** + * Creates a {@link VertexType} in the schema graph. This is the concrete step resolved from + * {@link AddVertexTypeStartStepPlaceholder}. + * + * @see org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource#addVType(String) + */ +public class AddVertexTypeStartStep extends AbstractStep<Vertex, Vertex> + implements Mutating<Event.VertexAddedEvent> { + + private final String label; + private boolean first = true; + private CallbackRegistry<Event.VertexAddedEvent> callbackRegistry; + + public AddVertexTypeStartStep(final Traversal.Admin traversal, final String label) { + super(traversal); + this.label = label; + } + + @Override + protected Traverser.Admin<Vertex> processNextStart() throws NoSuchElementException { + if (this.first) { + this.first = false; + final Graph graph = this.getTraversal().getGraph().get(); + if (!(graph instanceof GraphSchema)) + throw new IllegalStateException("addVType() requires a schema graph"); + final VertexType vt = ((GraphSchema) graph).addVertexType(this.label); + return this.getTraversal().getTraverserGenerator().generate((Vertex) vt, this, 1L); + } + throw FastNoSuchElementException.instance(); + } + + @Override + public CallbackRegistry<Event.VertexAddedEvent> getMutatingCallbackRegistry() { + if (null == this.callbackRegistry) this.callbackRegistry = new ListCallbackRegistry<>(); + return this.callbackRegistry; + } + + public String getLabel() { + return this.label; + } + + @Override + public int hashCode() { + return super.hashCode() ^ (null == this.label ? 0 : this.label.hashCode()); + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTypeStartStepPlaceholder.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTypeStartStepPlaceholder.java new file mode 100644 index 0000000000..fb5b1727b1 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTypeStartStepPlaceholder.java @@ -0,0 +1,73 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.process.traversal.step.map; + +import org.apache.tinkerpop.gremlin.process.traversal.Step; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValueHolder; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep; +import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import java.util.Collection; +import java.util.Collections; +import java.util.NoSuchElementException; + +/** + * Placeholder for {@link AddVertexTypeStartStep} that participates in the GValue reduction strategy. + */ +public class AddVertexTypeStartStepPlaceholder extends AbstractStep<Vertex, Vertex> + implements GValueHolder<Vertex, Vertex> { + + private final String label; + + public AddVertexTypeStartStepPlaceholder(final Traversal.Admin traversal, final String label) { + super(traversal); + this.label = label; + } + + @Override + protected Traverser.Admin<Vertex> processNextStart() throws NoSuchElementException { + throw new IllegalStateException("Placeholder step is not executable"); + } + + @Override + public Step<Vertex, Vertex> asConcreteStep() { + final AddVertexTypeStartStep step = new AddVertexTypeStartStep(this.getTraversal(), this.label); + TraversalHelper.copyLabels(this, step, false); + return step; + } + + @Override + public boolean isParameterized() { + return false; + } + + @Override + public void updateVariable(final String name, final Object value) { + // no GValue parameters + } + + @Override + public Collection<GValue<?>> getGValues() { + return Collections.emptyList(); + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddConstraintStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddConstraintStep.java new file mode 100644 index 0000000000..4129e01226 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddConstraintStep.java @@ -0,0 +1,73 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect; + +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.CallbackRegistry; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.ListCallbackRegistry; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import java.util.Objects; + +/** + * Adds a constraint property to the current schema type vertex. The traverser remains on the same vertex, + * enabling chaining of multiple constraints. + * + * @see org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal#constraint(String, Object) + */ +public class AddConstraintStep extends SideEffectStep<Vertex> + implements Mutating<Event.VertexPropertyChangedEvent> { + + private final String key; + private final Object value; + private CallbackRegistry<Event.VertexPropertyChangedEvent> callbackRegistry; + + public AddConstraintStep(final Traversal.Admin traversal, final String key, final Object value) { + super(traversal); + this.key = key; + this.value = value; + } + + @Override + protected void sideEffect(final Traverser.Admin<Vertex> traverser) { + traverser.get().property(this.key, this.value); + } + + @Override + public CallbackRegistry<Event.VertexPropertyChangedEvent> getMutatingCallbackRegistry() { + if (null == this.callbackRegistry) this.callbackRegistry = new ListCallbackRegistry<>(); + return this.callbackRegistry; + } + + public String getConstraintKey() { + return this.key; + } + + public Object getConstraintValue() { + return this.value; + } + + @Override + public int hashCode() { + return super.hashCode() ^ Objects.hash(this.key, this.value); + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddConstraintStepPlaceholder.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddConstraintStepPlaceholder.java new file mode 100644 index 0000000000..077a81980d --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddConstraintStepPlaceholder.java @@ -0,0 +1,73 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect; + +import org.apache.tinkerpop.gremlin.process.traversal.Step; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValueHolder; +import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import java.util.Collection; +import java.util.Collections; + +/** + * Placeholder for {@link AddConstraintStep} that participates in the GValue reduction strategy. + */ +public class AddConstraintStepPlaceholder extends SideEffectStep<Vertex> + implements GValueHolder<Vertex, Vertex> { + + private final String key; + private final Object value; + + public AddConstraintStepPlaceholder(final Traversal.Admin traversal, final String key, final Object value) { + super(traversal); + this.key = key; + this.value = value; + } + + @Override + protected void sideEffect(final Traverser.Admin<Vertex> traverser) { + throw new IllegalStateException("Placeholder step is not executable"); + } + + @Override + public Step<Vertex, Vertex> asConcreteStep() { + final AddConstraintStep step = new AddConstraintStep(this.getTraversal(), this.key, this.value); + TraversalHelper.copyLabels(this, step, false); + return step; + } + + @Override + public boolean isParameterized() { + return false; + } + + @Override + public void updateVariable(final String name, final Object value) { + // no GValue parameters + } + + @Override + public Collection<GValue<?>> getGValues() { + return Collections.emptyList(); + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/EdgeRole.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/EdgeRole.java new file mode 100644 index 0000000000..2bd8e98a4d --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/EdgeRole.java @@ -0,0 +1,78 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.structure; + +import java.util.Objects; + +/** + * An {@link EdgeRole} represents the connection between an {@link EdgeType} and a {@link VertexType} in a + * {@link GraphSchema}. It wraps a {@code from} or {@code to} edge in the schema graph, providing access to the + * connected {@link VertexType} and any constraint properties (e.g. multiplicity) stored on that edge. + * <p/> + * An {@code EdgeRole} is an in-memory object, not a vertex in the schema graph. It is a view over the underlying + * schema edge. + */ +public class EdgeRole { + + private final Edge edge; + + /** + * Create an {@link EdgeRole} wrapping the given schema edge. + * + * @param edge the {@code from} or {@code to} edge in the schema graph + */ + public EdgeRole(final Edge edge) { + this.edge = edge; + } + + /** + * Get the {@link VertexType} that this role connects to. + * + * @return the target vertex type + */ + public VertexType vertexType() { + return (VertexType) edge.inVertex(); + } + + /** + * Get the underlying schema edge, which may carry constraint properties such as multiplicity. + * + * @return the schema edge + */ + public Edge edge() { + return edge; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof EdgeRole)) return false; + return Objects.equals(this.edge, ((EdgeRole) o).edge); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.edge); + } + + @Override + public String toString() { + return "EdgeRole{" + edge.label() + " -> " + vertexType().label() + "}"; + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/EdgeType.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/EdgeType.java new file mode 100644 index 0000000000..3bda1b1389 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/EdgeType.java @@ -0,0 +1,122 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.structure; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Optional; + +/** + * An {@link EdgeType} is a {@link Vertex} in a {@link GraphSchema} that represents an edge type definition. + * The label of this vertex corresponds to the edge type name it defines (e.g. an {@code EdgeType} with label "knows" + * defines the schema for "knows" edges in the data graph). + * <p/> + * Connectivity constraints are expressed via {@link GraphSchema.Tokens#FROM} edges (to the source {@link VertexType}) + * and {@link GraphSchema.Tokens#TO} edges (to the target {@link VertexType}). Each such edge is represented as an + * {@link EdgeRole} which may carry its own constraints (e.g. multiplicity) as properties on the schema edge. + * <p/> + * Property definitions for this type are connected via {@link GraphSchema.Tokens#HAS_PROPERTY} edges to + * {@link PropertyDefinition} vertices. + */ +public interface EdgeType extends Vertex { + + /** + * The default label to use for an edge type vertex in the schema graph. + */ + public static final String DEFAULT_LABEL = "edgeType"; + + /** + * Get the {@link PropertyDefinition} vertices associated with this edge type via + * {@link GraphSchema.Tokens#HAS_PROPERTY} edges. + * + * @param propertyKeys the keys of the property definitions to retrieve. If no keys are provided, + * return all property definitions. + * @return an {@link Iterator} of property definitions for this edge type + */ + public Iterator<PropertyDefinition> propertyDefinitions(final String... propertyKeys); + + /** + * Get the {@link EdgeRole} objects representing the source vertex type connections for this edge type. + * These correspond to outgoing {@link GraphSchema.Tokens#FROM} edges from this vertex. + * + * @return an {@link Iterator} of edge roles for the source connections + */ + public Iterator<EdgeRole> from(); + + /** + * Get the {@link EdgeRole} objects representing the target vertex type connections for this edge type. + * These correspond to outgoing {@link GraphSchema.Tokens#TO} edges from this vertex. + * + * @return an {@link Iterator} of edge roles for the target connections + */ + public Iterator<EdgeRole> to(); + + /** + * Get the source {@link VertexType} vertices connected via {@link GraphSchema.Tokens#FROM} edges. + * + * @return a collection of source vertex types + */ + public default Collection<VertexType> fromVertexTypes() { + final Collection<VertexType> result = new ArrayList<>(); + from().forEachRemaining(r -> result.add(r.vertexType())); + return result; + } + + /** + * Get the target {@link VertexType} vertices connected via {@link GraphSchema.Tokens#TO} edges. + * + * @return a collection of target vertex types + */ + public default Collection<VertexType> toVertexTypes() { + final Collection<VertexType> result = new ArrayList<>(); + to().forEachRemaining(r -> result.add(r.vertexType())); + return result; + } + + /** + * Get all {@link EdgeRole} objects for this edge type (both {@code from} and {@code to}). + * + * @return a collection of all edge roles + */ + public default Collection<EdgeRole> edgeRoles() { + final Collection<EdgeRole> result = new ArrayList<>(); + from().forEachRemaining(result::add); + to().forEachRemaining(result::add); + return result; + } + + /** + * Look up a specific {@link EdgeRole} by vertex type and direction. + * + * @param vertexType the vertex type to match + * @param direction {@link Direction#OUT} for a {@code from} role, {@link Direction#IN} for a {@code to} role + * @return an {@link Optional} containing the matching edge role, or empty if not found + */ + public default Optional<EdgeRole> edgeRole(final VertexType vertexType, final Direction direction) { + if (direction == Direction.BOTH) + throw new IllegalArgumentException("Direction.BOTH is not supported for edgeRole() lookup"); + final Iterator<EdgeRole> roles = direction == Direction.OUT ? from() : to(); + while (roles.hasNext()) { + final EdgeRole role = roles.next(); + if (role.vertexType().equals(vertexType)) return Optional.of(role); + } + return Optional.empty(); + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java index 3f9f246297..d4d1c06997 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java @@ -345,6 +345,17 @@ public interface Graph extends AutoCloseable, Host { */ default ServiceRegistry getServiceRegistry() { return ServiceRegistry.EMPTY; } + /** + * Get the {@link GraphSchema} associated with this graph. The schema graph describes the types, + * property definitions, and constraints that govern the structure of this graph. + * + * @return the schema graph + * @throws UnsupportedOperationException if the graph does not support schema + */ + default GraphSchema schema() { + throw Graph.Exceptions.schemaNotSupported(); + } + /** * Graph variables are a set of key/value pairs associated with the graph. The keys are String and the values * are Objects. @@ -469,6 +480,7 @@ public interface Graph extends AutoCloseable, Host { String FEATURE_IO_WRITE = "IoWrite"; String FEATURE_ORDERABILITY_SEMANTICS = "OrderabilitySemantics"; String FEATURE_SERVICE_CALL = "ServiceCall"; + String FEATURE_SCHEMA = "Schema"; /** * Determines if the {@code Graph} implementation supports {@link GraphComputer} based processing. @@ -555,6 +567,14 @@ public interface Graph extends AutoCloseable, Host { return false; } + /** + * Determines if the {@code Graph} implementation supports schema definitions. + */ + @FeatureDescriptor(name = FEATURE_SCHEMA) + default boolean supportsSchema() { + return false; + } + /** * Gets the features related to "graph sideEffects" operation. */ @@ -1237,6 +1257,10 @@ public interface Graph extends AutoCloseable, Host { return new UnsupportedOperationException("Graph does not support graph computer"); } + public static UnsupportedOperationException schemaNotSupported() { + return new UnsupportedOperationException("Graph does not support schema"); + } + public static IllegalArgumentException graphDoesNotSupportProvidedGraphComputer(final Class graphComputerClass) { return new IllegalArgumentException("Graph does not support the provided graph computer: " + graphComputerClass.getSimpleName()); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphSchema.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphSchema.java new file mode 100644 index 0000000000..7753d4e5e1 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphSchema.java @@ -0,0 +1,118 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.structure; + +import java.util.Iterator; +import java.util.Optional; + +/** + * A {@link GraphSchema} is a {@link Graph} that describes the types, property definitions, and constraints governing + * the structure of a data graph. Because it extends {@code Graph}, the schema is itself traversable with Gremlin. + * <p/> + * Type definitions ({@link VertexType}, {@link EdgeType}, {@link PropertyDefinition}) are vertices in the schema + * graph, connected by well-known edge labels defined in {@link Tokens}. + */ +public interface GraphSchema extends Graph { + + /** + * Get the {@link VertexType} definitions in this schema graph. If labels are provided, only return vertex types + * matching those labels. If no labels are provided, return all vertex types. + * + * @param labels the labels of the vertex types to retrieve + * @return an {@link Iterator} of vertex types matching the provided labels + */ + public Iterator<VertexType> vertexTypes(final String... labels); + + /** + * Get a single {@link VertexType} by label. + * + * @param label the vertex type label + * @return an {@link Optional} containing the vertex type, or empty if not found + */ + public default Optional<VertexType> vertexType(final String label) { + final Iterator<VertexType> iter = vertexTypes(label); + return iter.hasNext() ? Optional.of(iter.next()) : Optional.empty(); + } + + /** + * Get the {@link EdgeType} definitions in this schema graph. If labels are provided, only return edge types + * matching those labels. If no labels are provided, return all edge types. + * + * @param labels the labels of the edge types to retrieve + * @return an {@link Iterator} of edge types matching the provided labels + */ + public Iterator<EdgeType> edgeTypes(final String... labels); + + /** + * Get a single {@link EdgeType} by label. + * + * @param label the edge type label + * @return an {@link Optional} containing the edge type, or empty if not found + */ + public default Optional<EdgeType> edgeType(final String label) { + final Iterator<EdgeType> iter = edgeTypes(label); + return iter.hasNext() ? Optional.of(iter.next()) : Optional.empty(); + } + + /** + * Add a {@link VertexType} to this schema graph with the given label. + * + * @param label the type name for the vertex type + * @return the newly created vertex type + */ + public VertexType addVertexType(final String label); + + /** + * Add an {@link EdgeType} to this schema graph with the given label. + * + * @param label the type name for the edge type + * @return the newly created edge type + */ + public EdgeType addEdgeType(final String label); + + /** + * Add a {@link PropertyDefinition} to this schema graph with the given name. + * + * @param name the property name + * @return the newly created property definition + */ + public PropertyDefinition addPropertyDefinition(final String name); + + /** + * Well-known edge labels and property keys used to connect and describe schema elements. + */ + public static final class Tokens { + private Tokens() {} + + /** + * Edge label connecting a type vertex to its {@link PropertyDefinition} vertices. + */ + public static final String HAS_PROPERTY = "hasProperty"; + + /** + * Edge label connecting an {@link EdgeType} vertex to its source {@link VertexType} vertex. + */ + public static final String FROM = "from"; + + /** + * Edge label connecting an {@link EdgeType} vertex to its target {@link VertexType} vertex. + */ + public static final String TO = "to"; + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyDefinition.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyDefinition.java new file mode 100644 index 0000000000..707d24b63c --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyDefinition.java @@ -0,0 +1,35 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.structure; + +/** + * A {@link PropertyDefinition} is a {@link Vertex} in a {@link GraphSchema} that represents a property definition. + * It is connected to its parent type vertex (e.g. {@link VertexType}) via a {@link GraphSchema.Tokens#HAS_PROPERTY} + * edge. + * <p/> + * Constraints such as data type, required, and cardinality are stored as regular properties on this vertex and are + * accessed via the standard {@link Vertex#property(String)} and {@link Vertex#properties(String...)} methods. + */ +public interface PropertyDefinition extends Vertex { + + /** + * The default label to use for a property definition. + */ + public static final String DEFAULT_LABEL = "propertyDefinition"; +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexType.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexType.java new file mode 100644 index 0000000000..87ca6d3376 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexType.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.structure; + +import java.util.Iterator; + +/** + * A {@link VertexType} is a {@link Vertex} in a {@link GraphSchema} that represents a vertex type definition. + * The label of this vertex corresponds to the type name it defines (e.g. a {@code VertexType} with label "person" + * defines the schema for person vertices in the data graph). + * <p/> + * Property definitions for this type are connected via {@link GraphSchema.Tokens#HAS_PROPERTY} edges to + * {@link PropertyDefinition} vertices. + */ +public interface VertexType extends Vertex { + + /** + * The default label to use for a vertex type. + */ + public static final String DEFAULT_LABEL = "vertexType"; + + /** + * Get the {@link PropertyDefinition} vertices associated with this vertex type via + * {@link GraphSchema.Tokens#HAS_PROPERTY} edges. + * + * @param propertyKeys the keys of the property definitions to retrieve. If no keys are provided, + * return all property definitions. + * @return an {@link Iterator} of property definitions for this vertex type + */ + public Iterator<PropertyDefinition> propertyDefinitions(final String... propertyKeys); +} diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdgeType.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdgeType.java new file mode 100644 index 0000000000..34ef9e64fb --- /dev/null +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdgeType.java @@ -0,0 +1,66 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.tinkergraph.structure; + +import org.apache.tinkerpop.gremlin.structure.Direction; +import org.apache.tinkerpop.gremlin.structure.EdgeRole; +import org.apache.tinkerpop.gremlin.structure.EdgeType; +import org.apache.tinkerpop.gremlin.structure.GraphSchema; +import org.apache.tinkerpop.gremlin.structure.PropertyDefinition; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; + +import java.util.Iterator; + +/** + * Reference implementation of {@link EdgeType} backed by a {@link TinkerVertex}. The label of this vertex + * corresponds to the edge type name it defines in the data graph. + */ +public class TinkerEdgeType extends TinkerVertex implements EdgeType { + + protected TinkerEdgeType(final Object id, final String label, final AbstractTinkerGraph graph) { + super(id, label, graph); + } + + @Override + public Iterator<PropertyDefinition> propertyDefinitions(final String... propertyKeys) { + final Iterator<Vertex> adjacent = this.vertices(Direction.OUT, GraphSchema.Tokens.HAS_PROPERTY); + if (propertyKeys.length == 0) { + return IteratorUtils.map(adjacent, v -> (PropertyDefinition) v); + } + return IteratorUtils.map( + IteratorUtils.filter(adjacent, v -> { + for (final String key : propertyKeys) { + if (v.label().equals(key)) return true; + } + return false; + }), + v -> (PropertyDefinition) v); + } + + @Override + public Iterator<EdgeRole> from() { + return IteratorUtils.map(this.edges(Direction.OUT, GraphSchema.Tokens.FROM), EdgeRole::new); + } + + @Override + public Iterator<EdgeRole> to() { + return IteratorUtils.map(this.edges(Direction.OUT, GraphSchema.Tokens.TO), EdgeRole::new); + } +} diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java index 28f3120e3b..6334c74bb4 100644 --- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java @@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Element; import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.GraphSchema; import org.apache.tinkerpop.gremlin.structure.Transaction; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; @@ -70,6 +71,7 @@ public class TinkerGraph extends AbstractTinkerGraph { }}; private final TinkerGraphFeatures features = new TinkerGraphFeatures(); + private TinkerGraphSchema schema; protected Map<Object, Vertex> vertices = new ConcurrentHashMap<>(); protected Map<Object, Edge> edges = new ConcurrentHashMap<>(); @@ -302,6 +304,22 @@ public class TinkerGraph extends AbstractTinkerGraph { edges.add(edge); } + @Override + public GraphSchema schema() { + if (null == this.schema) { + this.schema = new TinkerGraphSchema(EMPTY_CONFIGURATION); + } + return this.schema; + } + + @Override + public void close() { + if (null != this.schema) { + this.schema.close(); + } + super.close(); + } + /** * Return TinkerGraph feature set. * <p/> @@ -369,6 +387,11 @@ public class TinkerGraph extends AbstractTinkerGraph { return true; } + @Override + public boolean supportsSchema() { + return true; + } + } diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchema.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchema.java new file mode 100644 index 0000000000..d0e57ba9c0 --- /dev/null +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchema.java @@ -0,0 +1,117 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.tinkergraph.structure; + +import org.apache.commons.configuration2.BaseConfiguration; +import org.apache.commons.configuration2.Configuration; +import org.apache.tinkerpop.gremlin.structure.EdgeType; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.GraphSchema; +import org.apache.tinkerpop.gremlin.structure.PropertyDefinition; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.VertexType; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; + +import java.util.Iterator; + +/** + * Reference implementation of {@link GraphSchema} backed by a {@link TinkerGraph}. The schema is itself a graph + * whose vertices represent type definitions ({@link TinkerVertexType}, {@link TinkerEdgeType}) and property + * definitions ({@link TinkerPropertyDefinition}). + */ +public class TinkerGraphSchema extends TinkerGraph implements GraphSchema { + + private static final Configuration EMPTY_CONFIGURATION = new BaseConfiguration() {{ + this.setProperty(Graph.GRAPH, TinkerGraphSchema.class.getName()); + }}; + + TinkerGraphSchema(final Configuration configuration) { + super(configuration); + } + + @Override + public GraphSchema schema() { + throw new UnsupportedOperationException("A schema graph does not have a nested schema"); + } + + @Override + public Iterator<VertexType> vertexTypes(final String... labels) { + final Iterator<Vertex> allVertices = this.vertices(); + return IteratorUtils.map( + IteratorUtils.filter(allVertices, v -> { + if (!(v instanceof VertexType)) return false; + if (labels.length == 0) return true; + for (final String label : labels) { + if (v.label().equals(label)) return true; + } + return false; + }), + v -> (VertexType) v); + } + + @Override + public Iterator<EdgeType> edgeTypes(final String... labels) { + final Iterator<Vertex> allVertices = this.vertices(); + return IteratorUtils.map( + IteratorUtils.filter(allVertices, v -> { + if (!(v instanceof EdgeType)) return false; + if (labels.length == 0) return true; + for (final String label : labels) { + if (v.label().equals(label)) return true; + } + return false; + }), + v -> (EdgeType) v); + } + + @Override + protected TinkerVertex createTinkerVertex(final Object id, final String label, final AbstractTinkerGraph graph) { + if (PropertyDefinition.DEFAULT_LABEL.equals(label)) { + return new TinkerPropertyDefinition(id, label, graph); + } + if (EdgeType.DEFAULT_LABEL.equals(label)) { + return new TinkerEdgeType(id, label, graph); + } + return new TinkerVertexType(id, label, graph); + } + + @Override + public VertexType addVertexType(final String label) { + final Object id = vertexIdManager.getNextId(this); + final TinkerVertexType vt = new TinkerVertexType(id, label, this); + this.vertices.put(vt.id(), vt); + return vt; + } + + @Override + public EdgeType addEdgeType(final String label) { + final Object id = vertexIdManager.getNextId(this); + final TinkerEdgeType et = new TinkerEdgeType(id, label, this); + this.vertices.put(et.id(), et); + return et; + } + + @Override + public PropertyDefinition addPropertyDefinition(final String name) { + final Object id = vertexIdManager.getNextId(this); + final TinkerPropertyDefinition pd = new TinkerPropertyDefinition(id, name, this); + this.vertices.put(pd.id(), pd); + return pd; + } +} diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerPropertyDefinition.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerPropertyDefinition.java new file mode 100644 index 0000000000..f4c3910f13 --- /dev/null +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerPropertyDefinition.java @@ -0,0 +1,32 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.tinkergraph.structure; + +import org.apache.tinkerpop.gremlin.structure.PropertyDefinition; + +/** + * Reference implementation of {@link PropertyDefinition} backed by a {@link TinkerVertex}. Constraints such as + * data type and cardinality are stored as regular vertex properties. + */ +public class TinkerPropertyDefinition extends TinkerVertex implements PropertyDefinition { + + protected TinkerPropertyDefinition(final Object id, final String label, final AbstractTinkerGraph graph) { + super(id, label, graph); + } +} diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexType.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexType.java new file mode 100644 index 0000000000..05373bb5df --- /dev/null +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexType.java @@ -0,0 +1,55 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.tinkergraph.structure; + +import org.apache.tinkerpop.gremlin.structure.Direction; +import org.apache.tinkerpop.gremlin.structure.GraphSchema; +import org.apache.tinkerpop.gremlin.structure.PropertyDefinition; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.VertexType; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; + +import java.util.Iterator; + +/** + * Reference implementation of {@link VertexType} backed by a {@link TinkerVertex}. The label of this vertex + * corresponds to the type name it defines in the data graph. + */ +public class TinkerVertexType extends TinkerVertex implements VertexType { + + protected TinkerVertexType(final Object id, final String label, final AbstractTinkerGraph graph) { + super(id, label, graph); + } + + @Override + public Iterator<PropertyDefinition> propertyDefinitions(final String... propertyKeys) { + final Iterator<Vertex> adjacent = this.vertices(Direction.OUT, GraphSchema.Tokens.HAS_PROPERTY); + if (propertyKeys.length == 0) { + return IteratorUtils.map(adjacent, v -> (PropertyDefinition) v); + } + return IteratorUtils.map( + IteratorUtils.filter(adjacent, v -> { + for (final String key : propertyKeys) { + if (v.label().equals(key)) return true; + } + return false; + }), + v -> (PropertyDefinition) v); + } +} diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchemaTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchemaTest.java new file mode 100644 index 0000000000..d4a7dacd12 --- /dev/null +++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchemaTest.java @@ -0,0 +1,371 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.tinkergraph.structure; + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.Direction; +import org.apache.tinkerpop.gremlin.structure.EdgeRole; +import org.apache.tinkerpop.gremlin.structure.EdgeType; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.GraphSchema; +import org.apache.tinkerpop.gremlin.structure.PropertyDefinition; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.VertexType; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; +import org.junit.Test; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class TinkerGraphSchemaTest { + + @Test + public void shouldReturnSchemaFromTinkerGraph() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + assertNotNull(schema); + assertThat(schema, instanceOf(TinkerGraphSchema.class)); + } + + @Test + public void shouldReturnSameSchemaInstance() { + final TinkerGraph graph = TinkerGraph.open(); + assertSame(graph.schema(), graph.schema()); + } + + @Test(expected = UnsupportedOperationException.class) + public void shouldPreventSchemaOfSchema() { + final TinkerGraph graph = TinkerGraph.open(); + graph.schema().schema(); + } + + @Test + public void shouldCreateVertexType() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + final Vertex v = schema.addVertex(T.label, "person"); + assertThat(v, instanceOf(TinkerVertexType.class)); + assertThat(v, instanceOf(VertexType.class)); + assertEquals("person", v.label()); + } + + @Test + public void shouldCreatePropertyDefinition() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + final Vertex v = schema.addVertex(T.label, PropertyDefinition.DEFAULT_LABEL); + assertThat(v, instanceOf(TinkerPropertyDefinition.class)); + assertThat(v, instanceOf(PropertyDefinition.class)); + } + + @Test + public void shouldRetrieveVertexTypesByLabel() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.addVertex(T.label, "person"); + schema.addVertex(T.label, "software"); + schema.addVertex(T.label, PropertyDefinition.DEFAULT_LABEL); + + final List<VertexType> types = IteratorUtils.list(schema.vertexTypes("person")); + assertEquals(1, types.size()); + assertEquals("person", types.get(0).label()); + } + + @Test + public void shouldRetrieveAllVertexTypes() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.addVertex(T.label, "person"); + schema.addVertex(T.label, "software"); + schema.addVertex(T.label, PropertyDefinition.DEFAULT_LABEL); + + final List<VertexType> types = IteratorUtils.list(schema.vertexTypes()); + assertEquals(2, types.size()); + } + + @Test + public void shouldConnectPropertyDefinitionsWithDefaultLabel() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + final VertexType personType = (VertexType) schema.addVertex(T.label, "person"); + final Vertex nameProp = schema.addVertex(T.label, PropertyDefinition.DEFAULT_LABEL); + final Vertex ageProp = schema.addVertex(T.label, PropertyDefinition.DEFAULT_LABEL); + personType.addEdge(GraphSchema.Tokens.HAS_PROPERTY, nameProp); + personType.addEdge(GraphSchema.Tokens.HAS_PROPERTY, ageProp); + + final List<PropertyDefinition> props = IteratorUtils.list(personType.propertyDefinitions()); + assertEquals(2, props.size()); + } + + @Test + public void shouldStoreConstraintsAsProperties() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + final Vertex nameProp = schema.addVertex(T.label, PropertyDefinition.DEFAULT_LABEL, + "type", String.class.getName(), "required", true); + + assertEquals(String.class.getName(), nameProp.property("type").value()); + assertThat((Boolean) nameProp.property("required").value(), is(true)); + } + + @Test + public void shouldBeTraversableWithGremlin() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + final Vertex personType = schema.addVertex(T.label, "person"); + final Vertex nameProp = schema.addVertex(T.label, PropertyDefinition.DEFAULT_LABEL, + "type", String.class.getName()); + personType.addEdge(GraphSchema.Tokens.HAS_PROPERTY, nameProp); + + final GraphTraversalSource g = schema.traversal(); + final List<Vertex> props = g.V().hasLabel("person") + .out(GraphSchema.Tokens.HAS_PROPERTY) + .toList(); + assertEquals(1, props.size()); + assertEquals(String.class.getName(), props.get(0).property("type").value()); + } + + @Test + public void shouldCreateEdgeType() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + final Vertex v = schema.addVertex(T.label, EdgeType.DEFAULT_LABEL); + assertThat(v, instanceOf(TinkerEdgeType.class)); + assertThat(v, instanceOf(EdgeType.class)); + } + + @Test + public void shouldRetrieveAllEdgeTypes() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.addVertex(T.label, "person"); + schema.addVertex(T.label, EdgeType.DEFAULT_LABEL); + schema.addVertex(T.label, EdgeType.DEFAULT_LABEL); + + final List<EdgeType> types = IteratorUtils.list(schema.edgeTypes()); + assertEquals(2, types.size()); + } + + @Test + public void shouldFilterEdgeTypesByLabel() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.addVertex(T.label, EdgeType.DEFAULT_LABEL); + schema.addVertex(T.label, "person"); + + final List<EdgeType> filtered = IteratorUtils.list(schema.edgeTypes(EdgeType.DEFAULT_LABEL)); + assertEquals(1, filtered.size()); + } + + @Test + public void shouldConnectEdgeTypeToVertexTypesViaFromAndTo() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + final VertexType personType = (VertexType) schema.addVertex(T.label, "person"); + final VertexType softwareType = (VertexType) schema.addVertex(T.label, "software"); + final EdgeType createdType = (EdgeType) schema.addVertex(T.label, EdgeType.DEFAULT_LABEL); + + createdType.addEdge(GraphSchema.Tokens.FROM, personType, "multiplicity", "0..*"); + createdType.addEdge(GraphSchema.Tokens.TO, softwareType, "multiplicity", "0..*"); + + final List<EdgeRole> fromRoles = IteratorUtils.list(createdType.from()); + assertEquals(1, fromRoles.size()); + assertEquals("person", fromRoles.get(0).vertexType().label()); + assertEquals("0..*", fromRoles.get(0).edge().property("multiplicity").value()); + + final List<EdgeRole> toRoles = IteratorUtils.list(createdType.to()); + assertEquals(1, toRoles.size()); + assertEquals("software", toRoles.get(0).vertexType().label()); + } + + @Test + public void shouldConnectPropertyDefinitionsToEdgeType() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + final EdgeType knowsType = (EdgeType) schema.addVertex(T.label, EdgeType.DEFAULT_LABEL); + final Vertex weightProp = schema.addVertex(T.label, PropertyDefinition.DEFAULT_LABEL, + "type", Double.class.getName()); + knowsType.addEdge(GraphSchema.Tokens.HAS_PROPERTY, weightProp); + + final List<PropertyDefinition> props = IteratorUtils.list(knowsType.propertyDefinitions()); + assertEquals(1, props.size()); + assertEquals(Double.class.getName(), props.get(0).property("type").value()); + } + + @Test + public void shouldTraverseEdgeTypeSchemaWithGremlin() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + final Vertex personType = schema.addVertex(T.label, "person"); + final Vertex knowsType = schema.addVertex(T.label, EdgeType.DEFAULT_LABEL); + knowsType.addEdge(GraphSchema.Tokens.FROM, personType); + knowsType.addEdge(GraphSchema.Tokens.TO, personType); + + final GraphTraversalSource g = schema.traversal(); + final List<Vertex> targets = g.V().hasLabel(EdgeType.DEFAULT_LABEL) + .out(GraphSchema.Tokens.TO) + .toList(); + assertEquals(1, targets.size()); + assertEquals("person", targets.get(0).label()); + } + + // ---- Convenience method tests (big.12) ---- + + @Test + public void shouldLookupVertexTypeByLabel() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.addVertexType("person"); + schema.addVertexType("software"); + + final Optional<VertexType> person = schema.vertexType("person"); + assertTrue(person.isPresent()); + assertEquals("person", ((Vertex) person.get()).label()); + + assertFalse(schema.vertexType("nonexistent").isPresent()); + } + + @Test + public void shouldLookupEdgeTypeByLabel() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.addEdgeType("knows"); + + final Optional<EdgeType> knows = schema.edgeType("knows"); + assertTrue(knows.isPresent()); + assertEquals("knows", ((Vertex) knows.get()).label()); + + assertFalse(schema.edgeType("nonexistent").isPresent()); + } + + @Test + public void shouldReturnFromAndToVertexTypes() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + final VertexType person = schema.addVertexType("person"); + final VertexType software = schema.addVertexType("software"); + final EdgeType created = schema.addEdgeType("created"); + ((Vertex) created).addEdge(GraphSchema.Tokens.FROM, (Vertex) person); + ((Vertex) created).addEdge(GraphSchema.Tokens.TO, (Vertex) software); + + final Collection<VertexType> fromTypes = created.fromVertexTypes(); + assertEquals(1, fromTypes.size()); + assertEquals("person", ((Vertex) fromTypes.iterator().next()).label()); + + final Collection<VertexType> toTypes = created.toVertexTypes(); + assertEquals(1, toTypes.size()); + assertEquals("software", ((Vertex) toTypes.iterator().next()).label()); + } + + @Test + public void shouldReturnAllEdgeRoles() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + final VertexType person = schema.addVertexType("person"); + final EdgeType knows = schema.addEdgeType("knows"); + ((Vertex) knows).addEdge(GraphSchema.Tokens.FROM, (Vertex) person); + ((Vertex) knows).addEdge(GraphSchema.Tokens.TO, (Vertex) person); + + final Collection<EdgeRole> roles = knows.edgeRoles(); + assertEquals(2, roles.size()); + } + + @Test + public void shouldLookupEdgeRoleByVertexTypeAndDirection() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + final VertexType person = schema.addVertexType("person"); + final VertexType software = schema.addVertexType("software"); + final EdgeType created = schema.addEdgeType("created"); + ((Vertex) created).addEdge(GraphSchema.Tokens.FROM, (Vertex) person); + ((Vertex) created).addEdge(GraphSchema.Tokens.TO, (Vertex) software); + + final Optional<EdgeRole> fromRole = created.edgeRole(person, Direction.OUT); + assertTrue(fromRole.isPresent()); + assertEquals("person", ((Vertex) fromRole.get().vertexType()).label()); + + final Optional<EdgeRole> toRole = created.edgeRole(software, Direction.IN); + assertTrue(toRole.isPresent()); + + assertFalse(created.edgeRole(software, Direction.OUT).isPresent()); + } + + // ---- Review fix tests ---- + + @Test(expected = IllegalArgumentException.class) + public void shouldRejectDirectionBothInEdgeRoleLookup() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + final VertexType person = schema.addVertexType("person"); + final EdgeType knows = schema.addEdgeType("knows"); + ((Vertex) knows).addEdge(GraphSchema.Tokens.FROM, (Vertex) person); + + knows.edgeRole(person, Direction.BOTH); + } + + @Test + public void shouldSupportSchemaFeature() { + final TinkerGraph graph = TinkerGraph.open(); + assertTrue(graph.features().graph().supportsSchema()); + } + + @Test + public void shouldIsolateSchemaFromDataGraph() { + final TinkerGraph graph = TinkerGraph.open(); + graph.addVertex(T.label, "person", "name", "marko"); + final GraphSchema schema = graph.schema(); + schema.addVertexType("person"); + + // data graph should have 1 vertex, schema graph should have 1 vertex + assertEquals(1, IteratorUtils.count(graph.vertices())); + assertEquals(1, IteratorUtils.count(schema.vertices())); + } + + @Test + public void shouldAllowDuplicateVertexTypeLabels() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.addVertexType("person"); + schema.addVertexType("person"); + + // both should exist — no uniqueness constraint at this level + assertEquals(2, IteratorUtils.count(schema.vertexTypes("person"))); + } +} diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchemaTraversalTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchemaTraversalTest.java new file mode 100644 index 0000000000..4d30423be4 --- /dev/null +++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphSchemaTraversalTest.java @@ -0,0 +1,169 @@ +/* + * 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. + */ +package org.apache.tinkerpop.gremlin.tinkergraph.structure; + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.Direction; +import org.apache.tinkerpop.gremlin.structure.EdgeType; +import org.apache.tinkerpop.gremlin.structure.GraphSchema; +import org.apache.tinkerpop.gremlin.structure.PropertyDefinition; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.VertexType; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.Assert.assertEquals; + +public class TinkerGraphSchemaTraversalTest { + + @Test + public void shouldAddVertexTypeViaTraversal() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + final Vertex v = schema.traversal().addVType("person").next(); + assertThat(v, instanceOf(VertexType.class)); + assertEquals("person", v.label()); + } + + @Test + public void shouldAddEdgeTypeViaTraversal() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + final Vertex v = schema.traversal().addEType("knows").next(); + assertThat(v, instanceOf(EdgeType.class)); + assertEquals("knows", v.label()); + } + + @Test + public void shouldAddEdgeTypeWithFromTo() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.addVertexType("person"); + schema.addVertexType("software"); + + final Vertex et = schema.traversal().addEType("created").from("person").to("software").next(); + assertThat(et, instanceOf(EdgeType.class)); + + final List<Vertex> fromVertices = IteratorUtils.list(et.vertices(Direction.OUT, GraphSchema.Tokens.FROM)); + assertEquals(1, fromVertices.size()); + assertEquals("person", fromVertices.get(0).label()); + + final List<Vertex> toVertices = IteratorUtils.list(et.vertices(Direction.OUT, GraphSchema.Tokens.TO)); + assertEquals(1, toVertices.size()); + assertEquals("software", toVertices.get(0).label()); + } + + @Test + public void shouldAddPropertyTypeToVertexType() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + final Vertex pd = schema.traversal().addVType("person").propertyType("name").next(); + assertThat(pd, instanceOf(PropertyDefinition.class)); + assertEquals("name", pd.label()); + + // verify the property definition is connected to the vertex type + final List<VertexType> types = IteratorUtils.list(schema.vertexTypes("person")); + assertEquals(1, types.size()); + final List<PropertyDefinition> props = IteratorUtils.list(types.get(0).propertyDefinitions()); + assertEquals(1, props.size()); + assertEquals("name", ((Vertex) props.get(0)).label()); + } + + @Test + public void shouldAddConstraintToPropertyType() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + final Vertex pd = schema.traversal().addVType("person") + .propertyType("name") + .constraint("type", String.class.getName()) + .constraint("required", true) + .next(); + + assertEquals(String.class.getName(), pd.property("type").value()); + assertEquals(true, pd.property("required").value()); + } + + @Test + public void shouldChainMultiplePropertyTypes() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.traversal().addVType("person") + .propertyType("name").constraint("type", String.class.getName()).constraint("required", true) + .propertyType("age").constraint("type", Integer.class.getName()) + .iterate(); + + final List<VertexType> types = IteratorUtils.list(schema.vertexTypes("person")); + assertEquals(1, types.size()); + final List<PropertyDefinition> props = IteratorUtils.list(types.get(0).propertyDefinitions()); + assertEquals(2, props.size()); + } + + @Test + public void shouldAddPropertyTypeToEdgeType() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.addVertexType("person"); + + schema.traversal().addEType("knows") + .from("person").to("person") + .propertyType("weight").constraint("type", Double.class.getName()) + .iterate(); + + final List<EdgeType> edgeTypes = IteratorUtils.list(schema.edgeTypes("knows")); + assertEquals(1, edgeTypes.size()); + final List<PropertyDefinition> props = IteratorUtils.list(edgeTypes.get(0).propertyDefinitions()); + assertEquals(1, props.size()); + assertEquals(Double.class.getName(), ((Vertex) props.get(0)).property("type").value()); + } + + @Test + public void shouldVerifySchemaGraphStructure() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + + schema.traversal().addVType("person") + .propertyType("name").constraint("type", String.class.getName()) + .propertyType("age").constraint("type", Integer.class.getName()) + .iterate(); + + // verify via regular Gremlin traversal on the schema graph + final GraphTraversalSource g = schema.traversal(); + final List<Vertex> propDefs = g.V().hasLabel("person") + .out(GraphSchema.Tokens.HAS_PROPERTY) + .toList(); + assertEquals(2, propDefs.size()); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailAddETypeWithMissingVertexType() { + final TinkerGraph graph = TinkerGraph.open(); + final GraphSchema schema = graph.schema(); + schema.traversal().addEType("knows").from("nonexistent").to("nonexistent").next(); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailAddVTypeOnNonSchemaGraph() { + final TinkerGraph graph = TinkerGraph.open(); + graph.traversal().addVType("person").next(); + } +}
