Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graph Features for Prism Interface #519

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
93e474e
Prepare statement processing for the introduction of graphs
May 16, 2024
bb40400
Improve code readability
May 16, 2024
cc9e6b6
Add basic graph execution
May 16, 2024
d92e9bc
Add relational graph results
May 16, 2024
585dbd6
Add graph results
May 16, 2024
eb63649
Add graph serialization tests
May 17, 2024
e536fed
Use mavenLocal for driver
May 17, 2024
c44d234
Add testcase for relational multimodel operations
May 17, 2024
28989ed
Add testcase for multi-model relational metadata
May 17, 2024
e89ad2a
Add test for multimodel mql
May 17, 2024
8933073
Fix ddl result bug
May 17, 2024
aad24ef
Rephrase error message
May 17, 2024
5cc1df7
Add cypher tests
May 17, 2024
a660672
Remove broken assertion
Jul 4, 2024
c1642f8
Adjust test
Jul 4, 2024
eb4b060
Fix node serialization
Jul 5, 2024
aa036e5
Add not implemented exception and todo for paths
Jul 18, 2024
5091b41
Update api version
Jul 18, 2024
0a86c9d
Put classes into corresponding packages
Jul 19, 2024
3bedb68
Fix method access
Jul 19, 2024
208d7c3
Update to new Prism API files
gartens Oct 4, 2024
7ce3517
Use switch expression
gartens Oct 4, 2024
c1635e6
Rename source/target to left/right
gartens Oct 4, 2024
7b00a38
Fix fallout from rebase
gartens Oct 9, 2024
038d96d
Fix instances missed in previous
gartens Oct 9, 2024
5a65a20
Improve switch expression
gartens Oct 9, 2024
badae33
Use .toList() instead of Collectors.toList()
gartens Oct 9, 2024
c748e29
Remove unused state and errorCode from PIServiceExceptions
gartens Oct 9, 2024
45ba7a9
Make getExecutor switch over the data models
gartens Oct 9, 2024
e4ca96e
Fix tests
gartens Oct 4, 2024
74d36ef
Make conditionals easier to read
gartens Oct 9, 2024
23b8eec
Format code & optimize imports
gartens Oct 9, 2024
24efde1
Use Map.of instead of new HashMap
gartens Oct 9, 2024
5e2d29e
Fix PrismUtilsTest
gartens Oct 10, 2024
83e9e11
Add back GEOMETRY
gartens Oct 10, 2024
634792b
Clean up after CypherTests
gartens Oct 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ private ImmutableList<ImmutableList<RexLiteral>> getEdgeValues( ImmutableList<Po
row.add( getStringLiteral( edge.id.value, ID_TYPE ) );
row.add( getStringLiteral( edge.labels.get( 0 ).value, LABEL_TYPE ) );

row.add( getStringLiteral( edge.source.value, ID_TYPE ) );
row.add( getStringLiteral( edge.target.value, ID_TYPE ) );
row.add( getStringLiteral( edge.left.value, ID_TYPE ) );
row.add( getStringLiteral( edge.right.value, ID_TYPE ) );
rows.add( row.build() );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ public AlgDataTypeFieldImpl( Long id, String name, int index, AlgDataType type )

public AlgDataTypeFieldImpl( Long id, String name, String physicalName, int index, AlgDataType type ) {
super( name, id, type );
assert name != null;
assert type != null;
this.id = id;
this.name = name;
this.index = index;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@ private static void edgePropertiesTableInsert( DataContext context, List<Functio
private static void edgeTableInsert( DataContext context, List<Function0<Enumerable<?>>> enumerables, int i, AlgDataType idType, AlgDataType labelType, PolyEdge edge ) {
context.addParameterValues( 0, idType, Collections.nCopies( edge.labels.size(), edge.id ) );
context.addParameterValues( 1, labelType, List.of( edge.labels.get( 0 ) ) );
context.addParameterValues( 2, idType, List.of( edge.source ) );
context.addParameterValues( 3, idType, List.of( edge.target ) );
context.addParameterValues( 2, idType, List.of( edge.left ) );
context.addParameterValues( 3, idType, List.of( edge.right ) );

// execute all inserts
drainInserts( enumerables.get( i ), edge.labels.size() );
Expand Down
38 changes: 19 additions & 19 deletions core/src/main/java/org/polypheny/db/type/entity/graph/PolyEdge.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@
public class PolyEdge extends GraphPropertyHolder {

@JsonProperty
public PolyString source;
public PolyString left;
@JsonProperty
public PolyString target;
public PolyString right;
@JsonProperty
public EdgeDirection direction;

Expand All @@ -67,25 +67,25 @@ public class PolyEdge extends GraphPropertyHolder {
public PolyEdge(
@NonNull PolyDictionary properties,
List<PolyString> labels,
PolyString source,
PolyString target,
PolyString left,
PolyString right,
EdgeDirection direction,
PolyString variableName ) {
this( PolyString.of( UUID.randomUUID().toString() ), properties, labels, source, target, direction, variableName );
this( PolyString.of( UUID.randomUUID().toString() ), properties, labels, left, right, direction, variableName );
}


public PolyEdge(
@JsonProperty("id") PolyString id,
@JsonProperty("properties") @NonNull PolyDictionary properties,
@JsonProperty("labels") List<PolyString> labels,
@JsonProperty("source") PolyString source,
@JsonProperty("target") PolyString target,
@JsonProperty("source") PolyString left,
@JsonProperty("target") PolyString right,
@JsonProperty("direction") EdgeDirection direction,
@JsonProperty("variableName") PolyString variableName ) {
super( id, PolyType.EDGE, properties, labels, variableName );
this.source = source;
this.target = target;
this.left = left;
this.right = right;
this.direction = direction;
}

Expand All @@ -99,7 +99,7 @@ public int getVariants() {


public PolyEdge from( PolyString left, PolyString right ) {
return new PolyEdge( id, properties, labels, left == null ? this.source : left, right == null ? this.target : right, direction, null );
return new PolyEdge( id, properties, labels, left == null ? this.left : left, right == null ? this.right : right, direction, null );
}


Expand All @@ -120,7 +120,7 @@ public PolyEdge copyNamed( PolyString newName ) {
// no copy needed
return this;
}
return new PolyEdge( id, properties, labels, source, target, direction, newName );
return new PolyEdge( id, properties, labels, left, right, direction, newName );
}


Expand Down Expand Up @@ -182,8 +182,8 @@ public Expression asExpression() {
id.asExpression(),
properties.asExpression(),
labels.asExpression(),
source.asExpression(),
target.asExpression(),
left.asExpression(),
right.asExpression(),
Expressions.constant( direction ),
getVariableName() == null ? Expressions.constant( null ) : getVariableName().asExpression() ),
PolyEdge.class );
Expand All @@ -200,8 +200,8 @@ public String toJson() {
return "{\"id\":" + id.toQuotedJson() +
", \"properties\":" + properties.toJson() +
", \"labels\":" + labels.toJson() +
", \"source\":" + source.toQuotedJson() +
", \"target\":" + target.toQuotedJson() +
", \"source\":" + left.toQuotedJson() +
", \"target\":" + right.toQuotedJson() +
", \"direction\":\"" + direction.name() + "\"" +
"}";
}
Expand Down Expand Up @@ -238,8 +238,8 @@ public String toString() {
"id=" + id +
", properties=" + properties +
", labels=" + labels +
", leftId=" + source +
", rightId=" + target +
", leftId=" + left +
", rightId=" + right +
", direction=" + direction +
'}';
}
Expand All @@ -259,8 +259,8 @@ public void encode( BinaryOutput out, PolyEdge item ) {
out.writeUTF8Nullable( item.variableName.value );
}
out.writeUTF8( item.direction.name() );
out.writeUTF8( item.source.value );
out.writeUTF8( item.target.value );
out.writeUTF8( item.left.value );
out.writeUTF8( item.right.value );
out.writeUTF8( item.properties.serialize() );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,24 +208,24 @@ private List<TreePart> buildMatchingTree( List<PolySegment> segments ) {
// for one only edges, which have matching nodes are considered,
// additionally only not used edges can be use, relationship isomorphism prohibits this
BiPredicate<PolyEdge, TreePart> filter =
segment.direction == EdgeDirection.LEFT_TO_RIGHT ? (( e, p ) -> !p.usedEdgesIds.contains( e.id ) && e.source.equals( p.targetId )) :
segment.direction == EdgeDirection.RIGHT_TO_LEFT ? (( e, p ) -> !p.usedEdgesIds.contains( e.id ) && e.target.equals( p.targetId )) :
(( e, p ) -> !p.usedEdgesIds.contains( e.id ) && (e.target.equals( p.targetId ) || e.source.equals( p.targetId )));
segment.direction == EdgeDirection.LEFT_TO_RIGHT ? (( e, p ) -> !p.usedEdgesIds.contains( e.id ) && e.left.equals( p.targetId )) :
segment.direction == EdgeDirection.RIGHT_TO_LEFT ? (( e, p ) -> !p.usedEdgesIds.contains( e.id ) && e.right.equals( p.targetId )) :
(( e, p ) -> !p.usedEdgesIds.contains( e.id ) && (e.right.equals( p.targetId ) || e.left.equals( p.targetId )));

for ( TreePart part : last ) {
// only loop matching connections
for ( PolyEdge edge : edges.values().stream().filter( e -> filter.test( e, part ) ).toList() ) {
PolyNode left = nodes.get( edge.source );
PolyNode right = nodes.get( edge.target );
PolyNode left = nodes.get( edge.left );
PolyNode right = nodes.get( edge.right );
// then check if it matches pattern of segment either ()->() or ()-() depending if direction is specified
if ( segment.direction == EdgeDirection.LEFT_TO_RIGHT || segment.direction == EdgeDirection.NONE ) {
if ( segment.matches( left, edge, right ) && !part.usedEdgesIds.contains( edge.id ) && part.targetId.equals( edge.source ) ) {
matches.add( new TreePart( part, edge.id, edge.target, segment.edge.getVariableName(), segment.target.getVariableName() ) );
if ( segment.matches( left, edge, right ) && !part.usedEdgesIds.contains( edge.id ) && part.targetId.equals( edge.left ) ) {
matches.add( new TreePart( part, edge.id, edge.right, segment.edge.getVariableName(), segment.target.getVariableName() ) );
}
}
if ( segment.direction == EdgeDirection.RIGHT_TO_LEFT || segment.direction == EdgeDirection.NONE ) {
if ( segment.matches( right, edge, left ) && !part.usedEdgesIds.contains( edge.id ) && part.targetId.equals( edge.target ) ) {
matches.add( new TreePart( part, edge.id, edge.source, segment.edge.getVariableName(), segment.target.getVariableName() ) );
if ( segment.matches( right, edge, left ) && !part.usedEdgesIds.contains( edge.id ) && part.targetId.equals( edge.right ) ) {
matches.add( new TreePart( part, edge.id, edge.left, segment.edge.getVariableName(), segment.target.getVariableName() ) );
}
}

Expand All @@ -248,18 +248,18 @@ private List<TreePart> buildMatchingTree( List<PolySegment> segments ) {
private void attachEmptyStubs( PolySegment segment, List<TreePart> root ) {
Set<Pair<PolyString, PolyString>> usedIds = new HashSet<>();
for ( PolyEdge edge : edges.values() ) {
PolyNode left = nodes.get( edge.source );
PolyNode right = nodes.get( edge.target );
PolyNode left = nodes.get( edge.left );
PolyNode right = nodes.get( edge.right );
// We attach stubs, which allows ()->() and ()-()
if ( segment.direction == EdgeDirection.LEFT_TO_RIGHT || segment.direction == EdgeDirection.NONE ) {
if ( segment.matches( left, edge, right ) ) {
usedIds.add( Pair.of( edge.source, segment.source.getVariableName() ) );
usedIds.add( Pair.of( edge.left, segment.source.getVariableName() ) );
}
}
// We attach stubs, which allows ()<-() and ()-() AKA inverted
if ( segment.direction == EdgeDirection.RIGHT_TO_LEFT || segment.direction == EdgeDirection.NONE ) {
if ( segment.matches( right, edge, left ) ) {
usedIds.add( Pair.of( edge.target, segment.source.getVariableName() ) );
usedIds.add( Pair.of( edge.right, segment.source.getVariableName() ) );
}
}
}
Expand Down
182 changes: 182 additions & 0 deletions dbms/src/test/java/org/polypheny/db/prisminterface/CypherTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright 2019-2024 The Polypheny Project
*
* Licensed 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.polypheny.db.prisminterface;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.polypheny.db.TestHelper.JdbcConnection;
import org.polypheny.jdbc.PolyConnection;
import org.polypheny.jdbc.multimodel.GraphResult;
import org.polypheny.jdbc.multimodel.PolyRow;
import org.polypheny.jdbc.multimodel.PolyStatement;
import org.polypheny.jdbc.multimodel.RelationalResult;
import org.polypheny.jdbc.types.PolyEdge;
import org.polypheny.jdbc.types.PolyGraphElement;
import org.polypheny.jdbc.types.PolyNode;

public class CypherTest {

@BeforeAll
public static void setup() throws SQLException {
try ( Connection connection = new JdbcConnection( true ).getConnection() ) {
if ( !connection.isWrapperFor( PolyConnection.class ) ) {
fail( "Driver must support unwrapping to PolyConnection" );
}
PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement();
polyStatement.execute( "public", "sql", "DROP NAMESPACE IF EXISTS cyphertest" );
polyStatement.execute( "public", "sql", "CREATE GRAPH NAMESPACE cyphertest" );
polyStatement.execute( "cyphertest", "cypher", "CREATE (:Person {id: 1, name: 'Alice'})" );
polyStatement.execute( "cyphertest", "cypher", "CREATE (:Person {id: 2, name: 'Bob'})" );
polyStatement.execute( "cyphertest", "cypher", "CREATE (:Person {id: 3, name: 'Charlie'})" );
polyStatement.execute( "cyphertest", "cypher", "MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}) CREATE (a)-[:KNOWS]->(b)" );
polyStatement.execute( "cyphertest", "cypher", "MATCH (b:Person {name: 'Bob'}), (c:Person {name: 'Charlie'}) CREATE (b)-[:KNOWS]->(c)" );
}
}


@AfterAll
public static void teardown() throws SQLException {
try ( Connection connection = new JdbcConnection( true ).getConnection() ) {
if ( !connection.isWrapperFor( PolyConnection.class ) ) {
fail( "Driver must support unwrapping to PolyConnection" );
}
PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement();
polyStatement.execute( "public", "sql", "DROP NAMESPACE IF EXISTS cyphertest" );
}
}


@Test
public void cypherSelectNodesTest() throws SQLException {
try ( Connection connection = new JdbcConnection( true ).getConnection() ) {
if ( !connection.isWrapperFor( PolyConnection.class ) ) {
fail( "Driver must support unwrapping to PolyConnection" );
}
PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement();
GraphResult result = polyStatement.execute( "cyphertest", "cypher", "MATCH (n:Person) RETURN n ORDER BY n.id" ).unwrap( GraphResult.class );
Iterator<PolyGraphElement> elements = result.iterator();

assertTrue( elements.hasNext() );
PolyGraphElement element = elements.next();
assertEquals( "Alice", element.get( "name" ).asString() );
assertEquals( 1, element.getLabels().size() );
assertEquals( "Person", element.getLabels().get( 0 ) );

assertTrue( elements.hasNext() );
element = elements.next();
assertEquals( "Bob", element.get( "name" ).asString() );
assertEquals( 1, element.getLabels().size() );
assertEquals( "Person", element.getLabels().get( 0 ) );

assertTrue( elements.hasNext() );
element = elements.next();
assertEquals( "Charlie", element.get( "name" ).asString() );
assertEquals( 1, element.getLabels().size() );
assertEquals( "Person", element.getLabels().get( 0 ) );

assertFalse( elements.hasNext() );
}
}


@Test
public void cypherSelectEdgesTest() throws SQLException {
try ( Connection connection = new JdbcConnection( true ).getConnection() ) {
if ( !connection.isWrapperFor( PolyConnection.class ) ) {
fail( "Driver must support unwrapping to PolyConnection" );
}
PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement();
GraphResult result = polyStatement.execute( "cyphertest", "cypher", "MATCH ()-[r:KNOWS]->() RETURN r" ).unwrap( GraphResult.class );
Iterator<PolyGraphElement> elements = result.iterator();

assertTrue( elements.hasNext() );
PolyGraphElement element = elements.next();
assertEquals( "KNOWS", element.getLabels().get( 0 ) );

assertTrue( elements.hasNext() );
element = elements.next();
assertEquals( "KNOWS", element.getLabels().get( 0 ) );

assertFalse( elements.hasNext() );
}
}


@Test
public void cypherSelectPathsTest() throws SQLException {
try ( Connection connection = new JdbcConnection( true ).getConnection() ) {
if ( !connection.isWrapperFor( PolyConnection.class ) ) {
fail( "Driver must support unwrapping to PolyConnection" );
}
PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement();
GraphResult result = polyStatement.execute( "cyphertest", "cypher", "MATCH p=(:Person {name: 'Alice'})-[:KNOWS*]->(:Person {name: 'Charlie'}) RETURN p" ).unwrap( GraphResult.class );
Iterator<PolyGraphElement> elements = result.iterator();

assertTrue( elements.hasNext() );
PolyNode alice = elements.next().unwrap( PolyNode.class );
assertEquals( alice.getLabels(), List.of( "Person" ) );
assertEquals( alice.get( "name" ).asString(), "Alice" );

PolyEdge knows = elements.next().unwrap( PolyEdge.class );
assertEquals( knows.getLabels(), List.of( "KNOWS" ) );

PolyNode bob = elements.next().unwrap( PolyNode.class );
assertEquals( bob.getLabels(), List.of( "Person" ) );
assertEquals( bob.get( "name" ).asString(), "Bob" );

knows = elements.next().unwrap( PolyEdge.class );
assertEquals( knows.getLabels(), List.of( "KNOWS" ) );

PolyNode charlie = elements.next().unwrap( PolyNode.class );
assertEquals( charlie.getLabels(), List.of( "Person" ) );
assertEquals( charlie.get( "name" ).asString(), "Charlie" );

assertFalse( elements.hasNext() );
}
}


@Test
public void cypherRelationalTest() throws SQLException {
try ( Connection connection = new JdbcConnection( true ).getConnection() ) {
if ( !connection.isWrapperFor( PolyConnection.class ) ) {
fail( "Driver must support unwrapping to PolyConnection" );
}
PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement();
RelationalResult result = polyStatement.execute( "cyphertest", "cypher", "MATCH (n:Person {name: 'Alice'}) RETURN n.name, n.id" ).unwrap( RelationalResult.class );
Iterator<PolyRow> rows = result.iterator();

assertTrue( rows.hasNext() );
PolyRow row = rows.next();
assertEquals( "Alice", row.get( "n.name" ).asString() );
assertEquals( "1", row.get( "n.id" ).asString() );

assertFalse( rows.hasNext() );
}
}

}
Loading
Loading