diff --git a/build.gradle b/build.gradle index dda3804..dfb926a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,5 @@ plugins { + id 'java' id 'java-library' id 'maven-publish' id 'signing' @@ -20,6 +21,7 @@ version = versionMajor + "." + versionMinor + (versionQualifier != '' ? "-" + ve repositories { mavenCentral() + mavenLocal() maven { url "https://plugins.gradle.org/m2/" } @@ -55,7 +57,7 @@ def protobufVersion = "3.23.4" dependencies { // Prism API files (protobuf files), needs to be implementation due to the prism-api-version.properties - implementation group: 'org.polypheny', name: 'prism', version: '1.9' + implementation group: 'org.polypheny', name: 'prism', version: '1.10' // Protobuf implementation group: 'com.google.protobuf', name: 'protobuf-java', version: protobufVersion diff --git a/src/main/java/org/polypheny/jdbc/PrismInterfaceClient.java b/src/main/java/org/polypheny/jdbc/PrismInterfaceClient.java index e075229..a46764b 100644 --- a/src/main/java/org/polypheny/jdbc/PrismInterfaceClient.java +++ b/src/main/java/org/polypheny/jdbc/PrismInterfaceClient.java @@ -47,6 +47,7 @@ import org.polypheny.prism.Entity; import org.polypheny.prism.ExecuteIndexedStatementBatchRequest; import org.polypheny.prism.ExecuteIndexedStatementRequest; +import org.polypheny.prism.ExecuteNamedStatementRequest; import org.polypheny.prism.ExecuteUnparameterizedStatementBatchRequest; import org.polypheny.prism.ExecuteUnparameterizedStatementRequest; import org.polypheny.prism.FetchRequest; @@ -54,6 +55,7 @@ import org.polypheny.prism.Function; import org.polypheny.prism.FunctionsRequest; import org.polypheny.prism.IndexedParameters; +import org.polypheny.prism.NamedParameters; import org.polypheny.prism.Namespace; import org.polypheny.prism.NamespacesRequest; import org.polypheny.prism.PrepareStatementRequest; @@ -145,15 +147,26 @@ public void unregister( int timeout ) throws PrismInterfaceServiceException { public void executeUnparameterizedStatement( String namespaceName, String languageName, String statement, CallbackQueue callback, int timeout ) throws PrismInterfaceServiceException { + ExecuteUnparameterizedStatementRequest request = buildExecuteUnparameterizedStatementRequest( statement, namespaceName, languageName ); + rpc.executeUnparameterizedStatement( request, callback ); // TODO timeout + } + + + public void executeUnparameterizedStatementBatch( List statements, String namespaceName, String languageName, CallbackQueue updateCallback, int timeout ) throws PrismInterfaceServiceException { + List requests = statements.stream().map( s -> buildExecuteUnparameterizedStatementRequest( s, namespaceName, languageName ) ).collect( Collectors.toList() ); + executeUnparameterizedStatementBatch( requests, updateCallback, timeout ); + } + + + public ExecuteUnparameterizedStatementRequest buildExecuteUnparameterizedStatementRequest( String statement, String namespaceName, String languageName ) { ExecuteUnparameterizedStatementRequest.Builder requestBuilder = ExecuteUnparameterizedStatementRequest.newBuilder(); if ( namespaceName != null ) { requestBuilder.setNamespaceName( namespaceName ); } - ExecuteUnparameterizedStatementRequest request = requestBuilder + return requestBuilder .setLanguageName( languageName ) .setStatement( statement ) .build(); - rpc.executeUnparameterizedStatement( request, callback ); // TODO timeout } @@ -179,6 +192,19 @@ public PreparedStatementSignature prepareIndexedStatement( String namespaceName, } + public PreparedStatementSignature prepareNamedStatement( String namespaceName, String languageName, String statement, int timeout ) throws PrismInterfaceServiceException { + PrepareStatementRequest.Builder requestBuilder = PrepareStatementRequest.newBuilder(); + if ( namespaceName != null ) { + requestBuilder.setNamespaceName( namespaceName ); + } + PrepareStatementRequest request = requestBuilder + .setStatement( statement ) + .setLanguageName( languageName ) + .build(); + return rpc.prepareNamedStatement( request, timeout ); + } + + public StatementResult executeIndexedStatement( int statementId, List values, int fetchSize, int timeout ) throws PrismInterfaceServiceException { IndexedParameters parameters = IndexedParameters.newBuilder() .addAllParameters( ProtoUtils.serializeParameterList( values ) ) @@ -193,6 +219,20 @@ public StatementResult executeIndexedStatement( int statementId, List values, int fetchSize, int timeout ) throws PrismInterfaceServiceException { + NamedParameters parameters = NamedParameters.newBuilder() + .putAllParameters( ProtoUtils.serializeParameterMap( values ) ) + .build(); + ExecuteNamedStatementRequest request = ExecuteNamedStatementRequest.newBuilder() + .setStatementId( statementId ) + .setParameters( parameters ) + .setFetchSize( fetchSize ) + .build(); + + return rpc.executeNamedStatement( request, timeout ); + } + + public StatementBatchResponse executeIndexedStatementBatch( int statementId, List> parameterBatch, int timeout ) throws PrismInterfaceServiceException { List parameters = parameterBatch.stream() .map( ProtoUtils::serializeParameterList ) diff --git a/src/main/java/org/polypheny/jdbc/RpcService.java b/src/main/java/org/polypheny/jdbc/RpcService.java index 17ed8d8..bdcec00 100644 --- a/src/main/java/org/polypheny/jdbc/RpcService.java +++ b/src/main/java/org/polypheny/jdbc/RpcService.java @@ -55,6 +55,7 @@ import org.polypheny.prism.EntitiesResponse; import org.polypheny.prism.ExecuteIndexedStatementBatchRequest; import org.polypheny.prism.ExecuteIndexedStatementRequest; +import org.polypheny.prism.ExecuteNamedStatementRequest; import org.polypheny.prism.ExecuteUnparameterizedStatementBatchRequest; import org.polypheny.prism.ExecuteUnparameterizedStatementRequest; import org.polypheny.prism.FetchRequest; @@ -90,25 +91,25 @@ public class RpcService { private final AtomicLong idCounter = new AtomicLong( 1 ); - private final Transport con; + private final Transport connection; private final Thread service; - private boolean closed = false; - private boolean disconnectSent = false; + private boolean isClosed = false; + private boolean hasSentDisconnect = false; private IOException error = null; private final Map> callbacks = new ConcurrentHashMap<>(); private final Map> callbackQueues = new ConcurrentHashMap<>(); - RpcService( Transport con ) { - this.con = con; + RpcService( Transport connection ) { + this.connection = connection; this.service = new Thread( this::readResponses, "PrismInterfaceResponseHandler" ); this.service.start(); } void close() { - closed = true; - con.close(); + isClosed = true; + connection.close(); try { service.join(); } catch ( InterruptedException e ) { @@ -131,15 +132,15 @@ private void sendMessage( Request req ) throws IOException { throw e; } } - if ( this.closed ) { + if ( this.isClosed ) { throw new IOException( "Connection is closed" ); } - con.sendMessage( req.toByteArray() ); + connection.sendMessage( req.toByteArray() ); } private Response receiveMessage() throws IOException { - return Response.parseFrom( con.receiveMessage() ); + return Response.parseFrom( connection.receiveMessage() ); } @@ -177,19 +178,19 @@ private void readResponses() { c.complete( resp ); } } catch ( EOFException | ClosedChannelException e ) { - this.closed = true; + this.isClosed = true; callbacks.forEach( ( id, c ) -> c.completeExceptionally( e ) ); callbackQueues.forEach( ( id, cq ) -> cq.onError( e ) ); } catch ( IOException e ) { // Communicate this to ProtoInterfaceClient - this.closed = true; + this.isClosed = true; callbacks.forEach( ( id, c ) -> c.completeExceptionally( e ) ); callbackQueues.forEach( ( id, cq ) -> cq.onError( e ) ); /* For Windows */ - if ( e.getMessage().contains( "An existing connection was forcibly closed by the remote host" ) && disconnectSent ) { + if ( e.getMessage().contains( "An existing connection was forcibly closed by the remote host" ) && hasSentDisconnect ) { return; } /* For Windows */ - if ( e instanceof SocketException && e.getMessage().contains( "Connection reset" ) && disconnectSent ) { + if ( e instanceof SocketException && e.getMessage().contains( "Connection reset" ) && hasSentDisconnect ) { return; } // This will cause the exception to be thrown when the next call is made @@ -197,7 +198,7 @@ private void readResponses() { this.error = e; throw new RuntimeException( e ); } catch ( Throwable t ) { - this.closed = true; + this.isClosed = true; callbacks.forEach( ( id, c ) -> c.completeExceptionally( t ) ); callbackQueues.forEach( ( id, cq ) -> cq.onError( t ) ); log.error( "Unhandled exception", t ); @@ -224,7 +225,7 @@ private Response completeSynchronously( Request.Builder req, int timeout ) throw CompletableFuture f = new CompletableFuture<>(); callbacks.put( req.getId(), f ); if ( req.getTypeCase() == TypeCase.DISCONNECT_REQUEST ) { - disconnectSent = true; + hasSentDisconnect = true; } sendMessage( req.build() ); Response resp = waitForCompletion( f, timeout ); @@ -424,6 +425,13 @@ PreparedStatementSignature prepareIndexedStatement( PrepareStatementRequest msg, } + public PreparedStatementSignature prepareNamedStatement( PrepareStatementRequest msg, int timeout ) throws PrismInterfaceServiceException { + Request.Builder req = newMessage(); + req.setPrepareIndexedStatementRequest( msg ); + return completeSynchronously( req, timeout ).getPreparedStatementSignature(); + } + + StatementResult executeIndexedStatement( ExecuteIndexedStatementRequest msg, int timeout ) throws PrismInterfaceServiceException { Request.Builder req = newMessage(); req.setExecuteIndexedStatementRequest( msg ); @@ -431,6 +439,13 @@ StatementResult executeIndexedStatement( ExecuteIndexedStatementRequest msg, int } + public StatementResult executeNamedStatement( ExecuteNamedStatementRequest msg, int timeout ) throws PrismInterfaceServiceException { + Request.Builder req = newMessage(); + req.setExecuteNamedStatementRequest( msg ); + return completeSynchronously( req, timeout ).getStatementResult(); + } + + StatementBatchResponse executeIndexedStatementBatch( ExecuteIndexedStatementBatchRequest msg, int timeout ) throws PrismInterfaceServiceException { Request.Builder req = newMessage(); req.setExecuteIndexedStatementBatchRequest( msg ); diff --git a/src/main/java/org/polypheny/jdbc/multimodel/GraphResult.java b/src/main/java/org/polypheny/jdbc/multimodel/GraphResult.java new file mode 100644 index 0000000..1e20423 --- /dev/null +++ b/src/main/java/org/polypheny/jdbc/multimodel/GraphResult.java @@ -0,0 +1,116 @@ +/* + * 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.jdbc.multimodel; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import org.polypheny.jdbc.PolyConnection; +import org.polypheny.jdbc.PrismInterfaceClient; +import org.polypheny.jdbc.PrismInterfaceErrors; +import org.polypheny.jdbc.PrismInterfaceServiceException; +import org.polypheny.jdbc.properties.PropertyUtils; +import org.polypheny.jdbc.types.PolyGraphElement; +import org.polypheny.prism.Frame; +import org.polypheny.prism.Frame.ResultCase; +import org.polypheny.prism.GraphFrame; + +public class GraphResult extends Result implements Iterable { // implements iterable over some graph representation + + private final PolyStatement polyStatement; + private boolean isFullyFetched; + private final List elements; + + + public GraphResult( Frame frame, PolyStatement polyStatement ) { + super( ResultType.GRAPH ); + this.polyStatement = polyStatement; + this.isFullyFetched = frame.getIsLast(); + this.elements = new ArrayList<>(); + addGraphElements( frame.getGraphFrame() ); + } + + + private void addGraphElements( GraphFrame graphFrame ) { + graphFrame.getElementList().forEach( n -> elements.add( PolyGraphElement.of( n ) ) ); + } + + + private void fetchMore() throws PrismInterfaceServiceException { + int id = polyStatement.getStatementId(); + int timeout = getPolyphenyConnection().getTimeout(); + Frame frame = getPrismInterfaceClient().fetchResult( id, timeout, PropertyUtils.getDEFAULT_FETCH_SIZE() ); + if ( frame.getResultCase() != ResultCase.GRAPH_FRAME ) { + throw new PrismInterfaceServiceException( + PrismInterfaceErrors.RESULT_TYPE_INVALID, + "Statement returned a result of illegal type " + frame.getResultCase() + ); + } + isFullyFetched = frame.getIsLast(); + addGraphElements( frame.getGraphFrame() ); + } + + + private PolyConnection getPolyphenyConnection() { + return polyStatement.getConnection(); + } + + + private PrismInterfaceClient getPrismInterfaceClient() { + return getPolyphenyConnection().getPrismInterfaceClient(); + } + + + @Override + public Iterator iterator() { + return new GraphElementIterator(); + } + + + class GraphElementIterator implements Iterator { + + int index = -1; + + + @Override + public boolean hasNext() { + if ( index + 1 >= elements.size() ) { + if ( isFullyFetched ) { + return false; + } + try { + fetchMore(); + } catch ( PrismInterfaceServiceException e ) { + throw new RuntimeException( e ); + } + } + return index + 1 < elements.size(); + } + + + @Override + public PolyGraphElement next() { + if ( !hasNext() ) { + throw new NoSuchElementException( "There are no more graph elements" ); + } + return elements.get( ++index ); + } + + } + +} diff --git a/src/main/java/org/polypheny/jdbc/multimodel/PolyRow.java b/src/main/java/org/polypheny/jdbc/multimodel/PolyRow.java index 5fda1ed..9883550 100644 --- a/src/main/java/org/polypheny/jdbc/multimodel/PolyRow.java +++ b/src/main/java/org/polypheny/jdbc/multimodel/PolyRow.java @@ -16,45 +16,42 @@ package org.polypheny.jdbc.multimodel; +import java.sql.SQLException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.polypheny.jdbc.PrismInterfaceErrors; +import org.polypheny.jdbc.PrismInterfaceServiceException; import org.polypheny.jdbc.types.TypedValue; import org.polypheny.prism.Row; -public class PolyRow { +public class PolyRow extends ArrayList { - List values; + private RelationalMetadata metadata; - public PolyRow( List value ) { - this.values = new ArrayList<>( value ); + public PolyRow( List value, RelationalMetadata metadata ) { + super( value ); + this.metadata = metadata; } - public PolyRow( TypedValue... value ) { - this( Arrays.asList( value ) ); + public TypedValue get( String columnName ) throws PrismInterfaceServiceException { + try { + int index = metadata.getColumnIndexFromLabel( columnName ); + return get( index ); + } catch ( SQLException e ) { + throw new PrismInterfaceServiceException( + PrismInterfaceErrors.VALUE_ILLEGAL, + "Failed to retrieve column bsaed on the column name.", + e + ); + } } - public int getColumnCount() { - return values.size(); - } - - - public TypedValue getValue( int columnIndex ) { - return values.get( columnIndex ); - } - - - public static PolyRow of( E... values ) { - return new PolyRow( values ); - } - - - public static PolyRow fromProto( Row protoRow ) { - return new PolyRow( protoRow.getValuesList().stream().map( TypedValue::new ).collect( Collectors.toList() ) ); + public static PolyRow fromProto( Row protoRow, RelationalMetadata metadata ) { + return new PolyRow( protoRow.getValuesList().stream().map( TypedValue::new ).collect( Collectors.toList() ), metadata ); } } diff --git a/src/main/java/org/polypheny/jdbc/multimodel/PolyStatement.java b/src/main/java/org/polypheny/jdbc/multimodel/PolyStatement.java index debf74a..50021b9 100644 --- a/src/main/java/org/polypheny/jdbc/multimodel/PolyStatement.java +++ b/src/main/java/org/polypheny/jdbc/multimodel/PolyStatement.java @@ -16,29 +16,41 @@ package org.polypheny.jdbc.multimodel; +import java.util.HashMap; +import java.util.List; import lombok.Getter; import org.polypheny.jdbc.PolyConnection; import org.polypheny.jdbc.PrismInterfaceClient; import org.polypheny.jdbc.PrismInterfaceErrors; import org.polypheny.jdbc.PrismInterfaceServiceException; +import org.polypheny.jdbc.properties.PropertyUtils; +import org.polypheny.jdbc.types.TypedValue; import org.polypheny.jdbc.utils.CallbackQueue; import org.polypheny.prism.Frame; +import org.polypheny.prism.PreparedStatementSignature; import org.polypheny.prism.Response; +import org.polypheny.prism.StatementBatchResponse; import org.polypheny.prism.StatementResponse; +import org.polypheny.prism.StatementResult; public class PolyStatement { - private static final long SCALAR_NOT_SET = -1; private static final int NO_STATEMENT_ID = -1; @Getter private PolyConnection connection; @Getter private int statementId; + @Getter + private boolean isPrepared; - private void resetStatement() { + private void resetStatement() throws PrismInterfaceServiceException { + if ( statementId != -1 ) { + connection.getPrismInterfaceClient().closeStatement( statementId, connection.getTimeout() ); + } statementId = NO_STATEMENT_ID; + isPrepared = false; } @@ -53,6 +65,8 @@ private Result getResultFromFrame( Frame frame ) throws PrismInterfaceServiceExc return new RelationalResult( frame, this ); case DOCUMENT_FRAME: return new DocumentResult( frame, this ); + case GRAPH_FRAME: + return new GraphResult( frame, this ); } throw new PrismInterfaceServiceException( PrismInterfaceErrors.RESULT_TYPE_INVALID, "Statement produced unknown result type" ); } @@ -94,4 +108,77 @@ public Result execute( String namespaceName, String languageName, String stateme } } + + public List execute( String namespaceName, String languageName, List statements ) throws PrismInterfaceServiceException, InterruptedException { + resetStatement(); + CallbackQueue callback = new CallbackQueue<>( Response::getStatementBatchResponse ); + int timeout = connection.getTimeout(); + getPrismInterfaceClient().executeUnparameterizedStatementBatch( statements, namespaceName, languageName, callback, timeout ); + while ( true ) { + StatementBatchResponse response = callback.takeNext(); + if ( statementId == NO_STATEMENT_ID ) { + statementId = response.getBatchId(); + } + if ( response.getScalarsCount() == 0 ) { + continue; + } + callback.awaitCompletion(); + return response.getScalarsList(); + } + } + + + public void prepare( String namespaceName, String languageName, String statement ) throws PrismInterfaceServiceException { + int timeout = connection.getTimeout(); + if ( statement.contains( "?" ) ) { + PreparedStatementSignature signature = getPrismInterfaceClient().prepareIndexedStatement( namespaceName, languageName, statement, timeout ); + statementId = signature.getStatementId(); + isPrepared = true; + return; + } + if ( statement.contains( ":" ) ) { + org.polypheny.prism.PreparedStatementSignature signature = connection.getPrismInterfaceClient().prepareNamedStatement( namespaceName, languageName, statement, timeout ); + statementId = signature.getStatementId(); + isPrepared = true; + return; + } + throw new PrismInterfaceServiceException( PrismInterfaceErrors.VALUE_ILLEGAL, "Statement must be either of the indexed or named parameterized kind." ); + } + + + public Result executePrepared( List parameters ) throws PrismInterfaceServiceException { + if ( !isPrepared ) { + throw new PrismInterfaceServiceException( PrismInterfaceErrors.OPERATION_ILLEGAL, "This operation requires a statmement to be prepared first" ); + } + int timeout = connection.getTimeout(); + StatementResult result = getPrismInterfaceClient().executeIndexedStatement( statementId, parameters, PropertyUtils.getDEFAULT_FETCH_SIZE(), timeout ); + if ( !result.hasFrame() ) { + return new ScalarResult( result.getScalar() ); + } + return getResultFromFrame( result.getFrame() ); + } + + + public Result executePrepared( HashMap parameters ) throws PrismInterfaceServiceException { + if ( !isPrepared ) { + throw new PrismInterfaceServiceException( PrismInterfaceErrors.OPERATION_ILLEGAL, "This operation requires a statmement to be prepared first" ); + } + int timeout = connection.getTimeout(); + StatementResult result = getPrismInterfaceClient().executeNamedStatement( statementId, parameters, PropertyUtils.getDEFAULT_FETCH_SIZE(), timeout ); + if ( !result.hasFrame() ) { + return new ScalarResult( result.getScalar() ); + } + return getResultFromFrame( result.getFrame() ); + } + + + public List executePreparedBatch( List> parameterBatch ) throws PrismInterfaceServiceException { + if ( !isPrepared ) { + throw new PrismInterfaceServiceException( PrismInterfaceErrors.OPERATION_ILLEGAL, "This operation requires a statmement to be prepared first" ); + } + int timeout = connection.getTimeout(); + StatementBatchResponse response = getPrismInterfaceClient().executeIndexedStatementBatch( statementId, parameterBatch, timeout ); + return response.getScalarsList(); + } + } diff --git a/src/main/java/org/polypheny/jdbc/multimodel/RelationalColumnMetadata.java b/src/main/java/org/polypheny/jdbc/multimodel/RelationalColumnMetadata.java new file mode 100644 index 0000000..f0004ef --- /dev/null +++ b/src/main/java/org/polypheny/jdbc/multimodel/RelationalColumnMetadata.java @@ -0,0 +1,47 @@ +/* + * 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.jdbc.multimodel; + +import lombok.Getter; +import org.polypheny.prism.ColumnMeta; + +@Getter +public class RelationalColumnMetadata { + + private final int columnIndex; + private final boolean isNullable; + private final int length; + private final String columnLabel; + private final String columnName; + private final int precision; + + private final String protocolTypeName; + private final int scale; + + + public RelationalColumnMetadata( ColumnMeta columnMeta ) { + this.columnIndex = columnMeta.getColumnIndex(); + this.isNullable = columnMeta.getIsNullable(); + this.length = columnMeta.getLength(); + this.columnLabel = columnMeta.getColumnLabel(); + this.columnName = columnMeta.getColumnName(); + this.precision = columnMeta.getPrecision(); + this.protocolTypeName = columnMeta.getTypeMeta().getProtoValueType().name(); + this.scale = columnMeta.getScale(); + } + +} diff --git a/src/main/java/org/polypheny/jdbc/multimodel/RelationalMetadata.java b/src/main/java/org/polypheny/jdbc/multimodel/RelationalMetadata.java new file mode 100644 index 0000000..f4fea83 --- /dev/null +++ b/src/main/java/org/polypheny/jdbc/multimodel/RelationalMetadata.java @@ -0,0 +1,61 @@ +/* + * 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.jdbc.multimodel; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.polypheny.jdbc.PrismInterfaceErrors; +import org.polypheny.jdbc.PrismInterfaceServiceException; +import org.polypheny.prism.ColumnMeta; + +public class RelationalMetadata { + + private List columnMetas; + private Map columnIndexes; + + + public RelationalMetadata( List columnMetadata ) { + this.columnMetas = columnMetadata.stream().map( RelationalColumnMetadata::new ).collect( Collectors.toList() ); + this.columnIndexes = this.columnMetas.stream().collect( Collectors.toMap( RelationalColumnMetadata::getColumnLabel, RelationalColumnMetadata::getColumnIndex, ( m, n ) -> n ) ); + + } + + + public RelationalColumnMetadata getColumnMeta( int columnIndex ) throws PrismInterfaceServiceException { + try { + return columnMetas.get( columnIndex ); + } catch ( IndexOutOfBoundsException e ) { + throw new PrismInterfaceServiceException( PrismInterfaceErrors.VALUE_ILLEGAL, "Column index out of bounds", e ); + } + } + + + public int getColumnIndexFromLabel( String columnLabel ) throws PrismInterfaceServiceException { + Integer columnIndex = columnIndexes.get( columnLabel ); + if ( columnIndex == null ) { + throw new PrismInterfaceServiceException( PrismInterfaceErrors.COLUMN_NOT_EXISTS, "Invalid column label: " + columnLabel ); + } + return columnIndex; + } + + + public int getColumnCount() { + return columnMetas.size(); + } + +} diff --git a/src/main/java/org/polypheny/jdbc/multimodel/RelationalResult.java b/src/main/java/org/polypheny/jdbc/multimodel/RelationalResult.java index 4198591..e1590c8 100644 --- a/src/main/java/org/polypheny/jdbc/multimodel/RelationalResult.java +++ b/src/main/java/org/polypheny/jdbc/multimodel/RelationalResult.java @@ -20,6 +20,7 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +import lombok.Getter; import org.polypheny.jdbc.PolyConnection; import org.polypheny.jdbc.PrismInterfaceClient; import org.polypheny.jdbc.PrismInterfaceErrors; @@ -32,6 +33,8 @@ public class RelationalResult extends Result implements Iterable { private final PolyStatement polyStatement; + @Getter + private final RelationalMetadata metadata; private final List rows; private boolean isFullyFetched; @@ -41,12 +44,13 @@ public RelationalResult( Frame frame, PolyStatement polyStatement ) throws Prism this.polyStatement = polyStatement; this.isFullyFetched = frame.getIsLast(); this.rows = new ArrayList<>(); + this.metadata = new RelationalMetadata( frame.getRelationalFrame().getColumnMetaList() ); addRows( frame.getRelationalFrame() ); } private void addRows( RelationalFrame relationalFrame ) { - relationalFrame.getRowsList().forEach( d -> rows.add( PolyRow.fromProto( d ) ) ); + relationalFrame.getRowsList().forEach( d -> rows.add( PolyRow.fromProto( d, metadata ) ) ); } @@ -54,7 +58,7 @@ private void fetchMore() throws PrismInterfaceServiceException { int id = polyStatement.getStatementId(); int timeout = getPolyphenyConnection().getTimeout(); Frame frame = getPrismInterfaceClient().fetchResult( id, timeout, PropertyUtils.getDEFAULT_FETCH_SIZE() ); - if ( frame.getResultCase() != ResultCase.DOCUMENT_FRAME ) { + if ( frame.getResultCase() != ResultCase.RELATIONAL_FRAME ) { throw new PrismInterfaceServiceException( PrismInterfaceErrors.RESULT_TYPE_INVALID, "Statement returned a result of illegal type " + frame.getResultCase() diff --git a/src/main/java/org/polypheny/jdbc/multimodel/Result.java b/src/main/java/org/polypheny/jdbc/multimodel/Result.java index eb9db43..3c6bd8e 100644 --- a/src/main/java/org/polypheny/jdbc/multimodel/Result.java +++ b/src/main/java/org/polypheny/jdbc/multimodel/Result.java @@ -42,6 +42,7 @@ public T unwrap( Class aClass ) throws PrismInterfaceServiceException { public enum ResultType { RELATIONAL, DOCUMENT, + GRAPH, SCALAR } diff --git a/src/main/java/org/polypheny/jdbc/types/PolyDocument.java b/src/main/java/org/polypheny/jdbc/types/PolyDocument.java index 7e1bfdd..0d899b8 100644 --- a/src/main/java/org/polypheny/jdbc/types/PolyDocument.java +++ b/src/main/java/org/polypheny/jdbc/types/PolyDocument.java @@ -33,11 +33,6 @@ public PolyDocument() { } - public PolyDocument( HashMap entries ) { - super( entries ); - } - - public PolyDocument( ProtoDocument document ) { super(); document.getEntriesList().stream() diff --git a/src/main/java/org/polypheny/jdbc/types/PolyEdge.java b/src/main/java/org/polypheny/jdbc/types/PolyEdge.java new file mode 100644 index 0000000..5716e7e --- /dev/null +++ b/src/main/java/org/polypheny/jdbc/types/PolyEdge.java @@ -0,0 +1,48 @@ +/* + * 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.jdbc.types; + +import lombok.Getter; +import org.polypheny.prism.ProtoEdge; + +@Getter +public class PolyEdge extends PolyGraphElement { + + private final String left; + private final String right; + private final EdgeDirection direction; + + + public PolyEdge( ProtoEdge protoEdge ) { + super(); + this.id = protoEdge.getId(); + this.name = protoEdge.getName(); + this.labels = protoEdge.getLabelsList(); + protoEdge.getPropertiesMap().forEach( ( k, v ) -> put( k, new TypedValue( v ) ) ); + this.left = protoEdge.getSource(); + this.right = protoEdge.getTarget(); + this.direction = EdgeDirection.valueOf( protoEdge.getDirection().name() ); + } + + + enum EdgeDirection { + LEFT_TO_RIGHT, + RIGHT_TO_LEFT, + NONE + } + +} diff --git a/src/main/java/org/polypheny/jdbc/types/PolyGraphElement.java b/src/main/java/org/polypheny/jdbc/types/PolyGraphElement.java new file mode 100644 index 0000000..2e1de89 --- /dev/null +++ b/src/main/java/org/polypheny/jdbc/types/PolyGraphElement.java @@ -0,0 +1,54 @@ +/* + * 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.jdbc.types; + + +import java.util.HashMap; +import java.util.List; +import lombok.Getter; +import org.polypheny.jdbc.PrismInterfaceErrors; +import org.polypheny.jdbc.PrismInterfaceServiceException; +import org.polypheny.prism.GraphElement; + +@Getter +public abstract class PolyGraphElement extends HashMap { + + protected String id; + protected String name; + protected List labels; + + + public T unwrap( Class aClass ) throws PrismInterfaceServiceException { + if ( aClass.isInstance( this ) ) { + return aClass.cast( this ); + } + throw new PrismInterfaceServiceException( PrismInterfaceErrors.WRAPPER_INCORRECT_TYPE, "Not a wrapper for " + aClass ); + } + + + public static PolyGraphElement of( GraphElement element ) { + switch ( element.getElementCase() ) { + case NODE: + return new PolyNode( element.getNode() ); + case EDGE: + return new PolyEdge( element.getEdge() ); + default: + throw new RuntimeException( "Unknown graph element of type " + element.getElementCase() ); + } + } + +} diff --git a/src/main/java/org/polypheny/jdbc/types/PolyNode.java b/src/main/java/org/polypheny/jdbc/types/PolyNode.java new file mode 100644 index 0000000..5fc9e9c --- /dev/null +++ b/src/main/java/org/polypheny/jdbc/types/PolyNode.java @@ -0,0 +1,33 @@ +/* + * 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.jdbc.types; + +import org.polypheny.prism.ProtoNode; +import org.polypheny.prism.ProtoValue.ValueCase; + +public class PolyNode extends PolyGraphElement { + + + public PolyNode( ProtoNode protoNode ) { + super(); + this.id = protoNode.getId(); + this.name = protoNode.getName(); + this.labels = protoNode.getLabelsList(); + protoNode.getPropertiesMap().forEach( ( k, v ) -> put( k, new TypedValue( v ) ) ); + } + +} diff --git a/src/main/java/org/polypheny/jdbc/types/TypedValue.java b/src/main/java/org/polypheny/jdbc/types/TypedValue.java index 550af23..09451c1 100644 --- a/src/main/java/org/polypheny/jdbc/types/TypedValue.java +++ b/src/main/java/org/polypheny/jdbc/types/TypedValue.java @@ -1277,7 +1277,6 @@ public ProtoValue serialize() throws SQLException { return serializeAsProtoFile(); case DOCUMENT: return serializeAsProtoDocument(); - } throw new PrismInterfaceServiceException( PrismInterfaceErrors.DATA_TYPE_MISMATCH, "Failed to serialize unknown type: " + valueCase.name() ); } @@ -1468,14 +1467,4 @@ private static PolyInterval getInterval( ProtoInterval interval ) { return new PolyInterval( interval.getMonths(), interval.getMilliseconds() ); } - - @Override - public String toString() { - try { - return asString(); - } catch ( SQLException e ) { - throw new RuntimeException( e ); - } - } - } diff --git a/src/main/java/org/polypheny/jdbc/utils/ProtoUtils.java b/src/main/java/org/polypheny/jdbc/utils/ProtoUtils.java index 7cfa640..ef1b5a0 100644 --- a/src/main/java/org/polypheny/jdbc/utils/ProtoUtils.java +++ b/src/main/java/org/polypheny/jdbc/utils/ProtoUtils.java @@ -18,6 +18,8 @@ import java.sql.SQLException; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.stream.Collectors; import org.polypheny.jdbc.types.TypedValue; import org.polypheny.prism.ProtoString; @@ -45,4 +47,18 @@ public static List serializeParameterList( List values ) } ).collect( Collectors.toList() ); } + + public static Map serializeParameterMap( Map values ) { + return values.entrySet().stream().collect( Collectors.toMap( + Entry::getKey, + value -> { + try { + return value.getValue().serialize(); + } catch ( Exception e ) { + throw new RuntimeException( "Serialization failed", e ); + } + } + ) ); + } + } diff --git a/src/test/java/org/polypheny/jdbc/ConnectionTest.java b/src/test/java/org/polypheny/jdbc/ConnectionTest.java index a36283e..2e9e74a 100644 --- a/src/test/java/org/polypheny/jdbc/ConnectionTest.java +++ b/src/test/java/org/polypheny/jdbc/ConnectionTest.java @@ -31,6 +31,7 @@ import java.util.Properties; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class ConnectionTest { @@ -106,28 +107,106 @@ void testClientProperties() throws SQLException { @Test - void testMetaData() throws SQLException { + void testMetaDataGetURL() throws SQLException { DatabaseMetaData meta = con.getMetaData(); meta.getURL(); + } + + + @Test + void testMetaDataGetDatabaseProductName() throws SQLException { + DatabaseMetaData meta = con.getMetaData(); meta.getDatabaseProductName(); + } + + + @Test + void testMetaDataGetCatalogs() throws SQLException { + DatabaseMetaData meta = con.getMetaData(); meta.getCatalogs(); + } + + + @Test + void testMetaDataGetTableTypes() throws SQLException { + DatabaseMetaData meta = con.getMetaData(); meta.getTableTypes(); + } + + + @Test + void testMetaDataGetTypeInfo() throws SQLException { + DatabaseMetaData meta = con.getMetaData(); meta.getTypeInfo(); + } + + + @Test + void testMetaDataGetColumns() throws SQLException { + DatabaseMetaData meta = con.getMetaData(); meta.getColumns( "public", ".*", ".*", ".*" ); + } + + + @Test + void testMetaDataGetStringFunctions() throws SQLException { + DatabaseMetaData meta = con.getMetaData(); meta.getStringFunctions(); + } + + + @Test + void testMetaDataGetSystemFunctions() throws SQLException { + DatabaseMetaData meta = con.getMetaData(); meta.getSystemFunctions(); + } + + + @Test + void testMetaDataGetTimeDateFunctions() throws SQLException { + DatabaseMetaData meta = con.getMetaData(); meta.getTimeDateFunctions(); + } + + + @Test + void testMetaDataGetNumericFunctions() throws SQLException { + DatabaseMetaData meta = con.getMetaData(); meta.getNumericFunctions(); + } + + + @Test + void testMetaDataGetSQLKeywords() throws SQLException { + DatabaseMetaData meta = con.getMetaData(); meta.getSQLKeywords(); } @Test - void testMetaDataNotStrict() throws SQLException { + void testMetaDataGetProceduresNotStrict() throws SQLException { try ( Connection con = DriverManager.getConnection( "jdbc:polypheny://127.0.0.1:20590?strict=false", "pa", "" ) ) { DatabaseMetaData meta = con.getMetaData(); meta.getProcedures( "public", ".*", ".*" ); + } + } + + + @Test + @Disabled + // This test fails due to syntax definitions missing for some of the functions on the server side (operator registry). + void testMetaDataGetFunctionsNotStrict() throws SQLException { + try ( Connection con = DriverManager.getConnection( "jdbc:polypheny://127.0.0.1:20590?strict=false", "pa", "" ) ) { + DatabaseMetaData meta = con.getMetaData(); meta.getFunctions( "public", ".*", ".*" ); + } + } + + + @Test + void testMetaDataGetSchemasNotStrict() throws SQLException { + try ( Connection con = DriverManager.getConnection( "jdbc:polypheny://127.0.0.1:20590?strict=false", "pa", "" ) ) { + DatabaseMetaData meta = con.getMetaData(); meta.getSchemas( "public", ".*" ); } } diff --git a/src/test/java/org/polypheny/jdbc/Demo.java b/src/test/java/org/polypheny/jdbc/Demo.java new file mode 100644 index 0000000..e796f68 --- /dev/null +++ b/src/test/java/org/polypheny/jdbc/Demo.java @@ -0,0 +1,205 @@ +/* + * 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.jdbc; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import org.polypheny.jdbc.multimodel.DocumentResult; +import org.polypheny.jdbc.multimodel.GraphResult; +import org.polypheny.jdbc.multimodel.PolyRow; +import org.polypheny.jdbc.multimodel.PolyStatement; +import org.polypheny.jdbc.multimodel.RelationalMetadata; +import org.polypheny.jdbc.multimodel.RelationalResult; +import org.polypheny.jdbc.multimodel.Result; +import org.polypheny.jdbc.multimodel.ScalarResult; +import org.polypheny.jdbc.types.PolyDocument; +import org.polypheny.jdbc.types.PolyGraphElement; +import org.polypheny.jdbc.types.TypedValue; + +public class Demo { + + private static String language = "sql"; + private static String namespace = "public"; + private static Connection con; + + + public static void main( String[] args ) { + try { + con = DriverManager.getConnection( "jdbc:polypheny://127.0.0.1:20590?strict=false", "pa", "" ); + if ( !con.isWrapperFor( PolyConnection.class ) ) { + System.out.println( "Driver must support unwrapping to PolyConnection" ); + return; + } + Scanner scanner = new Scanner( System.in ); + + while ( true ) { + printPrompt(); + String input = scanner.nextLine(); + + if ( input.startsWith( "lng " ) ) { + language = input.substring( 4 ).trim(); + System.out.println( "Language set to " + language ); + } else if ( input.startsWith( "ns " ) ) { + namespace = input.substring( 3 ).trim(); + System.out.println( "Namespace set to " + namespace ); + } else if ( input.equals( "exit" ) ) { + break; + } else { + if ( language == null || namespace == null ) { + System.out.println( "Please set both language and namespace before executing a statement." ); + continue; + } + String statement = input.trim(); + executeStatement( namespace, language, statement ); + } + } + + scanner.close(); + } catch ( Exception e ) { + e.printStackTrace(); + } finally { + try { + if ( con != null && !con.isClosed() ) { + con.close(); + } + } catch ( Exception e ) { + e.printStackTrace(); + } + } + } + + + private static void printPrompt() { + System.out.print( language + "@" + namespace + "> " ); + } + + + private static void executeStatement( String namespace, String language, String statement ) { + try ( Connection con = DriverManager.getConnection( "jdbc:polypheny://127.0.0.1:20590?strict=false", "pa", "" ) ) { + if ( !con.isWrapperFor( PolyConnection.class ) ) { + System.out.println( "Driver must support unwrapping to PolyConnection" ); + return; + } + PolyStatement polyStatement = con.unwrap( PolyConnection.class ).createPolyStatement(); + Result result = polyStatement.execute( namespace, language, statement ); + switch ( result.getResultType() ) { + case RELATIONAL: + RelationalResult relationalResult = result.unwrap( RelationalResult.class ); + printRelationalResult( relationalResult ); + break; + case DOCUMENT: + DocumentResult documentResult = result.unwrap( DocumentResult.class ); + printDocumentResult( documentResult ); + break; + case SCALAR: + ScalarResult scalarResult = result.unwrap( ScalarResult.class ); + System.out.println( "Update count: " + scalarResult.getScalar() + "." ); + break; + case GRAPH: + GraphResult graphResult = result.unwrap( GraphResult.class ); + printGraphResult( graphResult ); + break; + default: + System.out.println( "Unknown result type." ); + } + } catch ( Exception e ) { + e.printStackTrace(); + } + } + + + private static void printRelationalResult( RelationalResult relationalResult ) throws PrismInterfaceServiceException { + RelationalMetadata metadata = relationalResult.getMetadata(); + int columnsCount = metadata.getColumnCount(); + + List columnLabels = new ArrayList<>(); + List columnWidths = new ArrayList<>(); + for ( int i = 0; i < columnsCount; i++ ) { + String label = metadata.getColumnMeta( i ).getColumnLabel(); + columnLabels.add( label ); + columnWidths.add( label.length() ); + } + + List> rows = new ArrayList<>(); + for ( PolyRow row : relationalResult ) { + List formattedRow = new ArrayList<>(); + for ( int colIndex = 0; colIndex < columnsCount; colIndex++ ) { + String valueStr = row.get( colIndex ).toString(); + formattedRow.add( valueStr ); + columnWidths.set( colIndex, Math.max( columnWidths.get( colIndex ), valueStr.length() ) ); + } + rows.add( formattedRow ); + } + + printSeparator( columnWidths ); + printRow( columnLabels, columnWidths ); + printSeparator( columnWidths ); + for ( List row : rows ) { + printRow( row, columnWidths ); + } + printSeparator( columnWidths ); + } + + + private static void printDocumentResult( DocumentResult documentResult ) { + System.out.println( "DOC------------------------" ); + for ( PolyDocument document : documentResult ) { + System.out.println( document.toString() ); + System.out.println( "---------------------------" ); + } + } + + + private static void printGraphResult( GraphResult graphResult ) { + System.out.println( "GRAPH----------------------" ); + for ( PolyGraphElement graphElement : graphResult ) { + for ( Map.Entry property : graphElement.entrySet() ) { + System.out.println( property.getKey() + ": " + property.getValue() ); + } + System.out.println( "---------------------------" ); + } + } + + + private static void printSeparator( List columnWidths ) { + for ( int width : columnWidths ) { + System.out.print( "+" ); + for ( int i = 0; i < width + 2; i++ ) { + System.out.print( "-" ); + } + } + System.out.println( "+" ); + } + + + private static void printRow( List row, List columnWidths ) { + for ( int i = 0; i < row.size(); i++ ) { + System.out.print( "| " + padRight( row.get( i ), columnWidths.get( i ) ) + " " ); + } + System.out.println( "|" ); + } + + + private static String padRight( String s, int n ) { + return String.format( "%-" + n + "s", s ); + } + +} diff --git a/src/test/java/org/polypheny/jdbc/QueryTest.java b/src/test/java/org/polypheny/jdbc/QueryTest.java index 0b74f49..09a6e00 100644 --- a/src/test/java/org/polypheny/jdbc/QueryTest.java +++ b/src/test/java/org/polypheny/jdbc/QueryTest.java @@ -27,15 +27,25 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.polypheny.jdbc.multimodel.DocumentResult; +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.multimodel.Result; import org.polypheny.jdbc.multimodel.Result.ResultType; import org.polypheny.jdbc.types.PolyDocument; +import org.polypheny.jdbc.types.PolyGraphElement; public class QueryTest { + private static final String SQL_LANGUAGE_NAME = "sql"; + private static final String SQL_TEST_QUERY = "SELECT * FROM customers"; + private static final String MQL_LANGUAGE_NAME = "mongo"; - private static final String TEST_QUERY = "db.customers.find({});"; + private static final String MQL_TEST_QUERY = "db.customers.find({});"; + + private static final String CYPHER_LANGUAGE_NAME = "cypher"; + private static final String CYPHER_TEST_QUERY = "MATCH (c:customers)"; @BeforeAll @@ -49,7 +59,7 @@ public void thisOneWorks() throws SQLException { try ( Connection connection = TestHelper.getConnection(); Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery( "SELECT * FROM customers" ) + ResultSet resultSet = statement.executeQuery( SQL_TEST_QUERY ) ) { while ( resultSet.next() ) { // Process the result set... @@ -65,7 +75,7 @@ public void simpleRelationalTest() { fail( "Driver must support unwrapping to PolyphenyConnection" ); } PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement(); - Result result = polyStatement.execute( "public", "sql", "SELECT * FROM customers" ); + Result result = polyStatement.execute( "public", SQL_LANGUAGE_NAME, SQL_TEST_QUERY ); assertEquals( ResultType.RELATIONAL, result.getResultType() ); } catch ( SQLException e ) { throw new RuntimeException( e ); @@ -80,7 +90,7 @@ public void simpleMqlTest() { fail( "Driver must support unwrapping to PolyphenyConnection" ); } PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement(); - Result result = polyStatement.execute( "public", MQL_LANGUAGE_NAME, TEST_QUERY ); + Result result = polyStatement.execute( "public", MQL_LANGUAGE_NAME, MQL_TEST_QUERY ); assertEquals( ResultType.DOCUMENT, result.getResultType() ); } catch ( SQLException e ) { throw new RuntimeException( e ); @@ -95,9 +105,10 @@ public void mqlDataRetrievalTest() { fail( "Driver must support unwrapping to PolyphenyConnection" ); } PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement(); - Result result = polyStatement.execute( "public", MQL_LANGUAGE_NAME, TEST_QUERY ); + Result result = polyStatement.execute( "public", MQL_LANGUAGE_NAME, MQL_TEST_QUERY ); DocumentResult docs = result.unwrap( DocumentResult.class ); for ( PolyDocument doc : docs ) { + // Process the results... } assertEquals( ResultType.DOCUMENT, result.getResultType() ); } catch ( SQLException e ) { @@ -105,4 +116,48 @@ public void mqlDataRetrievalTest() { } } + + @Test + public void simpleCypherNodesTest() { + try ( Connection connection = TestHelper.getConnection() ) { + if ( !connection.isWrapperFor( PolyConnection.class ) ) { + fail( "Driver must support unwrapping to PolyphenyConnection" ); + } + PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement(); + Result result = polyStatement.execute( "public", CYPHER_LANGUAGE_NAME, "MATCH (c:customers) WHERE c.name = \"Maria\" OR c.name = \"Daniel\" RETURN c" ); + assertEquals( ResultType.GRAPH, result.getResultType() ); + GraphResult graphResult = result.unwrap( GraphResult.class ); + for ( PolyGraphElement element : graphResult ) { + element.get( "name" ); + element.get( "year_joined" ); + element.getLabels(); + element.getId(); + } + } catch ( SQLException e ) { + throw new RuntimeException( e ); + } + } + + + @Test + public void simpleCypherPropertyTest() { + try ( Connection connection = TestHelper.getConnection() ) { + if ( !connection.isWrapperFor( PolyConnection.class ) ) { + fail( "Driver must support unwrapping to PolyphenyConnection" ); + } + PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement(); + Result result = polyStatement.execute( "public", CYPHER_LANGUAGE_NAME, "MATCH (c:customers)\n" + + "WHERE c.name = \"Maria\" OR c.name = \"Daniel\"\n" + + "RETURN c.name, c.year_joined" ); + assertEquals( ResultType.RELATIONAL, result.getResultType() ); + RelationalResult relationalResult = result.unwrap( RelationalResult.class ); + for ( PolyRow row : relationalResult ) { + row.get( 0 ); + row.get( "c.name" ); + } + } catch ( SQLException e ) { + throw new RuntimeException( e ); + } + } + } diff --git a/src/test/java/org/polypheny/jdbc/types/TypedValueTest.java b/src/test/java/org/polypheny/jdbc/types/TypedValueTest.java index e2dcbad..d090b63 100644 --- a/src/test/java/org/polypheny/jdbc/types/TypedValueTest.java +++ b/src/test/java/org/polypheny/jdbc/types/TypedValueTest.java @@ -1046,4 +1046,5 @@ void testFromInterval() throws SQLException { assertEquals( interval.toString(), value.asString() ); } + }