diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 2bd7f29783..34b5a1eb91 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: adapter: [ mongodb, hsqldb, monetdb, postgresql, file, cottontail, neo4j ] - name: Integration Tests (Java 17) + name: ${{ matrix.adapter }} steps: - name: Checkout uses: actions/checkout@v4 @@ -21,25 +21,19 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Set env variable run: | echo "POLYPHENY_HOME=$GITHUB_WORKSPACE" >> $GITHUB_ENV + - name: Print Java Version + timeout-minutes: 5 + run: ./gradlew printJavaVersion - name: Assemble - uses: nick-fields/retry@v2 - with: - max_attempts: 1 - timeout_minutes: 60 - command: ./gradlew assemble + timeout-minutes: 10 + run: ./gradlew assemble - name: Build Plugins - uses: nick-fields/retry@v2 - with: - max_attempts: 1 - timeout_minutes: 60 - command: ./gradlew assemblePlugins + timeout-minutes: 5 + run: ./gradlew assemblePlugins - name: Execute integration tests for ${{ matrix.adapter }} - uses: nick-fields/retry@v2 - with: - max_attempts: 1 - timeout_minutes: 30 - command: ./gradlew integrationTests -Dstore.default=${{ matrix.adapter }} + timeout-minutes: 30 + run: ./gradlew integrationTests -Dstore.default=${{ matrix.adapter }} diff --git a/.github/workflows/matrix.yml b/.github/workflows/matrix.yml index 055d62f868..c2aa42460d 100644 --- a/.github/workflows/matrix.yml +++ b/.github/workflows/matrix.yml @@ -26,21 +26,15 @@ jobs: - name: Set env variable run: | echo "POLYPHENY_HOME=$GITHUB_WORKSPACE" >> $GITHUB_ENV + - name: Print Java Version + timeout-minutes: 5 + run: ./gradlew printJavaVersion - name: Assemble - uses: nick-fields/retry@v2 - with: - max_attempts: 1 - timeout_minutes: 60 - command: ./gradlew assemble + timeout-minutes: 60 + run: ./gradlew assemble - name: Build Plugins - uses: nick-fields/retry@v2 - with: - max_attempts: 1 - timeout_minutes: 60 - command: ./gradlew assemblePlugins + timeout-minutes: 60 + run: ./gradlew assemblePlugins - name: Execute tests - uses: nick-fields/retry@v2 - with: - max_attempts: 1 - timeout_minutes: 30 - command: ./gradlew check + timeout-minutes: 30 + run: ./gradlew check diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index c8a333b565..d79e617d89 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -12,9 +12,8 @@ jobs: strategy: fail-fast: false matrix: - java: [ 17, 21 ] os: [ macos-latest, ubuntu-latest, windows-latest ] - name: Java ${{ matrix.java }} @ ${{ matrix.os }} + name: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v4 @@ -22,25 +21,19 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: ${{ matrix.java }} + java-version: 21 - name: Set env variable run: | echo "POLYPHENY_HOME=$GITHUB_WORKSPACE" >> $GITHUB_ENV + - name: Print Java Version + timeout-minutes: 5 + run: ./gradlew printJavaVersion - name: Assemble - uses: nick-fields/retry@v2 - with: - max_attempts: 1 - timeout_minutes: 60 - command: ./gradlew assemble + timeout-minutes: 60 + run: ./gradlew assemble - name: Build Plugins - uses: nick-fields/retry@v2 - with: - max_attempts: 1 - timeout_minutes: 60 - command: ./gradlew assemblePlugins + timeout-minutes: 60 + run: ./gradlew assemblePlugins - name: Execute tests - uses: nick-fields/retry@v2 - with: - max_attempts: 1 - timeout_minutes: 30 - command: ./gradlew -p plugins test + timeout-minutes: 30 + run: ./gradlew -p plugins test diff --git a/build.gradle b/build.gradle index 69e526f869..fa43f55b45 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,8 @@ allprojects { java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion.set(JavaLanguageVersion.of(17)) + vendor = JvmVendorSpec.ADOPTIUM } } @@ -203,6 +204,27 @@ allprojects { } +task printJavaVersion { + doLast { + println("Java version used for running Gradle: " + org.gradle.api.JavaVersion.current()) + println "Current JAVA_HOME used by Gradle: " + System.getProperty("java.home") + + // Retrieve and print the Java version configured in the toolchain + // Check if a toolchain is configured and retrieve details + if (project.extensions.findByType(JavaPluginExtension.class)?.toolchain != null) { + def toolchainService = project.extensions.getByType(JavaToolchainService.class) + def javaCompiler = toolchainService.compilerFor { + languageVersion.set(project.extensions.getByType(JavaPluginExtension.class).toolchain.languageVersion.get()) + } + println "Java version used by toolchain: " + javaCompiler.get().metadata.languageVersion + println "Toolchain JAVA_HOME: " + javaCompiler.get().metadata.installationPath + } else { + println "No toolchain configured." + } + } +} + + idea { project { settings { diff --git a/core/src/main/java/org/polypheny/db/ResultIterator.java b/core/src/main/java/org/polypheny/db/ResultIterator.java index 534c5a5d0c..7e8358540b 100644 --- a/core/src/main/java/org/polypheny/db/ResultIterator.java +++ b/core/src/main/java/org/polypheny/db/ResultIterator.java @@ -62,6 +62,11 @@ public ResultIterator( Iterator iterator, Statement statement, int public List> getNextBatch() { + return getNextBatch( batch ); + } + + + public List> getNextBatch( int fetchSize ) { StopWatch stopWatch = null; try { if ( isTimed ) { @@ -70,7 +75,7 @@ public List> getNextBatch() { } List> res = new ArrayList<>(); int i = 0; - while ( (batch < 0 || i++ < batch) && iterator.hasNext() ) { + while ( (fetchSize < 0 || i++ < fetchSize) && iterator.hasNext() ) { res.add( Lists.newArrayList( iterator.next() ) ); } @@ -134,7 +139,7 @@ public List getArrayRows() { @Override - public void close() throws Exception { + public void close() { try { if ( iterator instanceof AutoCloseable ) { ((AutoCloseable) iterator).close(); diff --git a/core/src/main/java/org/polypheny/db/adapter/DataContext.java b/core/src/main/java/org/polypheny/db/adapter/DataContext.java index 26013ef933..16d98cdf4d 100644 --- a/core/src/main/java/org/polypheny/db/adapter/DataContext.java +++ b/core/src/main/java/org/polypheny/db/adapter/DataContext.java @@ -29,6 +29,7 @@ import org.apache.calcite.linq4j.QueryProvider; import org.apache.calcite.linq4j.tree.Expressions; import org.apache.calcite.linq4j.tree.ParameterExpression; +import org.jetbrains.annotations.NotNull; import org.polypheny.db.adapter.java.JavaTypeFactory; import org.polypheny.db.algebra.type.AlgDataType; import org.polypheny.db.catalog.snapshot.Snapshot; @@ -77,7 +78,7 @@ public interface DataContext { Statement getStatement(); - void addParameterValues( long index, AlgDataType type, List data ); + void addParameterValues( long index, @NotNull AlgDataType type, List data ); AlgDataType getParameterType( long index ); @@ -85,9 +86,9 @@ public interface DataContext { void setParameterValues( List> values ); - Map getParameterTypes(); + Map getParameterTypes(); - void setParameterTypes( Map types ); + void setParameterTypes( Map types ); default void resetParameterValues() { throw new UnsupportedOperationException(); @@ -118,7 +119,7 @@ default void resetContext() { } - record ParameterValue(long index, AlgDataType type, PolyValue value) { + record ParameterValue( long index, AlgDataType type, PolyValue value ) { } @@ -239,7 +240,7 @@ public Statement getStatement() { @Override - public void addParameterValues( long index, AlgDataType type, List data ) { + public void addParameterValues( long index, @NotNull AlgDataType type, List data ) { } diff --git a/core/src/main/java/org/polypheny/db/adapter/Modifiable.java b/core/src/main/java/org/polypheny/db/adapter/Modifiable.java index dc338e360a..cc60be941b 100644 --- a/core/src/main/java/org/polypheny/db/adapter/Modifiable.java +++ b/core/src/main/java/org/polypheny/db/adapter/Modifiable.java @@ -270,11 +270,6 @@ static void dropGraphSubstitute( Modifiable modifiable, long allocation ) { } - static void dropCollectionSubstitute( Modifiable modifiable, long allocation ) { - modifiable.getCatalog().removeAllocAndPhysical( allocation ); - } - - default AlgNode getModify( long allocId, Modify modify, AlgBuilder builder ) { if ( modify.getEntity().unwrap( AllocationTable.class ).isPresent() ) { return getRelModify( allocId, (RelModify) modify, builder ); diff --git a/core/src/main/java/org/polypheny/db/algebra/AlgNode.java b/core/src/main/java/org/polypheny/db/algebra/AlgNode.java index 259e11f148..08293b170c 100644 --- a/core/src/main/java/org/polypheny/db/algebra/AlgNode.java +++ b/core/src/main/java/org/polypheny/db/algebra/AlgNode.java @@ -131,7 +131,6 @@ public interface AlgNode extends AlgOptNode, Cloneable { AlgDataType getTupleType(); - /** * Returns the type of the rows expected for an input. Defaults to {@link #getTupleType}. * @@ -353,7 +352,6 @@ default boolean isImplementationCacheable() { * Expands node * If a part of AlgNode is a LogicalViewScan it is replaced * Else recursively hands call down if view in deeper level - * */ default AlgNode unfoldView( @Nullable AlgNode parent, int index, AlgCluster cluster ) { int i = 0; @@ -387,6 +385,13 @@ default boolean containsScan() { return getInputs().stream().anyMatch( AlgNode::containsScan ); } + default boolean containsEntity() { + if ( getEntity() != null ) { + return true; + } + return getInputs().stream().anyMatch( AlgNode::containsEntity ); + } + /** * Context of an algebra expression, for purposes of checking validity. */ diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumUtils.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumUtils.java index 5adc7f7fab..e40f8e3073 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumUtils.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumUtils.java @@ -194,8 +194,8 @@ static Expression joinSelector( JoinAlgType joinType, PhysType physType, List x; case FALSE -> Expressions.call( BuiltInMethod.IS_TRUE.method, x ); case TRUE -> Expressions.call( BuiltInMethod.IS_NOT_FALSE.method, x ); - case IS_NULL -> Expressions.new_( PolyBoolean.class, Expressions.equal( x, NULL_EXPR ) ); - case IS_NOT_NULL -> Expressions.new_( PolyBoolean.class, Expressions.notEqual( x, NULL_EXPR ) ); + case IS_NULL -> Expressions.new_( PolyBoolean.class, PolyValue.isNullExpression( x ) ); + case IS_NOT_NULL -> Expressions.new_( PolyBoolean.class, Expressions.equal( PolyValue.isNullExpression( x ), Expressions.constant( false ) ) ); }; } } @@ -1962,7 +1962,7 @@ public Expression implement( RexToLixTranslator translator, RexCall call, List 1; case JSON, VARCHAR, VARBINARY -> AlgDataType.PRECISION_NOT_SPECIFIED; - case DECIMAL -> getMaxNumericPrecision(); + case DECIMAL -> PolyType.MAX_DECIMAL_PRECISION; case INTERVAL -> PolyType.DEFAULT_INTERVAL_START_PRECISION; case BOOLEAN -> 1; case TINYINT -> 3; @@ -90,7 +90,7 @@ public int getDefaultPrecision( PolyType typeName ) { @Override public int getMaxPrecision( PolyType typeName ) { return switch ( typeName ) { - case DECIMAL -> getMaxNumericPrecision(); + case DECIMAL -> PolyType.MAX_DECIMAL_PRECISION; case JSON, VARCHAR, CHAR, VARBINARY, BINARY -> 65536; case TIME, TIMESTAMP -> PolyType.MAX_DATETIME_PRECISION; case INTERVAL -> PolyType.MAX_INTERVAL_START_PRECISION; diff --git a/core/src/main/java/org/polypheny/db/catalog/Catalog.java b/core/src/main/java/org/polypheny/db/catalog/Catalog.java index b6166adc7c..92e78124cf 100644 --- a/core/src/main/java/org/polypheny/db/catalog/Catalog.java +++ b/core/src/main/java/org/polypheny/db/catalog/Catalog.java @@ -28,6 +28,7 @@ import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.pf4j.ExtensionPoint; import org.polypheny.db.adapter.AbstractAdapterSetting; import org.polypheny.db.adapter.Adapter; @@ -50,6 +51,7 @@ import org.polypheny.db.catalog.snapshot.Snapshot; import org.polypheny.db.iface.QueryInterfaceManager.QueryInterfaceTemplate; import org.polypheny.db.transaction.Transaction; +import org.polypheny.db.util.Pair; import org.polypheny.db.util.RunMode; public abstract class Catalog implements ExtensionPoint { @@ -107,8 +109,14 @@ public static void afterInit( Runnable action ) { public abstract void change(); + public abstract void executeCommitActions(); + + public abstract void clearCommitActions(); + public abstract void commit(); + public abstract Pair<@NotNull Boolean, @Nullable String> checkIntegrity(); + public abstract void rollback(); public abstract LogicalRelationalCatalog getLogicalRel( long namespaceId ); @@ -218,11 +226,11 @@ public void removeObserver( PropertyChangeListener listener ) { * Add a query interface * * @param uniqueName The unique name of the query interface - * @param clazz The class name of the query interface + * @param interfaceName The class name of the query interface * @param settings The configuration of the query interface * @return The id of the newly added query interface */ - public abstract long createQueryInterface( String uniqueName, String clazz, Map settings ); + public abstract long createQueryInterface( String uniqueName, String interfaceName, Map settings ); /** * Delete a query interface @@ -280,4 +288,6 @@ public static Snapshot snapshot() { public abstract void attachCommitConstraint( Supplier constraintChecker, String description ); + public abstract void attachCommitAction( Runnable action ); + } diff --git a/core/src/main/java/org/polypheny/db/catalog/IdBuilder.java b/core/src/main/java/org/polypheny/db/catalog/IdBuilder.java index 816f36faea..282b1d8241 100644 --- a/core/src/main/java/org/polypheny/db/catalog/IdBuilder.java +++ b/core/src/main/java/org/polypheny/db/catalog/IdBuilder.java @@ -16,39 +16,61 @@ package org.polypheny.db.catalog; +import io.activej.serializer.annotations.Deserialize; +import io.activej.serializer.annotations.Serialize; import java.util.concurrent.atomic.AtomicLong; import lombok.Value; @Value public class IdBuilder { - AtomicLong snapshotId; - AtomicLong namespaceId; - AtomicLong entityId; + @Serialize + public AtomicLong snapshotId; - AtomicLong physicalId; + @Serialize + public AtomicLong namespaceId; - AtomicLong allocId; - AtomicLong fieldId; + @Serialize + public AtomicLong entityId; - AtomicLong userId; + @Serialize + public AtomicLong physicalId; - AtomicLong indexId; + @Serialize + public AtomicLong allocId; - AtomicLong keyId; + @Serialize + public AtomicLong fieldId; - AtomicLong adapterId; + @Serialize + public AtomicLong userId; - AtomicLong adapterTemplateId; + @Serialize + public AtomicLong indexId; - AtomicLong interfaceId; + @Serialize + public AtomicLong keyId; - AtomicLong constraintId; + @Serialize + public AtomicLong adapterId; - AtomicLong groupId; + @Serialize + public AtomicLong adapterTemplateId; - AtomicLong partitionId; - AtomicLong placementId; + @Serialize + public AtomicLong interfaceId; + + @Serialize + public AtomicLong constraintId; + + @Serialize + public AtomicLong groupId; + + @Serialize + public AtomicLong partitionId; + + @Serialize + public AtomicLong placementId; private static IdBuilder INSTANCE; @@ -83,22 +105,22 @@ private IdBuilder() { public IdBuilder( - AtomicLong snapshotId, - AtomicLong namespaceId, - AtomicLong entityId, - AtomicLong fieldId, - AtomicLong userId, - AtomicLong allocId, - AtomicLong physicalId, - AtomicLong indexId, - AtomicLong keyId, - AtomicLong adapterId, - AtomicLong adapterTemplateId, - AtomicLong interfaceId, - AtomicLong constraintId, - AtomicLong groupId, - AtomicLong partitionId, - AtomicLong placementId ) { + @Deserialize("snapshotId") AtomicLong snapshotId, + @Deserialize("namespaceId") AtomicLong namespaceId, + @Deserialize("entityId") AtomicLong entityId, + @Deserialize("fieldId") AtomicLong fieldId, + @Deserialize("userId") AtomicLong userId, + @Deserialize("allocId") AtomicLong allocId, + @Deserialize("physicalId") AtomicLong physicalId, + @Deserialize("indexId") AtomicLong indexId, + @Deserialize("keyId") AtomicLong keyId, + @Deserialize("adapterId") AtomicLong adapterId, + @Deserialize("adapterTemplateId") AtomicLong adapterTemplateId, + @Deserialize("interfaceId") AtomicLong interfaceId, + @Deserialize("constraintId") AtomicLong constraintId, + @Deserialize("groupId") AtomicLong groupId, + @Deserialize("partitionId") AtomicLong partitionId, + @Deserialize("placementId") AtomicLong placementId ) { this.snapshotId = snapshotId; this.namespaceId = namespaceId; this.entityId = entityId; @@ -199,4 +221,26 @@ public long getNewAdapterTemplateId() { return adapterTemplateId.getAndIncrement(); } + + public synchronized void restore( IdBuilder idBuilder ) { + this.snapshotId.set( idBuilder.snapshotId.longValue() ); + this.namespaceId.set( idBuilder.namespaceId.longValue() ); + this.entityId.set( idBuilder.entityId.longValue() ); + this.fieldId.set( idBuilder.fieldId.longValue() ); + + this.indexId.set( idBuilder.indexId.longValue() ); + this.keyId.set( idBuilder.keyId.longValue() ); + this.userId.set( idBuilder.userId.longValue() ); + this.allocId.set( idBuilder.allocId.longValue() ); + this.physicalId.set( idBuilder.physicalId.longValue() ); + this.constraintId.set( idBuilder.constraintId.longValue() ); + + this.adapterId.set( idBuilder.adapterId.longValue() ); + this.adapterTemplateId.set( idBuilder.adapterTemplateId.longValue() ); + this.interfaceId.set( idBuilder.interfaceId.longValue() ); + this.groupId.set( idBuilder.groupId.longValue() ); + this.partitionId.set( idBuilder.partitionId.longValue() ); + this.placementId.set( idBuilder.placementId.longValue() ); + } + } diff --git a/core/src/main/java/org/polypheny/db/catalog/catalogs/AdapterCatalog.java b/core/src/main/java/org/polypheny/db/catalog/catalogs/AdapterCatalog.java index d3c31d73d6..4dc0b209ff 100644 --- a/core/src/main/java/org/polypheny/db/catalog/catalogs/AdapterCatalog.java +++ b/core/src/main/java/org/polypheny/db/catalog/catalogs/AdapterCatalog.java @@ -35,7 +35,6 @@ import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; import org.polypheny.db.catalog.Catalog; -import org.polypheny.db.catalog.IdBuilder; import org.polypheny.db.catalog.entity.allocation.AllocationEntity; import org.polypheny.db.catalog.entity.physical.PhysicalEntity; import org.polypheny.db.catalog.entity.physical.PhysicalField; @@ -49,8 +48,6 @@ @SerializeClass(subclasses = { DocAdapterCatalog.class, RelAdapterCatalog.class, GraphAdapterCatalog.class }) public abstract class AdapterCatalog { - IdBuilder idBuilder = IdBuilder.getInstance(); - @Serialize public long adapterId; @@ -81,7 +78,8 @@ public AdapterCatalog( Map physicals, Map allocations, Map> allocToPhysicals, - Map, PhysicalField> fields ) { + Map, PhysicalField> fields + ) { this.adapterId = adapterId; this.namespaces = new ConcurrentHashMap<>( namespaces ); this.physicals = new ConcurrentHashMap<>( physicals ); diff --git a/core/src/main/java/org/polypheny/db/catalog/entity/LogicalQueryInterface.java b/core/src/main/java/org/polypheny/db/catalog/entity/LogicalQueryInterface.java index 851a3e2f02..db423f2d70 100644 --- a/core/src/main/java/org/polypheny/db/catalog/entity/LogicalQueryInterface.java +++ b/core/src/main/java/org/polypheny/db/catalog/entity/LogicalQueryInterface.java @@ -43,7 +43,7 @@ public class LogicalQueryInterface implements PolyObject { @Serialize public String name; @Serialize - public String clazz; + public String interfaceName; @Serialize public ImmutableMap settings; @@ -51,11 +51,11 @@ public class LogicalQueryInterface implements PolyObject { public LogicalQueryInterface( @Deserialize("id") final long id, @Deserialize("name") @NonNull final String uniqueName, - @Deserialize("clazz") @NonNull final String clazz, + @Deserialize("interfaceName") @NonNull final String interfaceName, @Deserialize("settings") @NonNull final Map settings ) { this.id = id; this.name = uniqueName; - this.clazz = clazz; + this.interfaceName = interfaceName; this.settings = ImmutableMap.copyOf( settings ); } diff --git a/core/src/main/java/org/polypheny/db/catalog/impl/PolyCatalog.java b/core/src/main/java/org/polypheny/db/catalog/impl/PolyCatalog.java index 4b36738aa3..8a491ea088 100644 --- a/core/src/main/java/org/polypheny/db/catalog/impl/PolyCatalog.java +++ b/core/src/main/java/org/polypheny/db/catalog/impl/PolyCatalog.java @@ -32,6 +32,8 @@ import java.util.stream.Collectors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.polypheny.db.adapter.AbstractAdapterSetting; import org.polypheny.db.adapter.Adapter; import org.polypheny.db.adapter.AdapterManager; @@ -68,6 +70,7 @@ import org.polypheny.db.catalog.persistance.Persister; import org.polypheny.db.catalog.snapshot.Snapshot; import org.polypheny.db.catalog.snapshot.impl.SnapshotBuilder; +import org.polypheny.db.catalog.util.ConstraintCondition; import org.polypheny.db.iface.QueryInterfaceManager.QueryInterfaceTemplate; import org.polypheny.db.transaction.Transaction; import org.polypheny.db.type.PolySerializable; @@ -86,7 +89,8 @@ public class PolyCatalog extends Catalog implements PolySerializable { /** * Constraints which have to be met before a commit can be executed. */ - private final Collection, String>> commitConstraints = new ConcurrentLinkedDeque<>(); + private final Collection commitConstraints = new ConcurrentLinkedDeque<>(); + private final Collection commitActions = new ConcurrentLinkedDeque<>(); @Serialize @@ -119,7 +123,9 @@ public class PolyCatalog extends Catalog implements PolySerializable { @Serialize public final Map adapterRestore; - public final IdBuilder idBuilder = IdBuilder.getInstance(); + @Serialize + public final IdBuilder idBuilder; + private final Persister persister; @Getter @@ -139,7 +145,8 @@ public PolyCatalog() { Map.of(), Map.of(), Map.of(), - Map.of() ); + Map.of(), + IdBuilder.getInstance() ); } @@ -150,8 +157,10 @@ public PolyCatalog( @Deserialize("allocationCatalogs") Map allocationCatalogs, @Deserialize("adapterRestore") Map adapterRestore, @Deserialize("adapters") Map adapters, - @Deserialize("interfaces") Map interfaces ) { + @Deserialize("interfaces") Map interfaces, + @Deserialize("idBuilder") IdBuilder idBuilder ) { // persistent data + this.idBuilder = idBuilder; this.users = new ConcurrentHashMap<>( users ); this.logicalCatalogs = new ConcurrentHashMap<>( logicalCatalogs ); this.allocationCatalogs = new ConcurrentHashMap<>( allocationCatalogs ); @@ -193,22 +202,24 @@ public void change() { } + @Override + public void executeCommitActions() { + // execute physical changes + commitActions.forEach( Runnable::run ); + } + + + @Override + public void clearCommitActions() { + commitActions.clear(); + } + + public synchronized void commit() { if ( !this.dirty.get() ) { log.debug( "Nothing changed" ); return; } - // check constraints e.g. primary key constraints - List> fails = commitConstraints - .stream() - .map( c -> Pair.of( c.left.get(), c.right ) ) - .filter( c -> !c.left ) - .toList(); - - if ( !fails.isEmpty() ) { - commitConstraints.clear(); - throw new GenericRuntimeException( "DDL constraints not met: \n" + fails.stream().map( f -> f.right ).collect( Collectors.joining( ",\n " ) ) + "." ); - } log.debug( "commit" ); @@ -224,11 +235,33 @@ public synchronized void commit() { persister.write( backup ); this.dirty.set( false ); this.commitConstraints.clear(); + this.commitActions.clear(); + } + + + @Override + public Pair<@NotNull Boolean, @Nullable String> checkIntegrity() { + // check constraints e.g. primary key constraints + List> fails = commitConstraints + .stream() + .map( c -> Pair.of( c.condition().get(), c.errorMessage() ) ) + .filter( c -> !c.left ) + .toList(); + + if ( !fails.isEmpty() ) { + commitConstraints.clear(); + return Pair.of( false, "DDL constraints not met: \n" + fails.stream().map( f -> f.right ).collect( Collectors.joining( ",\n " ) ) + "." ); + } + return Pair.of( true, null ); } public void rollback() { long id = snapshot.id(); + + commitActions.clear(); + commitConstraints.clear(); + restoreLastState(); log.debug( "rollback" ); @@ -255,6 +288,7 @@ private void restoreLastState() { interfaces.putAll( old.interfaces ); adapterRestore.clear(); adapterRestore.putAll( old.adapterRestore ); + idBuilder.restore( old.idBuilder ); } @@ -398,10 +432,15 @@ public void dropAdapter( long id ) { @Override - public long createQueryInterface( String uniqueName, String clazz, Map settings ) { + public long createQueryInterface( String uniqueName, String interfaceName, Map settings ) { long id = idBuilder.getNewInterfaceId(); - interfaces.put( id, new LogicalQueryInterface( id, uniqueName, clazz, settings ) ); + synchronized ( this ) { + if ( interfaces.values().stream().anyMatch( l -> l.name.equals( uniqueName ) ) ) { + throw new GenericRuntimeException( "There is already a query interface with name " + uniqueName ); + } + interfaces.put( id, new LogicalQueryInterface( id, uniqueName, interfaceName, settings ) ); + } change(); return id; @@ -469,7 +508,13 @@ public void restore( Transaction transaction ) { @Override public void attachCommitConstraint( Supplier constraintChecker, String description ) { - commitConstraints.add( Pair.of( constraintChecker, description ) ); + commitConstraints.add( new ConstraintCondition( constraintChecker, description ) ); + } + + + @Override + public void attachCommitAction( Runnable action ) { + commitActions.add( action ); } diff --git a/core/src/main/java/org/polypheny/db/catalog/snapshot/Snapshot.java b/core/src/main/java/org/polypheny/db/catalog/snapshot/Snapshot.java index 828ae3e88f..26a0770cdf 100644 --- a/core/src/main/java/org/polypheny/db/catalog/snapshot/Snapshot.java +++ b/core/src/main/java/org/polypheny/db/catalog/snapshot/Snapshot.java @@ -17,6 +17,7 @@ package org.polypheny.db.catalog.snapshot; import java.util.List; +import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; @@ -47,7 +48,8 @@ public interface Snapshot extends OperatorTable { * @param name Pattern for the namespace name. null returns all. * @return List of namespaces which fit to the specified filter. If there is no namespace which meets the criteria, an empty list is returned. */ - @NotNull List getNamespaces( @Nullable Pattern name ); + @NotNull + List getNamespaces( @Nullable Pattern name ); /** * Returns the namespace with the specified id. @@ -55,7 +57,8 @@ public interface Snapshot extends OperatorTable { * @param id The id of the namespace * @return The namespace */ - @NotNull Optional getNamespace( long id ); + @NotNull + Optional getNamespace( long id ); /** * Returns the namespace with the given name. @@ -63,7 +66,8 @@ public interface Snapshot extends OperatorTable { * @param name The name of the namespace * @return The namespace */ - @NotNull Optional getNamespace( String name ); + @NotNull + Optional getNamespace( String name ); /** @@ -72,7 +76,8 @@ public interface Snapshot extends OperatorTable { * @param name The name of the user * @return The user */ - @NotNull Optional getUser( String name ); + @NotNull + Optional getUser( String name ); /** * Get the user with the specified id. @@ -80,7 +85,8 @@ public interface Snapshot extends OperatorTable { * @param id The id of the user * @return The user */ - @NotNull Optional getUser( long id ); + @NotNull + Optional getUser( long id ); /** * Get list of all adapters @@ -94,23 +100,25 @@ public interface Snapshot extends OperatorTable { * * @return The adapter */ - @NotNull Optional getAdapter( String uniqueName ); + @NotNull + Optional getAdapter( String uniqueName ); /** * Get an adapter by its id * * @return The adapter */ - @NotNull Optional getAdapter( long id ); + @NotNull + Optional getAdapter( long id ); /* - * Get list of all query interfaces + * Get map of all query interfaces * - * @return List of query interfaces + * @return Map of query interfaces */ @NotNull - List getQueryInterfaces(); + Map getQueryInterfaces(); /** * Get a query interface by its unique name @@ -121,8 +129,8 @@ public interface Snapshot extends OperatorTable { @NotNull Optional getQueryInterface( String uniqueName ); - @NotNull Optional getInterfaceTemplate( String name ); - + @NotNull + Optional getInterfaceTemplate( String name ); List getTablesForPeriodicProcessing(); diff --git a/core/src/main/java/org/polypheny/db/catalog/snapshot/impl/SnapshotImpl.java b/core/src/main/java/org/polypheny/db/catalog/snapshot/impl/SnapshotImpl.java index 5008961457..bd8b51595f 100644 --- a/core/src/main/java/org/polypheny/db/catalog/snapshot/impl/SnapshotImpl.java +++ b/core/src/main/java/org/polypheny/db/catalog/snapshot/impl/SnapshotImpl.java @@ -167,8 +167,8 @@ public List getAdapters() { @Override - public @NotNull List getQueryInterfaces() { - return interfaces.values().asList(); + public @NotNull Map getQueryInterfaces() { + return ImmutableMap.copyOf( interfaces ); } diff --git a/core/src/main/java/org/polypheny/db/catalog/util/ConstraintCondition.java b/core/src/main/java/org/polypheny/db/catalog/util/ConstraintCondition.java new file mode 100644 index 0000000000..3552022659 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/catalog/util/ConstraintCondition.java @@ -0,0 +1,24 @@ +/* + * 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.catalog.util; + +import java.util.function.Supplier; + +public record ConstraintCondition( Supplier condition, String errorMessage ) { + + +} diff --git a/core/src/main/java/org/polypheny/db/ddl/DdlManager.java b/core/src/main/java/org/polypheny/db/ddl/DdlManager.java index 68fca87dd6..2205918381 100644 --- a/core/src/main/java/org/polypheny/db/ddl/DdlManager.java +++ b/core/src/main/java/org/polypheny/db/ddl/DdlManager.java @@ -564,23 +564,7 @@ public static DdlManager getInstance() { * Helper class which holds all information required for creating a column, * decoupled from a specific query language */ - @Value - public static class FieldInformation { - - public String name; - public ColumnTypeInformation typeInformation; - public Collation collation; - public PolyValue defaultValue; - public int position; - - - public FieldInformation( String name, ColumnTypeInformation typeInformation, Collation collation, PolyValue defaultValue, int position ) { - this.name = name; - this.typeInformation = typeInformation; - this.collation = collation; - this.defaultValue = defaultValue; - this.position = position; - } + public record FieldInformation( String name, ColumnTypeInformation typeInformation, Collation collation, PolyValue defaultValue, int position ) { } @@ -620,18 +604,8 @@ public ConstraintInformation( String name, ConstraintType type, List col * Helper class, which holds all type information for a column * decoupled from the used query language */ - @Value - public static class ColumnTypeInformation { - - public PolyType type; - @Nullable - public PolyType collectionType; - public Integer precision; - public Integer scale; - public Integer dimension; - public Integer cardinality; - public Boolean nullable; + public record ColumnTypeInformation( PolyType type, @Nullable PolyType collectionType, Integer precision, Integer scale, Integer dimension, Integer cardinality, Boolean nullable ) { public ColumnTypeInformation( PolyType type, diff --git a/core/src/main/java/org/polypheny/db/docker/DockerManager.java b/core/src/main/java/org/polypheny/db/docker/DockerManager.java index e69da48201..d84cce3224 100644 --- a/core/src/main/java/org/polypheny/db/docker/DockerManager.java +++ b/core/src/main/java/org/polypheny/db/docker/DockerManager.java @@ -35,10 +35,10 @@ import org.polypheny.db.config.RuntimeConfig; import org.polypheny.db.docker.exceptions.DockerUserException; import org.polypheny.db.docker.models.DockerHost; -import org.polypheny.db.util.RunMode; import org.polypheny.db.docker.models.DockerInstanceInfo; import org.polypheny.db.docker.models.HandshakeInfo; import org.polypheny.db.docker.models.UpdateDockerResponse; +import org.polypheny.db.util.RunMode; public final class DockerManager { diff --git a/core/src/main/java/org/polypheny/db/docker/PolyphenyDockerClient.java b/core/src/main/java/org/polypheny/db/docker/PolyphenyDockerClient.java index 942151f24c..0204a50a81 100644 --- a/core/src/main/java/org/polypheny/db/docker/PolyphenyDockerClient.java +++ b/core/src/main/java/org/polypheny/db/docker/PolyphenyDockerClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -19,8 +19,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.Socket; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/core/src/main/java/org/polypheny/db/docker/PolyphenyHandshakeClient.java b/core/src/main/java/org/polypheny/db/docker/PolyphenyHandshakeClient.java index af9c951c34..0dd30dbe48 100644 --- a/core/src/main/java/org/polypheny/db/docker/PolyphenyHandshakeClient.java +++ b/core/src/main/java/org/polypheny/db/docker/PolyphenyHandshakeClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. diff --git a/core/src/main/java/org/polypheny/db/docker/PolyphenyTlsClient.java b/core/src/main/java/org/polypheny/db/docker/PolyphenyTlsClient.java index e3a252dc75..a885909adc 100644 --- a/core/src/main/java/org/polypheny/db/docker/PolyphenyTlsClient.java +++ b/core/src/main/java/org/polypheny/db/docker/PolyphenyTlsClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. diff --git a/core/src/main/java/org/polypheny/db/functions/Functions.java b/core/src/main/java/org/polypheny/db/functions/Functions.java index 22d65fb849..4684df755d 100644 --- a/core/src/main/java/org/polypheny/db/functions/Functions.java +++ b/core/src/main/java/org/polypheny/db/functions/Functions.java @@ -290,7 +290,7 @@ public static Enumerable batch( final DataContext context, final Enumerable

Enumerable streamRight( final DataContext context, final Enumerable baz, final Function0> executorCall, final List polyTypes ) { AlgDataTypeFactory factory = new PolyTypeFactoryImpl( AlgDataTypeSystem.DEFAULT ); - List algDataTypes = polyTypes.stream().map( typeName -> typeName == PolyType.ARRAY ? factory.createArrayType( factory.createPolyType( PolyType.ANY ), -1 ) : factory.createPolyType( typeName ) ).toList(); + List algDataTypes = polyTypes.stream().map( typeName -> typeName == PolyType.ARRAY ? factory.createArrayType( factory.createPolyType( PolyType.ANY ), -1 ) : deriveType( factory, typeName ) ).toList(); boolean single = polyTypes.size() == 1; @@ -323,6 +323,14 @@ public static Enumerable streamRight( final DataContext context } + private static AlgDataType deriveType( AlgDataTypeFactory factory, PolyType type ) { + if ( type == PolyType.CHAR ) { + return factory.createPolyType( PolyType.VARCHAR ); + } + return factory.createPolyType( type ); + } + + @Nullable private static Enumerable handleContextBatch( DataContext context, @@ -879,10 +887,12 @@ public static PolyBoolean similar( PolyString s, PolyString pattern ) { /** - * SQL = operator applied to Object values (including String; neither side may be null). + * SQL = operator applied to number values. */ public static PolyBoolean eq( PolyNumber b0, PolyNumber b1 ) { - if ( b0 == null || b1 == null ) { + if ( (b0 == null || b0.isNull()) && (b1 == null || b1.isNull()) ) { + return PolyBoolean.TRUE; + } else if ( b0 == null || b0.isNull() || b1 == null || b1.isNull() ) { return PolyBoolean.FALSE; } return PolyBoolean.of( b0.bigDecimalValue().stripTrailingZeros().equals( b1.bigDecimalValue().stripTrailingZeros() ) ); @@ -924,6 +934,9 @@ private static boolean allAssignablePoly( Class clazz, Obje * SQL <gt; operator applied to Object values (including String; neither side may be null). */ public static PolyBoolean ne( PolyValue b0, PolyValue b1 ) { + if ( b0.isNull() || b1.isNull() ) { + return PolyBoolean.FALSE; + } return PolyBoolean.of( b0.compareTo( b1 ) != 0 ); } @@ -951,6 +964,9 @@ public static PolyBoolean lt( PolyValue b0, PolyValue b1 ) { public static PolyBoolean lt( PolyNumber b0, PolyNumber b1 ) { + if ( b0.isNull() || b1.isNull() ) { + return PolyBoolean.FALSE; + } return PolyBoolean.of( PolyNumber.compareTo( b0, b1 ) < 0 ); } @@ -992,6 +1008,9 @@ public static PolyBoolean le( PolyValue b0, PolyValue b1 ) { public static PolyBoolean le( PolyNumber b0, PolyNumber b1 ) { + if ( b0.isNull() || b1.isNull() ) { + return PolyBoolean.FALSE; + } return PolyBoolean.of( b0.compareTo( b1 ) <= 0 ); } @@ -999,9 +1018,12 @@ public static PolyBoolean le( PolyNumber b0, PolyNumber b1 ) { /** - * SQL > operator applied to boolean values. + * SQL > operator applied to numeric values. */ public static PolyBoolean gt( PolyNumber b0, PolyNumber b1 ) { + if ( b0.isNull() || b1.isNull() ) { + return PolyBoolean.FALSE; + } return PolyBoolean.of( b0.bigDecimalValue().compareTo( b1.bigDecimalValue() ) > 0 ); } @@ -1033,6 +1055,9 @@ public static PolyBoolean gt( PolyValue b0, PolyValue b1 ) { * SQL operator applied to BigDecimal values. */ public static PolyBoolean ge( PolyNumber b0, PolyNumber b1 ) { + if ( b0.isNull() || b1.isNull() ) { + return PolyBoolean.FALSE; + } return PolyBoolean.of( b0.bigDecimalValue().compareTo( b1.bigDecimalValue() ) >= 0 ); } diff --git a/core/src/main/java/org/polypheny/db/iface/QueryInterface.java b/core/src/main/java/org/polypheny/db/iface/QueryInterface.java index c6e47f67a7..e01f8d4eef 100644 --- a/core/src/main/java/org/polypheny/db/iface/QueryInterface.java +++ b/core/src/main/java/org/polypheny/db/iface/QueryInterface.java @@ -20,6 +20,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -38,8 +39,6 @@ public abstract class QueryInterface implements Runnable, PropertyChangeListener protected final transient TransactionManager transactionManager; protected final transient Authenticator authenticator; - @Getter - private final long queryInterfaceId; @Getter private final String uniqueName; @@ -54,16 +53,14 @@ public abstract class QueryInterface implements Runnable, PropertyChangeListener public QueryInterface( final TransactionManager transactionManager, final Authenticator authenticator, - final long queryInterfaceId, final String uniqueName, final Map settings, final boolean supportsDml, final boolean supportsDdl ) { this.transactionManager = transactionManager; this.authenticator = authenticator; - this.queryInterfaceId = queryInterfaceId; this.uniqueName = uniqueName; - this.settings = settings; + this.settings = new HashMap<>( settings ); this.supportsDml = supportsDml; this.supportsDdl = supportsDdl; @@ -139,6 +136,9 @@ public static abstract class QueryInterfaceSetting { public final boolean required; public final boolean modifiable; + + public abstract String getDefault(); + } @@ -152,6 +152,11 @@ public QueryInterfaceSettingInteger( String name, boolean canBeNull, boolean req this.defaultValue = defaultValue; } + + public String getDefault() { + return defaultValue.toString(); + } + } @@ -165,6 +170,11 @@ public QueryInterfaceSettingLong( String name, boolean canBeNull, boolean requir this.defaultValue = defaultValue; } + + public String getDefault() { + return defaultValue.toString(); + } + } @@ -178,6 +188,11 @@ public QueryInterfaceSettingString( String name, boolean canBeNull, boolean requ this.defaultValue = defaultValue; } + + public String getDefault() { + return defaultValue; + } + } @@ -191,17 +206,29 @@ public QueryInterfaceSettingBoolean( String name, boolean canBeNull, boolean req this.defaultValue = defaultValue; } + + public String getDefault() { + return Boolean.toString( defaultValue ); + } + } public static class QueryInterfaceSettingList extends QueryInterfaceSetting { public final List options; + public final String defaultValue; - public QueryInterfaceSettingList( String name, boolean canBeNull, boolean required, boolean modifiable, List options ) { + public QueryInterfaceSettingList( String name, boolean canBeNull, boolean required, boolean modifiable, List options, String defaultValue ) { super( name, canBeNull, required, modifiable ); this.options = options; + this.defaultValue = defaultValue; + } + + + public String getDefault() { + return defaultValue; } } diff --git a/core/src/main/java/org/polypheny/db/iface/QueryInterfaceManager.java b/core/src/main/java/org/polypheny/db/iface/QueryInterfaceManager.java index fb5e1a857f..35e8708c13 100644 --- a/core/src/main/java/org/polypheny/db/iface/QueryInterfaceManager.java +++ b/core/src/main/java/org/polypheny/db/iface/QueryInterfaceManager.java @@ -17,20 +17,12 @@ package org.polypheny.db.iface; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.collect.ImmutableMap; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import com.google.gson.JsonSerializer; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -import lombok.AllArgsConstructor; +import java.util.concurrent.atomic.AtomicReference; import lombok.extern.slf4j.Slf4j; import org.polypheny.db.catalog.Catalog; import org.polypheny.db.catalog.entity.LogicalQueryInterface; @@ -77,19 +69,19 @@ public QueryInterface getQueryInterface( String uniqueName ) { } - public static void addInterfaceType( String interfaceName, Class clazz, Map defaultSettings ) { - Catalog.getInstance().createInterfaceTemplate( clazz.getSimpleName(), new QueryInterfaceTemplate( clazz, interfaceName, defaultSettings ) ); - //REGISTER.put( clazz.getSimpleName(), new QueryInterfaceType( clazz, interfaceName, defaultSettings ) ); + public static void addInterfaceTemplate( + String interfaceName, String description, + List availableSettings, Function5, QueryInterface> deployer ) { + Catalog.getInstance().createInterfaceTemplate( interfaceName, new QueryInterfaceTemplate( interfaceName, description, + deployer, availableSettings ) ); } - public static void removeInterfaceType( Class clazz ) { - for ( LogicalQueryInterface queryInterface : Catalog.getInstance().getSnapshot().getQueryInterfaces() ) { - if ( queryInterface.clazz.equals( clazz.getName() ) ) { - throw new GenericRuntimeException( "Cannot remove the interface type, there is still a interface active." ); - } + public static void removeInterfaceType( String interfaceName ) { + if ( Catalog.snapshot().getQueryInterfaces().values().stream().anyMatch( i -> i.getInterfaceName().equals( interfaceName ) ) ) { + throw new GenericRuntimeException( "Cannot remove the interface type, there is still a interface active." ); } - Catalog.getInstance().dropInterfaceTemplate( clazz.getSimpleName() ); + Catalog.getInstance().dropInterfaceTemplate( interfaceName ); } @@ -103,22 +95,32 @@ public ImmutableMap getQueryInterfaces() { } - public List getAvailableQueryInterfaceTypes() { - List result = new LinkedList<>(); + public List getAvailableQueryInterfaceTemplates() { + return Catalog.snapshot().getInterfaceTemplates(); + } + + + private void startInterface( QueryInterface instance, String interfaceName, Long id ) { + Thread thread = new Thread( instance ); + AtomicReference error = new AtomicReference<>(); + thread.setUncaughtExceptionHandler( ( Thread t, Throwable e ) -> error.set( e ) ); + thread.start(); + try { - for ( Class clazz : Catalog.snapshot().getInterfaceTemplates().stream().map( v -> v.clazz ).collect( Collectors.toList() ) ) { - // Exclude abstract classes - if ( !Modifier.isAbstract( clazz.getModifiers() ) ) { - String name = (String) clazz.getDeclaredField( "INTERFACE_NAME" ).get( null ); - String description = (String) clazz.getDeclaredField( "INTERFACE_DESCRIPTION" ).get( null ); - List settings = (List) clazz.getDeclaredField( "AVAILABLE_SETTINGS" ).get( null ); - result.add( new QueryInterfaceInformation( name, description, clazz, settings ) ); - } - } - } catch ( NoSuchFieldException | IllegalAccessException e ) { - throw new GenericRuntimeException( "Something went wrong while retrieving list of available query interface types.", e ); + thread.join(); + } catch ( InterruptedException e ) { + log.warn( "Interrupted on join()", e ); + } + if ( error.get() != null ) { + throw new GenericRuntimeException( error.get() ); } - return result; + + if ( id == null ) { + id = Catalog.getInstance().createQueryInterface( instance.getUniqueName(), interfaceName, instance.getCurrentSettings() ); + } + interfaceByName.put( instance.getUniqueName(), instance ); + interfaceById.put( id, instance ); + interfaceThreadById.put( id, thread ); } @@ -126,72 +128,31 @@ public List getAvailableQueryInterfaceTypes() { * Restores query interfaces from catalog */ public void restoreInterfaces( Snapshot snapshot ) { - try { - List interfaces = snapshot.getQueryInterfaces(); - for ( LogicalQueryInterface iface : interfaces ) { - String[] split = iface.clazz.split( "\\$" ); - split = split[split.length - 1].split( "\\." ); - Class clazz = Catalog.snapshot().getInterfaceTemplate( split[split.length - 1] ).orElseThrow().clazz; - Constructor ctor = clazz.getConstructor( TransactionManager.class, Authenticator.class, long.class, String.class, Map.class ); - QueryInterface instance = (QueryInterface) ctor.newInstance( transactionManager, authenticator, iface.id, iface.name, iface.settings ); - - Thread thread = new Thread( instance ); - thread.start(); - - try { - thread.join(); - } catch ( InterruptedException e ) { - log.warn( "Interrupted on join()", e ); + Map interfaces = snapshot.getQueryInterfaces(); + interfaces.forEach( ( id, l ) -> { + QueryInterface q = Catalog.snapshot().getInterfaceTemplate( l.interfaceName ) + .map( t -> t.deployer.get( transactionManager, authenticator, l.name, l.settings ) ) + .orElseThrow(); + startInterface( q, l.interfaceName, id ); } - - interfaceByName.put( instance.getUniqueName(), instance ); - interfaceById.put( instance.getQueryInterfaceId(), instance ); - interfaceThreadById.put( instance.getQueryInterfaceId(), thread ); - } - } catch ( NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e ) { - throw new GenericRuntimeException( "Something went wrong while restoring query interfaces from the catalog.", e ); - } + ); } - public QueryInterface addQueryInterface( Catalog catalog, String clazzName, String uniqueName, Map settings ) { + public QueryInterface createQueryInterface( String interfaceName, String uniqueName, Map settings ) { uniqueName = uniqueName.toLowerCase(); if ( interfaceByName.containsKey( uniqueName ) ) { throw new GenericRuntimeException( "There is already a query interface with this unique name" ); } QueryInterface instance; - long ifaceId = -1; + QueryInterfaceTemplate template = Catalog.snapshot().getInterfaceTemplate( interfaceName ).orElseThrow(); try { - String[] split = clazzName.split( "\\$" ); - split = split[split.length - 1].split( "\\." ); - Class clazz = Catalog.snapshot().getInterfaceTemplate( split[split.length - 1] ).orElseThrow().clazz; - Constructor ctor = clazz.getConstructor( TransactionManager.class, Authenticator.class, long.class, String.class, Map.class ); - ifaceId = catalog.createQueryInterface( uniqueName, clazzName, settings ); - instance = (QueryInterface) ctor.newInstance( transactionManager, authenticator, ifaceId, uniqueName, settings ); - - Thread thread = new Thread( instance ); - thread.start(); - - try { - thread.join(); - } catch ( InterruptedException e ) { - log.warn( "Interrupted on join()", e ); - } - - interfaceByName.put( instance.getUniqueName(), instance ); - interfaceById.put( instance.getQueryInterfaceId(), instance ); - interfaceThreadById.put( instance.getQueryInterfaceId(), thread ); - } catch ( InvocationTargetException e ) { - if ( ifaceId != -1 ) { - catalog.dropQueryInterface( ifaceId ); - } - throw new GenericRuntimeException( "Something went wrong while adding a new query interface: " + e.getCause().getMessage(), e ); - } catch ( NoSuchMethodException | IllegalAccessException | InstantiationException e ) { - if ( ifaceId != -1 ) { - catalog.dropQueryInterface( ifaceId ); - } - throw new GenericRuntimeException( "Something went wrong while adding a new query interface!", e ); + instance = template.deployer().get( transactionManager, authenticator, uniqueName, settings ); + } catch ( GenericRuntimeException e ) { + throw new GenericRuntimeException( "Failed to deploy query interface: " + e.getMessage() ); } + startInterface( instance, interfaceName, null ); + return instance; } @@ -216,49 +177,36 @@ public void removeQueryInterface( Catalog catalog, String uniqueName ) { } - @AllArgsConstructor - public static class QueryInterfaceInformation { - - public final String name; - public final String description; - public final Class clazz; - public final List availableSettings; - - - public static String toJson( QueryInterfaceInformation[] queryInterfaceInformations ) { - JsonSerializer queryInterfaceInformationSerializer = ( src, typeOfSrc, context ) -> { - JsonObject jsonStore = new JsonObject(); - jsonStore.addProperty( "name", src.name ); - jsonStore.addProperty( "description", src.description ); - jsonStore.addProperty( "clazz", src.clazz.getCanonicalName() ); - jsonStore.add( "availableSettings", context.serialize( src.availableSettings ) ); - return jsonStore; - }; - Gson qiiGson = new GsonBuilder().registerTypeAdapter( QueryInterfaceInformation.class, queryInterfaceInformationSerializer ).create(); - return qiiGson.toJson( queryInterfaceInformations, QueryInterfaceInformation[].class ); - } + /** + * Model needed for the UI + */ + public record QueryInterfaceInformationRequest( + @JsonSerialize String interfaceName, + @JsonSerialize String uniqueName, + @JsonSerialize Map currentSettings ) { } - /** - * Model needed for the UI - */ - public static class QueryInterfaceInformationRequest { + public record QueryInterfaceTemplate( + @JsonSerialize String interfaceName, + @JsonSerialize String description, + Function5, QueryInterface> deployer, + @JsonSerialize List availableSettings ) { - public String clazzName; - public String uniqueName; - public Map currentSettings; + public Map getDefaultSettings() { + Map m = new HashMap<>(); + availableSettings.forEach( ( s ) -> m.put( s.name, s.getDefault() ) ); + return m; + } } - @AllArgsConstructor - public static class QueryInterfaceTemplate { + @FunctionalInterface + public interface Function5 { - public Class clazz; - public String interfaceName; - public Map defaultSettings; + R get( P1 p1, P2 p2, P3 p3, P4 p4 ); } diff --git a/core/src/main/java/org/polypheny/db/interpreter/Interpreter.java b/core/src/main/java/org/polypheny/db/interpreter/Interpreter.java index fa7291c2dc..b63866a765 100644 --- a/core/src/main/java/org/polypheny/db/interpreter/Interpreter.java +++ b/core/src/main/java/org/polypheny/db/interpreter/Interpreter.java @@ -538,4 +538,3 @@ interface ScalarCompiler { } } - diff --git a/core/src/main/java/org/polypheny/db/interpreter/ScanNode.java b/core/src/main/java/org/polypheny/db/interpreter/ScanNode.java index cff2d75716..890247cef5 100644 --- a/core/src/main/java/org/polypheny/db/interpreter/ScanNode.java +++ b/core/src/main/java/org/polypheny/db/interpreter/ScanNode.java @@ -161,7 +161,6 @@ private static ScanNode createFilterable( Compiler compiler, RelScan alg, Imm } - private static ScanNode createEnumerable( Compiler compiler, RelScan alg, Enumerable> enumerable, final ImmutableList acceptedProjects, List rejectedFilters, final ImmutableList rejectedProjects ) { if ( !rejectedFilters.isEmpty() ) { final RexNode filter = RexUtil.composeConjunction( alg.getCluster().getRexBuilder(), rejectedFilters ); @@ -209,4 +208,3 @@ public Row apply( Row row ) { } } - diff --git a/core/src/main/java/org/polypheny/db/languages/LanguageManager.java b/core/src/main/java/org/polypheny/db/languages/LanguageManager.java index c643edaed5..07a6321158 100644 --- a/core/src/main/java/org/polypheny/db/languages/LanguageManager.java +++ b/core/src/main/java/org/polypheny/db/languages/LanguageManager.java @@ -108,9 +108,9 @@ public List anyPrepareQuery( QueryContext context, Statem throw new GenericRuntimeException( String.format( "%s query is empty", context.getLanguage().serializedName() ) ); } - parsedQueries = context.getLanguage().splitter().apply( context ); + parsedQueries = context.getLanguage().parser().apply( context ); } catch ( Throwable e ) { - log.warn( "Error on preparing query: " + e.getMessage() ); + log.warn( "Error on preparing query: {}", e.getMessage() ); if ( transaction.isAnalyze() ) { transaction.getQueryAnalyzer().attachStacktrace( e ); } @@ -133,7 +133,7 @@ public List anyPrepareQuery( QueryContext context, Statem try { // test if parsing was successful if ( parsed.getQueryNode().isEmpty() ) { - Exception e = new GenericRuntimeException( "Error during parsing of query \"" + context.getQuery() + "\"" ); + Exception e = new GenericRuntimeException( "Error during parsing of query \"%s\"".formatted( context.getQuery() ) ); return handleParseException( statement, parsed, transaction, e, implementationContexts ); } @@ -194,7 +194,7 @@ public List anyPrepareQuery( QueryContext context, Statem implementationContexts.add( new ImplementationContext( implementation, parsed, statement, null ) ); } catch ( Throwable e ) { - log.warn( "Caught exception: ", e ); + log.warn( "Caught exception: ", e ); // TODO: This should not log in all cases, at least not with stacktrace if ( transaction.isAnalyze() ) { transaction.getQueryAnalyzer().attachStacktrace( e ); } @@ -266,4 +266,14 @@ public static List toQueryNodes( QueryContext queries ) { .toList(); } + + public static List toUnsplitQueryNodes( QueryContext queries ) { + Processor processor = queries.getLanguage().processorSupplier().get(); + List splitQueries = List.of( queries.getQuery() ); + + return splitQueries.stream().flatMap( q -> processor.parse( q ).stream().map( single -> Pair.of( single, q ) ) ) + .map( p -> ParsedQueryContext.fromQuery( p.right, p.left, queries ) ) + .toList(); + } + } diff --git a/core/src/main/java/org/polypheny/db/languages/QueryLanguage.java b/core/src/main/java/org/polypheny/db/languages/QueryLanguage.java index 5a1de840f0..9a95d43308 100644 --- a/core/src/main/java/org/polypheny/db/languages/QueryLanguage.java +++ b/core/src/main/java/org/polypheny/db/languages/QueryLanguage.java @@ -41,7 +41,7 @@ public record QueryLanguage( @Nullable ParserFactory factory, @NotNull Supplier processorSupplier, @Nullable BiFunction validatorSupplier, - @NotNull Function> splitter, + @NotNull Function> parser, @NotNull Function limitRemover ) { diff --git a/core/src/main/java/org/polypheny/db/nodes/OperatorImpl.java b/core/src/main/java/org/polypheny/db/nodes/OperatorImpl.java index e11abd99fe..7169557b71 100644 --- a/core/src/main/java/org/polypheny/db/nodes/OperatorImpl.java +++ b/core/src/main/java/org/polypheny/db/nodes/OperatorImpl.java @@ -18,10 +18,10 @@ import java.util.List; import lombok.Getter; +import lombok.Setter; import org.polypheny.db.algebra.constant.Kind; import org.polypheny.db.algebra.constant.Monotonicity; import org.polypheny.db.algebra.operators.OperatorName; -import org.polypheny.db.catalog.exceptions.GenericRuntimeException; import org.polypheny.db.languages.ParserPos; import org.polypheny.db.nodes.BasicNodeVisitor.ArgHandler; import org.polypheny.db.nodes.Function.FunctionType; @@ -61,6 +61,7 @@ public FunctionType getFunctionType() { */ protected final PolyOperandTypeChecker operandTypeChecker; + @Setter @Getter private OperatorName operatorName; @@ -74,17 +75,9 @@ public OperatorImpl( String name, Kind kind, PolyReturnTypeInference returnTypeI } - public void setOperatorName( OperatorName operatorName ) { - if ( this.operatorName != null ) { - throw new GenericRuntimeException( "The operatorName can only be set once." ); - } - this.operatorName = operatorName; - } - - /** * Returns whether the given operands are valid. If not valid and {@code fail}, throws an assertion error. - * + *

* Similar to {#@link #checkOperandCount}, but some operators may have different valid operands in {@link Node} and {@code RexNode} formats (some examples are CAST and AND), * and this method throws internal errors, not user errors. */ @@ -96,7 +89,7 @@ public boolean validRexOperands( int count, Litmus litmus ) { /** * Creates a call to this operand with an array of operands. - * + *

* The position of the resulting call is the union of the pos and the positions of all the operands. * * @param pos Parser position @@ -111,7 +104,7 @@ public final Call createCall( ParserPos pos, Node... operands ) { /** * Creates a call to this operand with a list of operands contained in a {@link NodeList}. - * + *

* The position of the resulting call inferred from the SqlNodeList. * * @param nodeList List of arguments @@ -125,7 +118,7 @@ public final Call createCall( NodeList nodeList ) { /** * Creates a call to this operand with a list of operands. - * + *

* The position of the resulting call is the union of the pos and the positions of all the operands. */ @Override @@ -173,7 +166,7 @@ public R acceptCall( NodeVisitor visitor, Call call ) { /** * Accepts a {@link NodeVisitor}, directing an {@link ArgHandler} to visit an operand of a call. - * + *

* The argument handler allows fine control about how the operands are visited, and how the results are combined. * * @param visitor Visitor @@ -192,7 +185,7 @@ public void acceptCall( NodeVisitor visitor, Call call, boolean onlyExpre /** * Returns whether this operator is an aggregate function. By default, subclass type is used (an instance of SqlAggFunction is assumed to be an aggregator; anything else is not). - * + *

* Per SQL:2011, there are aggregate functions and window functions. * Every aggregate function (e.g. SUM) is also a window function. * There are window functions that are not aggregate functions, e.g. RANK, NTILE, LEAD, FIRST_VALUE.

@@ -210,12 +203,12 @@ public boolean isAggregator() { /** * Returns whether this is a window function that requires an OVER clause. - * + *

* For example, returns true for {@code RANK}, {@code DENSE_RANK} and other ranking functions; returns false for {@code SUM}, {@code COUNT}, {@code MIN}, {@code MAX}, {@code AVG} * (they can be used as non-window aggregate functions). - * + *

* If {@code requiresOver} returns true, then {@link #isAggregator()} must also return true. - * + *

* #@see #allowsFraming() * * @see #requiresOrder() @@ -228,7 +221,7 @@ public boolean requiresOver() { /** * Returns whether this is a window function that requires ordering. - * + *

* Per SQL:2011, 2, 6.10: "If <ntile function>, <lead or lag function>, RANK or DENSE_RANK is specified, then the window ordering clause shall be present." * * @see #isAggregator() @@ -241,11 +234,11 @@ public boolean requiresOrder() { /** * Returns whether this is a group function. - * + *

* Group functions can only appear in the GROUP BY clause. - * + *

* Examples are {@code HOP}, {@code TUMBLE}, {@code SESSION}. - * + *

* Group functions have auxiliary functions, e.g. {@code HOP_START}, but these are not group functions. */ @Override @@ -256,7 +249,7 @@ public boolean isGroup() { /** * Returns whether this is an group auxiliary function. - * + *

* Examples are {@code HOP_START} and {@code HOP_END} (both auxiliary to {@code HOP}). * * @see #isGroup() @@ -310,7 +303,7 @@ public String getSignatureTemplate( final int operandsCount ) { /** * Returns whether a call to this operator is monotonic. - * + *

* Default implementation returns {@link Monotonicity#NOT_MONOTONIC}. * * @param call Call to this operator with particular arguments and information about the monotonicity of the arguments diff --git a/core/src/main/java/org/polypheny/db/plan/AlgCluster.java b/core/src/main/java/org/polypheny/db/plan/AlgCluster.java index 035548276c..215880665c 100644 --- a/core/src/main/java/org/polypheny/db/plan/AlgCluster.java +++ b/core/src/main/java/org/polypheny/db/plan/AlgCluster.java @@ -39,6 +39,7 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import lombok.Getter; +import org.jetbrains.annotations.NotNull; import org.polypheny.db.algebra.AlgNode; import org.polypheny.db.algebra.core.CorrelationId; import org.polypheny.db.algebra.metadata.AlgMetadataProvider; @@ -60,6 +61,7 @@ public class AlgCluster { @Getter private final AlgDataTypeFactory typeFactory; @Getter + @NotNull private final AlgPlanner planner; private final AtomicInteger nextCorrel; @Getter diff --git a/core/src/main/java/org/polypheny/db/plan/AlgOptUtil.java b/core/src/main/java/org/polypheny/db/plan/AlgOptUtil.java index 7cae92f742..47d43b9be7 100644 --- a/core/src/main/java/org/polypheny/db/plan/AlgOptUtil.java +++ b/core/src/main/java/org/polypheny/db/plan/AlgOptUtil.java @@ -125,6 +125,7 @@ import org.polypheny.db.tools.AlgBuilder; import org.polypheny.db.tools.AlgBuilderFactory; import org.polypheny.db.type.ArrayType; +import org.polypheny.db.type.BasicPolyType; import org.polypheny.db.type.MultisetPolyType; import org.polypheny.db.type.PolyType; import org.polypheny.db.util.ImmutableBitSet; @@ -1441,6 +1442,9 @@ public static boolean eq( final String desc1, AlgDataType type1, final String de // This is why we have to handle it differently here and actually compare the properties // of the array types. // This means we are comparing the component type, cardinality, and dimension. + if ( type1 instanceof BasicPolyType && type1.getPolyType() == PolyType.ARRAY && type1.getPolyType() == type2.getPolyType() ) { + return litmus.succeed(); + } if ( type1.getPolyType() == PolyType.ARRAY && type2.getPolyType() == PolyType.ARRAY ) { ArrayType arrayType1 = (ArrayType) type1; ArrayType arrayType2 = (ArrayType) type2; @@ -3127,4 +3131,3 @@ private Exists( AlgNode r, boolean indicator, boolean outerJoin ) { } } - diff --git a/core/src/main/java/org/polypheny/db/plan/VisitorDataContext.java b/core/src/main/java/org/polypheny/db/plan/VisitorDataContext.java index 91138e4f1c..1be8b96e7f 100644 --- a/core/src/main/java/org/polypheny/db/plan/VisitorDataContext.java +++ b/core/src/main/java/org/polypheny/db/plan/VisitorDataContext.java @@ -38,6 +38,7 @@ import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.calcite.linq4j.QueryProvider; +import org.jetbrains.annotations.NotNull; import org.polypheny.db.adapter.DataContext; import org.polypheny.db.adapter.java.JavaTypeFactory; import org.polypheny.db.algebra.AlgNode; @@ -111,7 +112,7 @@ public Statement getStatement() { @Override - public void addParameterValues( long index, AlgDataType type, List data ) { + public void addParameterValues( long index, @NotNull AlgDataType type, List data ) { throw new UnsupportedOperationException( "This operation is not supported for " + getClass().getSimpleName() ); } diff --git a/core/src/main/java/org/polypheny/db/plan/volcano/AlgSubset.java b/core/src/main/java/org/polypheny/db/plan/volcano/AlgSubset.java index 9e93af9b14..eed119c71e 100644 --- a/core/src/main/java/org/polypheny/db/plan/volcano/AlgSubset.java +++ b/core/src/main/java/org/polypheny/db/plan/volcano/AlgSubset.java @@ -154,6 +154,12 @@ public boolean containsScan() { } + @Override + public boolean containsEntity() { + return set.alg.containsEntity(); + } + + public AlgNode getOriginal() { return set.alg; } @@ -282,7 +288,6 @@ public Collection getParentRels() { } - /** * Adds expression rel to this subset. */ diff --git a/core/src/main/java/org/polypheny/db/prepare/PolyphenyDbPrepareImpl.java b/core/src/main/java/org/polypheny/db/prepare/PolyphenyDbPrepareImpl.java index 571a59e9af..bb88217637 100644 --- a/core/src/main/java/org/polypheny/db/prepare/PolyphenyDbPrepareImpl.java +++ b/core/src/main/java/org/polypheny/db/prepare/PolyphenyDbPrepareImpl.java @@ -598,4 +598,3 @@ public RexNode parameter( ParameterExpression param ) { } } - diff --git a/core/src/main/java/org/polypheny/db/processing/QueryContext.java b/core/src/main/java/org/polypheny/db/processing/QueryContext.java index c90f58f30f..f0d0a0fba9 100644 --- a/core/src/main/java/org/polypheny/db/processing/QueryContext.java +++ b/core/src/main/java/org/polypheny/db/processing/QueryContext.java @@ -50,6 +50,9 @@ public class QueryContext { @Builder.Default boolean isAnalysed = false; + @Builder.Default + boolean isAutoGenerated = false; + @Builder.Default boolean usesCache = true; @@ -87,7 +90,8 @@ public class QueryContext { @SuperBuilder(toBuilder = true) public static class ParsedQueryContext extends QueryContext { - @Nullable Node queryNode; + @Nullable + Node queryNode; public static ParsedQueryContext fromQuery( String query, Node queryNode, QueryContext context ) { @@ -100,6 +104,7 @@ public static ParsedQueryContext fromQuery( String query, Node queryNode, QueryC return ParsedQueryContext.builder() .query( query ) .queryNode( queryNode ) + .isAutoGenerated( context.isAutoGenerated ) .language( context.language ) .isAnalysed( context.isAnalysed ) .usesCache( context.usesCache ) diff --git a/core/src/main/java/org/polypheny/db/rex/RexInterpreter.java b/core/src/main/java/org/polypheny/db/rex/RexInterpreter.java index 819d9c8a89..27a3df0964 100644 --- a/core/src/main/java/org/polypheny/db/rex/RexInterpreter.java +++ b/core/src/main/java/org/polypheny/db/rex/RexInterpreter.java @@ -346,7 +346,7 @@ private PolyValue case_( List values ) { elseValue = Util.last( values ); } for ( int i = 0; i < size; i += 2 ) { - if ( values.get( i ).isBoolean() && !values.get( i ).isNull() && values.get( i ).asBoolean().value.equals( true ) ) { + if ( values.get( i ).isBoolean() && !(values.get( i ) == null || values.get( i ).isNull()) && values.get( i ).asBoolean().value.equals( true ) ) { return values.get( i + 1 ); } } @@ -379,7 +379,7 @@ private PolyValue compare( List values, IntPredicate p ) { private boolean containsNull( List values ) { for ( PolyValue value : values ) { - if ( value == N ) { + if ( value == N || value.isNull() ) { return true; } } @@ -396,7 +396,7 @@ enum Truthy { static Truthy of( PolyValue c ) { - return c.isNull() || !c.isBoolean() ? UNKNOWN : (c.asBoolean().value ? TRUE : FALSE); + return c == null || c.isNull() || !c.isBoolean() ? UNKNOWN : (c.asBoolean().value ? TRUE : FALSE); } diff --git a/core/src/main/java/org/polypheny/db/schema/DummyDataContext.java b/core/src/main/java/org/polypheny/db/schema/DummyDataContext.java index 55098f5740..dce95f4af9 100644 --- a/core/src/main/java/org/polypheny/db/schema/DummyDataContext.java +++ b/core/src/main/java/org/polypheny/db/schema/DummyDataContext.java @@ -21,6 +21,7 @@ import java.util.Map; import lombok.Getter; import org.apache.calcite.linq4j.QueryProvider; +import org.jetbrains.annotations.NotNull; import org.polypheny.db.adapter.DataContext; import org.polypheny.db.adapter.java.JavaTypeFactory; import org.polypheny.db.algebra.type.AlgDataType; @@ -77,7 +78,7 @@ public Statement getStatement() { @Override - public void addParameterValues( long index, AlgDataType type, List data ) { + public void addParameterValues( long index, @NotNull AlgDataType type, List data ) { throw new UnsupportedOperationException( "This operation is not supported for " + getClass().getSimpleName() ); } diff --git a/core/src/main/java/org/polypheny/db/transaction/Transaction.java b/core/src/main/java/org/polypheny/db/transaction/Transaction.java index 6994f65f4c..73fc015925 100644 --- a/core/src/main/java/org/polypheny/db/transaction/Transaction.java +++ b/core/src/main/java/org/polypheny/db/transaction/Transaction.java @@ -92,6 +92,8 @@ public interface Transaction { void updateAccessMode( AccessMode accessCandidate ); + TransactionManager getTransactionManager(); + /** * Flavor, how multimedia results should be returned from a store. */ diff --git a/core/src/main/java/org/polypheny/db/type/PolySerializable.java b/core/src/main/java/org/polypheny/db/type/PolySerializable.java index b876b3dd11..b15115b7fd 100644 --- a/core/src/main/java/org/polypheny/db/type/PolySerializable.java +++ b/core/src/main/java/org/polypheny/db/type/PolySerializable.java @@ -18,11 +18,17 @@ import com.drew.lang.Charsets; import io.activej.codegen.DefiningClassLoader; +import io.activej.serializer.BinaryInput; +import io.activej.serializer.BinaryOutput; import io.activej.serializer.BinarySerializer; +import io.activej.serializer.CompatibilityLevel; +import io.activej.serializer.CorruptedDataException; import io.activej.serializer.SerializerFactory; +import io.activej.serializer.def.SimpleSerializerDef; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import org.polypheny.db.catalog.exceptions.GenericRuntimeException; import org.polypheny.db.config.RuntimeConfig; import org.polypheny.db.plugins.PolyPluginManager; @@ -37,7 +43,7 @@ public interface PolySerializable { Charset SERIALIZAION_CHARSET = Charsets.ISO_8859_1; static , T> BinarySerializer buildSerializer( C clazz ) { - return SerializerFactory.defaultInstance() + return SerializerFactory.defaultInstance().builder().with( AtomicLong.class, ctx -> new AtomicLongSerializer() ).build() .create( CLASS_LOADER, clazz ); } @@ -84,4 +90,24 @@ static T deserialize( byte[] serialized, Class c PolySerializable copy(); + class AtomicLongSerializer extends SimpleSerializerDef { + + @Override + protected BinarySerializer createSerializer( int version, CompatibilityLevel compatibilityLevel ) { + return new BinarySerializer<>() { + @Override + public void encode( BinaryOutput out, AtomicLong item ) { + out.writeVarLong( item.get() ); + } + + + @Override + public AtomicLong decode( BinaryInput in ) throws CorruptedDataException { + return new AtomicLong( in.readVarLong() ); + } + }; + } + + } + } diff --git a/core/src/main/java/org/polypheny/db/type/PolyType.java b/core/src/main/java/org/polypheny/db/type/PolyType.java index 1a4402fa1a..94a30ad2cf 100644 --- a/core/src/main/java/org/polypheny/db/type/PolyType.java +++ b/core/src/main/java/org/polypheny/db/type/PolyType.java @@ -152,7 +152,7 @@ public enum PolyType { PolyTypeFamily.CHARACTER ), TEXT( - PrecScale.NO_NO, + PrecScale.NO_NO | PrecScale.YES_NO, false, Types.VARCHAR, PolyTypeFamily.CHARACTER ), @@ -335,6 +335,7 @@ public enum PolyType { public static final int MIN_INTERVAL_FRACTIONAL_SECOND_PRECISION = 1; public static final int MAX_INTERVAL_START_PRECISION = 10; public static final int MAX_INTERVAL_FRACTIONAL_SECOND_PRECISION = 9; + public static final int MAX_DECIMAL_PRECISION = 64; // Cached map of enum values private static final Map VALUES_MAP = Util.enumConstants( PolyType.class ); diff --git a/core/src/main/java/org/polypheny/db/type/PolyTypeFactoryImpl.java b/core/src/main/java/org/polypheny/db/type/PolyTypeFactoryImpl.java index 32b9f1daa0..cc3ec2220d 100644 --- a/core/src/main/java/org/polypheny/db/type/PolyTypeFactoryImpl.java +++ b/core/src/main/java/org/polypheny/db/type/PolyTypeFactoryImpl.java @@ -344,7 +344,10 @@ private AlgDataType leastRestrictiveSqlType( List types ) { } } - resultType = createPolyType( newTypeName, precision ); + if ( newTypeName != PolyType.TEXT ) { + resultType = createPolyType( newTypeName, precision ); + } + } Charset charset = null; Collation collation = null; diff --git a/core/src/main/java/org/polypheny/db/type/PolyTypeUtil.java b/core/src/main/java/org/polypheny/db/type/PolyTypeUtil.java index d60f44b800..681e12242a 100644 --- a/core/src/main/java/org/polypheny/db/type/PolyTypeUtil.java +++ b/core/src/main/java/org/polypheny/db/type/PolyTypeUtil.java @@ -588,8 +588,12 @@ public static boolean canAssignFrom( AlgDataType toType, AlgDataType fromType ) if ( toType.getPolyType() != PolyType.ARRAY ) { return false; } - ArrayType fromPolyType = (ArrayType) fromType; - ArrayType toPolyType = (ArrayType) toType; + if ( fromType instanceof BasicPolyType && fromType.getPolyType() == toType.getPolyType() ) { + return true; + } + if ( !(fromType instanceof ArrayType fromPolyType) || !(toType instanceof ArrayType toPolyType) ) { + return false; + } //check if the nested types can be assigned AlgDataType fromComponentType = fromPolyType.getNestedComponentType(); AlgDataType toComponentType = toPolyType.getNestedComponentType(); diff --git a/core/src/main/java/org/polypheny/db/type/entity/PolyBinary.java b/core/src/main/java/org/polypheny/db/type/entity/PolyBinary.java index deb44f0bc4..ec28e4f105 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/PolyBinary.java +++ b/core/src/main/java/org/polypheny/db/type/entity/PolyBinary.java @@ -23,8 +23,6 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.google.common.io.BaseEncoding; @@ -33,6 +31,7 @@ import java.util.BitSet; import java.util.Objects; import lombok.Value; +import org.apache.calcite.avatica.util.Base64; import org.apache.calcite.avatica.util.ByteString; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; @@ -49,8 +48,6 @@ public class PolyBinary extends PolyValue { public static final PolyBinary EMPTY = new PolyBinary( new byte[0], null ); @JsonProperty() - @JsonSerialize(using = ByteStringSerializer.class) - @JsonDeserialize(using = ByteStringDeserializer.class) public byte[] value; @JsonProperty() @@ -130,6 +127,11 @@ public String toHexString() { } + public String as64String() { + return Base64.encodeBytes( value ); + } + + @Override public @NotNull PolyString asString() { return value == null ? PolyString.of( null ) : PolyString.of( toHexString() ); diff --git a/core/src/main/java/org/polypheny/db/type/entity/PolyInterval.java b/core/src/main/java/org/polypheny/db/type/entity/PolyInterval.java index 534f9261db..c482e898ab 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/PolyInterval.java +++ b/core/src/main/java/org/polypheny/db/type/entity/PolyInterval.java @@ -40,6 +40,7 @@ @NonFinal public class PolyInterval extends PolyValue { + @Getter @NotNull public Long millis; @@ -165,7 +166,7 @@ public PolyNumber getLeap( IntervalQualifier intervalQualifier ) { } - public record MonthsMilliseconds(long months, long milliseconds) { + private record MonthsMilliseconds( long months, long milliseconds ) { } diff --git a/core/src/main/java/org/polypheny/db/type/entity/PolyList.java b/core/src/main/java/org/polypheny/db/type/entity/PolyList.java index 8cd9d9e67d..3775e7de99 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/PolyList.java +++ b/core/src/main/java/org/polypheny/db/type/entity/PolyList.java @@ -66,7 +66,7 @@ import org.polypheny.db.util.Pair; @Slf4j -@EqualsAndHashCode(callSuper = true) +@EqualsAndHashCode(callSuper = false) @Value @JsonSerialize(using = PolyListSerializer.class) @JsonDeserialize(using = PolyListDeserializer.class) diff --git a/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java b/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java index 1d7cf39557..c2be251cb9 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java +++ b/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java @@ -41,6 +41,7 @@ import java.lang.reflect.Type; import java.sql.Timestamp; import java.util.Calendar; +import java.util.List; import java.util.Optional; import lombok.EqualsAndHashCode; import lombok.Value; @@ -50,6 +51,7 @@ import org.apache.calcite.linq4j.function.Function1; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; +import org.apache.calcite.linq4j.tree.Types; import org.apache.commons.lang3.NotImplementedException; import org.bson.BsonDocument; import org.bson.json.JsonParseException; @@ -230,11 +232,38 @@ public static Function1 getPolyToJava( AlgDataType type, bool } case FILE, IMAGE, AUDIO, VIDEO -> o -> o.asBlob().asByteArray(); case DOCUMENT -> o -> o.asDocument().toJson(); + case ANY -> o -> getPolyToJavaRuntime( o, arrayAsList ); default -> throw new NotImplementedException( "meta: " + type.getFullTypeString() ); }; } + private static Object getPolyToJavaRuntime( PolyValue value, boolean arrayAsList ) { + if ( value == null || value.isNull() ) { + return null; + } + + return switch ( value.type ) { + case VARCHAR, CHAR, TEXT -> value.asString().value; + case INTEGER, TINYINT, SMALLINT -> value.asNumber().IntValue(); + case FLOAT, REAL -> value.asNumber().FloatValue(); + case DOUBLE -> value.asNumber().DoubleValue(); + case BIGINT -> value.asNumber().LongValue(); + case DECIMAL -> value.asNumber().BigDecimalValue(); + case DATE -> value.asDate().getDaysSinceEpoch(); + case TIME -> value.asTime().getMillisOfDay(); + case TIMESTAMP -> value.asTimestamp().millisSinceEpoch; + case BOOLEAN -> value.asBoolean().value; + case ARRAY -> arrayAsList + ? (value.asList().value.stream().map( e -> getPolyToJavaRuntime( e, true ) ).toList()) + : value.asList().value.stream().map( e -> getPolyToJavaRuntime( e, false ) ).toList().toArray(); + case FILE, IMAGE, AUDIO, VIDEO -> value.asBlob().asByteArray(); + case DOCUMENT -> value.asDocument().toJson(); + default -> throw new NotImplementedException( "meta: " + value.type ); + }; + } + + private static AlgDataType getAndDecreaseArrayDimensionIfNecessary( ArrayType type ) { AlgDataType component = type.getComponentType(); while ( component.getPolyType() == PolyType.ARRAY ) { @@ -275,6 +304,20 @@ public static PolyValue fromJson( String json ) { } + public static AlgDataType deriveType( PolyValue value, AlgDataTypeFactory typeFactory ) { + if ( value == null ) { + return typeFactory.createPolyType( PolyType.NULL ); + } + PolyType type = value.type; + + if ( type == PolyType.ARRAY ) { + return typeFactory.createArrayType( typeFactory.createPolyType( PolyType.ANY ), -1 ); + } + + return typeFactory.createPolyType( type ); + } + + @NotNull public String toTypedJson() { try { @@ -365,6 +408,14 @@ public static PolyValue getNull( Class clazz ) { } + public static @NotNull Expression isNullExpression( Expression operand ) { + if ( Types.isArray( operand.getType() ) ) { + return Expressions.equal( operand, Expressions.constant( null ) ); + } + return Expressions.foldOr( List.of( Expressions.equal( operand, Expressions.constant( null ) ), Expressions.call( operand, "isNull" ) ) ); + } + + public static Class classFrom( PolyType polyType ) { return switch ( polyType ) { case BOOLEAN -> PolyBoolean.class; diff --git a/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyBigDecimal.java b/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyBigDecimal.java index 0478f49887..37beedc62c 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyBigDecimal.java +++ b/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyBigDecimal.java @@ -83,6 +83,9 @@ public static PolyBigDecimal of( double value ) { public static PolyBigDecimal of( Number value, int precision, int scale ) { + if ( value instanceof BigDecimal bigDecimal ) { + return PolyBigDecimal.of( bigDecimal ); + } return PolyBigDecimal.of( value.doubleValue() ); } @@ -190,37 +193,6 @@ public String toString() { } - @Override - public boolean equals( Object o ) { - if ( this == o ) { - return true; - } - if ( o == null ) { - return false; - } - - if ( !(o instanceof PolyValue) ) { - return false; - } - - if ( ((PolyValue) o).isNull() ) { - return false; - } - - if ( !((PolyValue) o).isNumber() ) { - return false; - } - BigDecimal that = ((PolyValue) o).asNumber().bigDecimalValue(); - return Objects.equals( value.stripTrailingZeros(), that.stripTrailingZeros() ); - } - - - @Override - public int hashCode() { - return Objects.hash( super.hashCode(), value ); - } - - @Override public int compareTo( @NotNull PolyValue o ) { if ( !o.isNumber() ) { @@ -249,6 +221,43 @@ public PolySerializable copy() { } + @Override + public boolean isNull() { + return value == null; + } + + + @Override + public boolean equals( Object o ) { + if ( this == o ) { + return true; + } + if ( o == null ) { + return false; + } + + if ( !(o instanceof PolyValue val) ) { + return false; + } + + if ( val.isNull() ) { + return false; + } + + if ( !val.isNumber() ) { + return false; + } + BigDecimal that = val.asNumber().bigDecimalValue(); + return Objects.equals( value.stripTrailingZeros(), that.stripTrailingZeros() ); + } + + + @Override + public int hashCode() { + return Objects.hash( super.hashCode(), value ); + } + + @Override public Object toJava() { return value; diff --git a/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyDouble.java b/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyDouble.java index a4d3c151f9..047ceea8c9 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyDouble.java +++ b/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyDouble.java @@ -29,6 +29,7 @@ import io.activej.serializer.def.SimpleSerializerDef; import java.math.BigDecimal; import java.util.Objects; +import lombok.EqualsAndHashCode; import lombok.Value; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; @@ -47,6 +48,7 @@ public class PolyDouble extends PolyNumber { @Serialize @JsonProperty @Nullable + @EqualsAndHashCode.Include public Double value; @@ -88,12 +90,6 @@ public int compareTo( @NotNull PolyValue o ) { } - @Override - public int hashCode() { - return Objects.hash( super.hashCode(), value ); - } - - public static PolyDouble convert( @Nullable PolyValue value ) { if ( value == null ) { return null; @@ -192,7 +188,7 @@ public PolyNumber negate() { @Override - public @Nullable Long deriveByteSize() { + public @NotNull Long deriveByteSize() { return 8L; } @@ -203,6 +199,12 @@ public Object toJava() { } + @Override + public int hashCode() { + return Objects.hash( super.hashCode(), value ); + } + + public static class PolyDoubleSerializerDef extends SimpleSerializerDef { @Override diff --git a/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyFloat.java b/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyFloat.java index d6676ae054..636122c298 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyFloat.java +++ b/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyFloat.java @@ -29,6 +29,7 @@ import io.activej.serializer.def.SimpleSerializerDef; import java.math.BigDecimal; import java.util.Objects; +import lombok.EqualsAndHashCode; import lombok.Value; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; @@ -42,11 +43,13 @@ import org.polypheny.db.type.entity.category.PolyNumber; @Value +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) public class PolyFloat extends PolyNumber { @Serialize @JsonProperty @Nullable + @EqualsAndHashCode.Include public Float value; @@ -106,12 +109,6 @@ public int compareTo( @NotNull PolyValue o ) { } - @Override - public int hashCode() { - return Objects.hash( super.hashCode(), value ); - } - - @Override public Expression asExpression() { return Expressions.new_( PolyFloat.class, Expressions.constant( value ) ); @@ -150,7 +147,7 @@ public double doubleValue() { @Override public BigDecimal bigDecimalValue() { - return BigDecimal.valueOf( value ); + return value == null ? null : new BigDecimal( Float.toString( value ) ); } @@ -191,7 +188,7 @@ public PolyNumber negate() { @Override - public @Nullable Long deriveByteSize() { + public @NotNull Long deriveByteSize() { return 4L; } @@ -202,6 +199,12 @@ public Object toJava() { } + @Override + public int hashCode() { + return Objects.hash( super.hashCode(), value ); + } + + public static class PolyFloatSerializerDef extends SimpleSerializerDef { @Override diff --git a/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyInteger.java b/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyInteger.java index 93315440d4..48d5d99bda 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyInteger.java +++ b/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyInteger.java @@ -114,12 +114,6 @@ public static PolyInteger ofNullable( Number value ) { } - @Override - public int hashCode() { - return Objects.hash( super.hashCode(), value ); - } - - @Override public Expression asExpression() { return Expressions.new_( PolyInteger.class, Expressions.constant( value ) ); @@ -226,6 +220,12 @@ public PolyNumber negate() { } + @Override + public int hashCode() { + return Objects.hash( super.hashCode(), value ); + } + + public static class PolyIntegerSerializerDef extends SimpleSerializerDef { @Override diff --git a/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyLong.java b/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyLong.java index a3a26a41ca..10b2047f6f 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyLong.java +++ b/core/src/main/java/org/polypheny/db/type/entity/numerical/PolyLong.java @@ -98,12 +98,6 @@ public int compareTo( @NotNull PolyValue o ) { } - @Override - public int hashCode() { - return Objects.hash( super.hashCode(), value ); - } - - @Override public Expression asExpression() { return Expressions.new_( PolyLong.class, Expressions.constant( value ) ); @@ -182,6 +176,12 @@ public PolyNumber negate() { } + @Override + public int hashCode() { + return Objects.hash( super.hashCode(), value ); + } + + public static PolyLong convert( PolyValue value ) { if ( value == null ) { return null; diff --git a/core/src/main/java/org/polypheny/db/util/BsonUtil.java b/core/src/main/java/org/polypheny/db/util/BsonUtil.java index 11743dc983..75c15fa83a 100644 --- a/core/src/main/java/org/polypheny/db/util/BsonUtil.java +++ b/core/src/main/java/org/polypheny/db/util/BsonUtil.java @@ -19,6 +19,7 @@ import com.mongodb.client.gridfs.GridFSBucket; import java.io.PushbackInputStream; import java.math.BigDecimal; +import java.math.RoundingMode; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; @@ -29,6 +30,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Optional; import java.util.Queue; import java.util.function.Consumer; import java.util.function.Function; @@ -40,6 +42,7 @@ import org.apache.calcite.linq4j.tree.Expressions; import org.apache.commons.lang3.NotImplementedException; import org.bson.BsonArray; +import org.bson.BsonBinary; import org.bson.BsonBoolean; import org.bson.BsonDecimal128; import org.bson.BsonDocument; @@ -65,6 +68,7 @@ import org.polypheny.db.runtime.ComparableList; import org.polypheny.db.runtime.PolyCollections.FlatMap; import org.polypheny.db.type.PolyType; +import org.polypheny.db.type.entity.PolyBinary; import org.polypheny.db.type.entity.PolyBoolean; import org.polypheny.db.type.entity.PolyList; import org.polypheny.db.type.entity.PolyNull; @@ -83,6 +87,8 @@ public class BsonUtil { private final static List stops = new ArrayList<>(); public static final String DOC_MONTH_KEY = "m"; public static final String DOC_MILLIS_KEY = "ms"; + public static final String DOC_MEDIA_TYPE_KEY = "_type"; + public static final String DOC_MEDIA_ID_KEY = "_id"; static { @@ -193,17 +199,17 @@ public static BsonValue getAsBson( PolyValue obj, PolyType type, GridFSBucket bu } return switch ( type ) { case BIGINT -> handleBigInt( obj ); - case DECIMAL -> handleDecimal( obj ); + case DECIMAL -> handleDecimal( obj, Optional.empty() ); case TINYINT -> handleTinyInt( obj ); case SMALLINT -> handleSmallInt( obj ); case INTEGER -> handleInteger( obj ); case FLOAT, REAL -> new BsonDouble( Double.parseDouble( obj.toString() ) ); - case DOUBLE -> handleDouble( obj ); + case DOUBLE -> handleDouble( obj, Optional.empty() ); case DATE -> handleDate( obj ); case TIME -> handleTime( obj ); case TIMESTAMP -> handleTimestamp( obj ); case BOOLEAN -> new BsonBoolean( obj.asBoolean().value ); - case BINARY -> new BsonString( new ByteString( obj.asBinary().value ).toBase64String() ); + case BINARY, VARBINARY -> new BsonBinary( obj.asBinary().value ); case AUDIO, IMAGE, VIDEO, FILE -> handleMultimedia( bucket, obj ); case INTERVAL -> handleInterval( obj ); case JSON -> handleDocument( obj ); @@ -220,7 +226,7 @@ public static BsonValue getAsBson( PolyValue obj, PolyType type, GridFSBucket bu * @param bucket the bucket can be used to retrieve multimedia objects * @return the transformer method, which can be used to get the correct BsonValues */ - public static Function getBsonTransformer( Queue types, GridFSBucket bucket ) { + public static Function getBsonTransformer( Queue>> types, GridFSBucket bucket ) { Function function = getBsonTransformerPrimitive( types, bucket ); return ( o ) -> { if ( o == null || o.isNull() ) { @@ -240,26 +246,27 @@ public static Function getBsonTransformer( Queue * @param bucket the bucket can be used to retrieve multimedia objects * @return the transformer method, which can be used to get the correct BsonValues */ - private static Function getBsonTransformerPrimitive( Queue types, GridFSBucket bucket ) { - return switch ( Objects.requireNonNull( types.poll() ) ) { + private static Function getBsonTransformerPrimitive( Queue>> types, GridFSBucket bucket ) { + Pair> type = types.poll(); + return switch ( Objects.requireNonNull( type.left ) ) { case BIGINT -> BsonUtil::handleBigInt; - case DECIMAL -> BsonUtil::handleDecimal; + case DECIMAL -> obj -> handleDecimal( obj, type.right ); case TINYINT -> BsonUtil::handleTinyInt; case SMALLINT -> BsonUtil::handleSmallInt; case INTEGER -> BsonUtil::handleInteger; - case FLOAT, REAL -> BsonUtil::handleNonDouble; - case DOUBLE -> BsonUtil::handleDouble; + case FLOAT, REAL -> obj -> handleNonDouble( obj, type.right ); + case DOUBLE -> obj -> handleDouble( obj, type.right ); case DATE -> BsonUtil::handleDate; case TIME -> BsonUtil::handleTime; case TIMESTAMP -> BsonUtil::handleTimestamp; case BOOLEAN -> BsonUtil::handleBoolean; - case BINARY -> BsonUtil::handleBinary; + case BINARY, VARBINARY -> BsonUtil::handleBinary; case AUDIO, IMAGE, VIDEO, FILE -> ( o ) -> handleMultimedia( bucket, o ); case INTERVAL -> BsonUtil::handleInterval; case JSON -> BsonUtil::handleDocument; case ARRAY -> { Function transformer = getBsonTransformer( types, bucket ); - yield ( o ) -> new BsonArray( o.asList().stream().map( e -> transformer.apply( (PolyValue) e ) ).toList() ); + yield ( o ) -> new BsonArray( o.asList().stream().map( transformer::apply ).toList() ); } case DOCUMENT -> o -> BsonDocument.parse( "{ k:" + (o.isString() ? o.asString().toQuotedJson() : o.toJson()) + "}" ).get( "k" ); default -> BsonUtil::handleString; @@ -272,13 +279,21 @@ private static BsonValue handleString( PolyValue obj ) { } - private static BsonValue handleNonDouble( PolyValue obj ) { + private static BsonValue handleNonDouble( PolyValue obj, Optional precision ) { return new BsonDouble( Double.parseDouble( obj.toString() ) ); } private static BsonValue handleBinary( Object obj ) { - return new BsonString( ((ByteString) obj).toBase64String() ); + if ( obj instanceof PolyBinary polyBinary ) { + return new BsonBinary( polyBinary.value ); + } else if ( obj instanceof ByteString byteString ) { + return new BsonBinary( byteString.getBytes() ); + } else if ( obj instanceof byte[] bytes ) { + return new BsonBinary( bytes ); + } + throw new GenericRuntimeException( "The provided object is not a binary object." ); + } @@ -305,16 +320,19 @@ private static BsonValue handleBigInt( PolyValue obj ) { } - private static BsonValue handleDouble( PolyValue obj ) { + private static BsonValue handleDouble( PolyValue obj, Optional precision ) { return new BsonDouble( obj.asNumber().DoubleValue() ); } private static BsonValue handleMultimedia( GridFSBucket bucket, PolyValue o ) { + if ( o.isBinary() ) { + return new BsonBinary( o.asBinary().value ); + } ObjectId id = bucket.uploadFromStream( "_", o.asBlob().asBinaryStream() ); return new BsonDocument() - .append( "_type", new BsonString( "s" ) ) - .append( "_id", new BsonString( id.toString() ) ); + .append( DOC_MEDIA_TYPE_KEY, new BsonString( "s" ) ) + .append( DOC_MEDIA_ID_KEY, new BsonString( id.toString() ) ); } @@ -326,8 +344,10 @@ private static BsonValue handleInterval( PolyValue obj ) { } - private static BsonValue handleDecimal( PolyValue obj ) { - return new BsonDecimal128( new Decimal128( obj.asNumber().BigDecimalValue() ) ); + private static BsonValue handleDecimal( PolyValue obj, Optional precision ) { + BigDecimal decimal = obj.asNumber().BigDecimalValue(); + decimal = precision.isPresent() ? decimal.setScale( precision.get(), RoundingMode.HALF_UP ) : decimal; + return new BsonDecimal128( new Decimal128( decimal ) ); } diff --git a/core/src/main/java/org/polypheny/db/util/Util.java b/core/src/main/java/org/polypheny/db/util/Util.java index 023222845a..8121cbd564 100644 --- a/core/src/main/java/org/polypheny/db/util/Util.java +++ b/core/src/main/java/org/polypheny/db/util/Util.java @@ -100,6 +100,7 @@ import org.apache.calcite.linq4j.Ord; import org.polypheny.db.algebra.AlgCollation; import org.polypheny.db.algebra.AlgFieldCollation; +import org.polypheny.db.algebra.AlgNode; import org.polypheny.db.algebra.constant.Kind; import org.polypheny.db.algebra.constant.Monotonicity; import org.polypheny.db.algebra.fun.AggFunction; @@ -2054,6 +2055,11 @@ public static Collation getDefaultCollation() { } + public static boolean containsEntity( AlgNode a ) { + return a.containsEntity(); + } + + /** * Exception used to interrupt a tree walk of any kind. */ diff --git a/core/src/test/java/org/polypheny/db/catalog/MockCatalog.java b/core/src/test/java/org/polypheny/db/catalog/MockCatalog.java index 0a69cbdaf3..7a3f583fa3 100644 --- a/core/src/test/java/org/polypheny/db/catalog/MockCatalog.java +++ b/core/src/test/java/org/polypheny/db/catalog/MockCatalog.java @@ -17,8 +17,13 @@ package org.polypheny.db.catalog; import java.beans.PropertyChangeListener; +import java.util.Collection; import java.util.Map; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.Supplier; import org.apache.commons.lang3.NotImplementedException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.polypheny.db.adapter.DeployMode; import org.polypheny.db.catalog.catalogs.AllocationDocumentCatalog; import org.polypheny.db.catalog.catalogs.AllocationGraphCatalog; @@ -32,6 +37,7 @@ import org.polypheny.db.catalog.entity.LogicalUser; import org.polypheny.db.catalog.logistic.DataModel; import org.polypheny.db.catalog.snapshot.Snapshot; +import org.polypheny.db.util.Pair; /** @@ -42,6 +48,9 @@ */ public abstract class MockCatalog extends Catalog { + public Collection commitActions = new ConcurrentLinkedDeque<>(); + + @Override public void init() { throw new NotImplementedException(); @@ -108,7 +117,6 @@ public AllocationGraphCatalog getAllocGraph( long namespaceId ) { } - @Override public void addObserver( PropertyChangeListener listener ) { super.addObserver( listener ); @@ -144,6 +152,7 @@ public void rollback() { throw new NotImplementedException(); } + @Override public long createNamespace( String name, DataModel dataModel, boolean caseSensitive ) { throw new NotImplementedException(); @@ -181,7 +190,7 @@ public void dropAdapter( long id ) { @Override - public long createQueryInterface( String uniqueName, String clazz, Map settings ) { + public long createQueryInterface( String uniqueName, String interfaceName, Map settings ) { throw new NotImplementedException(); } @@ -203,4 +212,34 @@ public void clear() { throw new NotImplementedException(); } + + @Override + public void executeCommitActions() { + this.commitActions.forEach( Runnable::run ); + } + + + @Override + public void clearCommitActions() { + this.commitActions.clear(); + } + + + @Override + public void attachCommitConstraint( Supplier constraintChecker, String description ) { + // empty on purpose + } + + + @Override + public void attachCommitAction( Runnable action ) { + commitActions.add( action ); + } + + + @Override + public Pair<@NotNull Boolean, @Nullable String> checkIntegrity() { + return Pair.of( true, null ); + } + } diff --git a/core/src/test/java/org/polypheny/db/snapshot/MockSnapshot.java b/core/src/test/java/org/polypheny/db/snapshot/MockSnapshot.java index e46ba988b5..6bd0f34fbe 100644 --- a/core/src/test/java/org/polypheny/db/snapshot/MockSnapshot.java +++ b/core/src/test/java/org/polypheny/db/snapshot/MockSnapshot.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import lombok.EqualsAndHashCode; import lombok.Value; @@ -107,7 +108,7 @@ public List getAdapters() { @Override - public @NotNull List getQueryInterfaces() { + public @NotNull Map getQueryInterfaces() { throw new UnsupportedOperationException(); } diff --git a/dbms/src/main/java/org/polypheny/db/PolyphenyDb.java b/dbms/src/main/java/org/polypheny/db/PolyphenyDb.java index 886d4a8464..2b75323c45 100644 --- a/dbms/src/main/java/org/polypheny/db/PolyphenyDb.java +++ b/dbms/src/main/java/org/polypheny/db/PolyphenyDb.java @@ -122,6 +122,9 @@ public class PolyphenyDb { @Option(name = { "-mode" }, description = "Special system configuration for running tests", typeConverterProvider = PolyModesConverter.class) public static RunMode mode = RunMode.PRODUCTION; + @Option(name = { "-noAutoDocker" }, description = "Do not perform automatic setup with a local Docker instance") + public static boolean noAutoDocker = false; + @Option(name = { "-gui" }, description = "Show splash screen on startup and add taskbar gui") public boolean desktopMode = false; @@ -358,9 +361,11 @@ public void join( final long millis ) throws InterruptedException { StatusService.initialize( transactionManager, server.getServer() ); Catalog.resetDocker = resetDocker; // TODO: Needed? - log.debug( "Setting Docker Timeouts" ); - RuntimeConfig.DOCKER_TIMEOUT.setInteger( mode == RunMode.DEVELOPMENT || mode == RunMode.TEST ? 5 : RuntimeConfig.DOCKER_TIMEOUT.getInteger() ); - initializeDockerManager(); + if ( !noAutoDocker ) { + log.debug( "Setting Docker Timeouts" ); + RuntimeConfig.DOCKER_TIMEOUT.setInteger( mode == RunMode.DEVELOPMENT || mode == RunMode.TEST ? 5 : RuntimeConfig.DOCKER_TIMEOUT.getInteger() ); + performAutoSetup(); + } // Initialize plugin manager PolyPluginManager.init( resetPlugins ); @@ -438,6 +443,7 @@ public void join( final long millis ) throws InterruptedException { log.info( " http://localhost:{}", RuntimeConfig.WEBUI_SERVER_PORT.getInteger() ); log.info( "****************************************************************************************************" ); isReady = true; + server.setReady( true ); // Initialize statistic settings StatisticsManager.getInstance().initializeStatisticSettings(); @@ -465,7 +471,7 @@ public void join( final long millis ) throws InterruptedException { } - private void initializeDockerManager() { + private void performAutoSetup() { if ( AutoDocker.getInstance().isAvailable() ) { if ( mode == RunMode.TEST ) { resetDocker = true; diff --git a/dbms/src/main/java/org/polypheny/db/ddl/DdlManagerImpl.java b/dbms/src/main/java/org/polypheny/db/ddl/DdlManagerImpl.java index 2dfbacbf67..611e4ede96 100644 --- a/dbms/src/main/java/org/polypheny/db/ddl/DdlManagerImpl.java +++ b/dbms/src/main/java/org/polypheny/db/ddl/DdlManagerImpl.java @@ -208,7 +208,7 @@ public long createNamespace( String initialName, DataModel type, boolean ifNotEx @Override public void createStore( String uniqueName, String adapterName, AdapterType adapterType, Map config, DeployMode mode ) { uniqueName = uniqueName.toLowerCase(); - Adapter adapter = AdapterManager.getInstance().addAdapter( adapterName, uniqueName, adapterType, mode, config ); + AdapterManager.getInstance().addAdapter( adapterName, uniqueName, adapterType, mode, config ); } @@ -273,7 +273,10 @@ public void createSource( String uniqueName, String adapterName, long namespace, } buildNamespace( Catalog.defaultNamespaceId, logical, adapter ); - adapter.createTable( null, LogicalTableWrapper.of( logical, columns, List.of() ), AllocationTableWrapper.of( allocation.unwrap( AllocationTable.class ).orElseThrow(), aColumns ) ); + + catalog.attachCommitAction( () -> + // we can execute with initial logical and allocation data as this is a source and this will not change + adapter.createTable( null, LogicalTableWrapper.of( logical, columns, List.of() ), AllocationTableWrapper.of( allocation.unwrap( AllocationTable.class ).orElseThrow(), aColumns ) ) ); catalog.updateSnapshot(); } @@ -474,46 +477,48 @@ public void createColumn( String columnName, LogicalTable table, String beforeCo int position = updateAdjacentPositions( table, beforeColumn, afterColumn ); - LogicalColumn addedColumn = catalog.getLogicalRel( table.namespaceId ).addColumn( + LogicalColumn initialAddedColumn = catalog.getLogicalRel( table.namespaceId ).addColumn( columnName, table.id, position, - type.type, - type.collectionType, - type.precision, - type.scale, - type.dimension, - type.cardinality, + type.type(), + type.collectionType(), + type.precision(), + type.scale(), + type.dimension(), + type.cardinality(), nullable, Collation.getDefaultCollation() ); // Add default value - addedColumn = addDefaultValue( table.namespaceId, defaultValue, addedColumn ); + LogicalColumn addedColumn = addDefaultValue( table.namespaceId, defaultValue, initialAddedColumn ); // Ask router on which stores this column shall be placed List> stores = RoutingManager.getInstance().getCreatePlacementStrategy().getDataStoresForNewRelField( addedColumn ); // Add column on underlying data stores and insert default value - for ( DataStore store : stores ) { - AllocationPlacement placement = catalog.getSnapshot().alloc().getPlacement( store.getAdapterId(), table.id ).orElseThrow(); + catalog.attachCommitAction( () -> { + for ( DataStore store : stores ) { + AllocationPlacement placement = catalog.getSnapshot().alloc().getPlacement( store.getAdapterId(), table.id ).orElseThrow(); - catalog.getAllocRel( table.namespaceId ).addColumn( - placement.id, - // Will be set later - table.id, // Will be set later - addedColumn.id, - store.adapterId, - PlacementType.AUTOMATIC, - catalog.getSnapshot().alloc().getColumns( placement.id ).size() );// we just append it at the end //Not a valid partitionID --> placeholder - for ( AllocationEntity entity : catalog.getSnapshot().alloc().getAllocsOfPlacement( placement.id ) ) { - AdapterManager - .getInstance() - .getStore( store.getAdapterId() ) - .orElseThrow() - .addColumn( statement.getPrepareContext(), entity.id, addedColumn ); + catalog.getAllocRel( table.namespaceId ).addColumn( + placement.id, + // Will be set later + table.id, // Will be set later + addedColumn.id, + store.adapterId, + PlacementType.AUTOMATIC, + catalog.getSnapshot().alloc().getColumns( placement.id ).size() );// we just append it at the end //Not a valid partitionID --> placeholder + for ( AllocationEntity entity : catalog.getSnapshot().alloc().getAllocsOfPlacement( placement.id ) ) { + AdapterManager + .getInstance() + .getStore( store.getAdapterId() ) + .orElseThrow() + .addColumn( statement.getPrepareContext(), entity.id, addedColumn ); + } } - } + } ); catalog.updateSnapshot(); // Reset plan cache implementation cache & routing cache @@ -758,9 +763,12 @@ public void createAllocationPlacement( LogicalTable table, List n Catalog.getInstance().updateSnapshot(); - // Copy data to the newly added placements - DataMigrator dataMigrator = statement.getTransaction().getDataMigrator(); - dataMigrator.copyData( statement.getTransaction(), catalog.getSnapshot().getAdapter( dataStore.getAdapterId() ).orElseThrow(), table, adjustedColumns, placement ); + List finalColumns = adjustedColumns; + catalog.attachCommitAction( () -> { + // Copy data to the newly added placements + DataMigrator dataMigrator = statement.getTransaction().getDataMigrator(); + dataMigrator.copyData( statement.getTransaction(), catalog.getSnapshot().getAdapter( dataStore.getAdapterId() ).orElseThrow(), table, finalColumns, placement ); + } ); // Reset query plan cache, implementation cache & routing cache statement.getQueryProcessor().resetCaches(); @@ -808,13 +816,15 @@ public void createPrimaryKey( LogicalTable table, List columnNames, Stat placement.adapterId, PlacementType.AUTOMATIC, 0 ); - for ( AllocationPartition partition : catalog.getSnapshot().alloc().getPartitionsFromLogical( table.id ) ) { - AllocationEntity entity = catalog.getSnapshot().alloc().getAlloc( placement.id, partition.id ).orElseThrow(); - AdapterManager.getInstance().getStore( placement.adapterId ).orElseThrow().addColumn( - statement.getPrepareContext(), - entity.id, - catalog.getSnapshot().rel().getColumn( columnId ).orElseThrow() ); - } + catalog.attachCommitAction( () -> { + for ( AllocationPartition partition : catalog.getSnapshot().alloc().getPartitionsFromLogical( table.id ) ) { + AllocationEntity entity = catalog.getSnapshot().alloc().getAlloc( placement.id, partition.id ).orElseThrow(); + AdapterManager.getInstance().getStore( placement.adapterId ).orElseThrow().addColumn( + statement.getPrepareContext(), + entity.id, + catalog.getSnapshot().rel().getColumn( columnId ).orElseThrow() ); + } + } ); } } } @@ -897,14 +907,18 @@ public void dropColumn( LogicalTable table, String columnName, Statement stateme private void deleteAllocationColumn( LogicalTable table, Statement statement, AllocationColumn allocationColumn ) { if ( table.entityType == EntityType.ENTITY ) { - for ( AllocationEntity allocation : catalog.getSnapshot().alloc().getAllocsOfPlacement( allocationColumn.placementId ) ) { - AdapterManager.getInstance().getStore( allocationColumn.adapterId ) - .orElseThrow() - .dropColumn( - statement.getPrepareContext(), - allocation.id, - allocationColumn.columnId ); - } + // we use closure to cache the physical we have to delete later + List allocsOfPlacement = catalog.getSnapshot().alloc().getAllocsOfPlacement( allocationColumn.placementId ); + catalog.attachCommitAction( () -> { + for ( AllocationEntity allocation : allocsOfPlacement ) { + AdapterManager.getInstance().getStore( allocationColumn.adapterId ) + .orElseThrow() + .dropColumn( + statement.getPrepareContext(), + allocation.id, + allocationColumn.columnId ); + } + } ); } catalog.getAllocRel( table.namespaceId ).deleteColumn( allocationColumn.placementId, allocationColumn.columnId ); @@ -1002,8 +1016,11 @@ public void dropIndex( LogicalTable table, String indexName, Statement statement } else { DataStore store = AdapterManager.getInstance().getStore( index.location ).orElseThrow(); AllocationPlacement placement = Catalog.snapshot().alloc().getPlacement( store.getAdapterId(), table.id ).orElseThrow(); - catalog.getSnapshot().alloc().getAllocsOfPlacement( placement.id ).forEach( allocation -> { - store.dropIndex( statement.getPrepareContext(), index, List.of( allocation.id ) ); + + catalog.attachCommitAction( () -> { + catalog.getSnapshot().alloc().getAllocsOfPlacement( placement.id ).forEach( allocation -> { + store.dropIndex( statement.getPrepareContext(), index, List.of( allocation.id ) ); + } ); } ); } @@ -1029,12 +1046,14 @@ public void dropPlacement( LogicalTable table, DataStore store, Statement sta // Delete polystore index IndexManager.getInstance().deleteIndex( index ); } else { - // Delete index on storeId - AdapterManager.getInstance().getStore( index.location ) - .orElseThrow() - .dropIndex( - statement.getPrepareContext(), - index, catalog.getSnapshot().alloc().getPartitionsOnDataPlacement( index.location, table.id ) ); + catalog.attachCommitAction( () -> { + // Delete index on storeId + AdapterManager.getInstance().getStore( index.location ) + .orElseThrow() + .dropIndex( + statement.getPrepareContext(), + index, catalog.getSnapshot().alloc().getPartitionsOnDataPlacement( index.location, table.id ) ); + } ); } // Delete index in catalog catalog.getLogicalRel( table.namespaceId ).deleteIndex( index.id ); @@ -1059,7 +1078,9 @@ public void dropPlacement( LogicalTable table, DataStore store, Statement sta private void dropAllocation( long namespaceId, DataStore store, Statement statement, long allocId ) { // Physically delete the data from the storeId - store.dropTable( statement.getPrepareContext(), allocId ); + catalog.attachCommitAction( () -> { + store.dropTable( statement.getPrepareContext(), allocId ); + } ); catalog.getAllocRel( namespaceId ).deleteAllocation( allocId ); } @@ -1087,24 +1108,28 @@ public void setColumnType( LogicalTable table, String columnName, ColumnTypeInfo LogicalColumn logicalColumn = catalog.getSnapshot().rel().getColumn( table.id, columnName ).orElseThrow(); + checkValidType( type ); + catalog.getLogicalRel( table.namespaceId ).setColumnType( logicalColumn.id, - type.type, - type.collectionType, - type.precision, - type.scale, - type.dimension, - type.cardinality ); + type.type(), + type.collectionType(), + type.precision(), + type.scale(), + type.dimension(), + type.cardinality() ); catalog.updateSnapshot(); for ( AllocationColumn allocationColumn : catalog.getSnapshot().alloc().getColumnFromLogical( logicalColumn.id ).orElseThrow() ) { - for ( AllocationEntity allocation : catalog.getSnapshot().alloc().getAllocsOfPlacement( allocationColumn.placementId ) ) { - AdapterManager.getInstance().getStore( allocationColumn.adapterId ) - .orElseThrow() - .updateColumnType( - statement.getPrepareContext(), - allocation.id, - catalog.getSnapshot().rel().getColumn( logicalColumn.id ).orElseThrow() ); - } + catalog.attachCommitAction( () -> { + for ( AllocationEntity allocation : catalog.getSnapshot().alloc().getAllocsOfPlacement( allocationColumn.placementId ) ) { + AdapterManager.getInstance().getStore( allocationColumn.adapterId ) + .orElseThrow() + .updateColumnType( + statement.getPrepareContext(), + allocation.id, + catalog.getSnapshot().rel().getColumn( logicalColumn.id ).orElseThrow() ); + } + } ); } // Reset plan cache implementation cache & routing cache @@ -1112,6 +1137,14 @@ public void setColumnType( LogicalTable table, String columnName, ColumnTypeInfo } + private static void checkValidType( ColumnTypeInformation type ) { + // check arrays to be correctly typed + if ( type.type() == PolyType.ARRAY && type.collectionType() == null ) { + throw new GenericRuntimeException( "Array type must specify a collection type" ); + } + } + + @Override public void setColumnNullable( LogicalTable table, String columnName, boolean nullable, Statement statement ) { LogicalColumn logicalColumn = catalog.getSnapshot().rel().getColumn( table.id, columnName ).orElseThrow(); @@ -1403,25 +1436,33 @@ public void modifyPartitionPlacement( LogicalTable table, List partitionId if ( !addedPartitions.isEmpty() ) { for ( long partitionId : addedPartitions ) { - AllocationTable allocation = addAllocationTable( table.namespaceId, statement, table, columns, List.of(), placement.id, partitionId, allocationColumns, store ); - dataMigrator.copyData( statement.getTransaction(), catalog.getSnapshot().getAdapter( storeId ).orElseThrow(), table, columns, allocation ); + AllocationTable allocation = addAllocationTable( table.namespaceId, statement, table, placement.id, partitionId, store, true ); + catalog.attachCommitAction( () -> { + dataMigrator.copyData( statement.getTransaction(), catalog.getSnapshot().getAdapter( storeId ).orElseThrow(), table, columns, allocation ); + } ); } // Add indexes on this new Partition Placement if there is already an index - for ( LogicalIndex currentIndex : catalog.getSnapshot().rel().getIndexes( table.id, false ) ) { - if ( currentIndex.location == storeId ) { - store.addIndex( statement.getPrepareContext(), currentIndex, catalog.getSnapshot().alloc().getAllocsOfPlacement( placement.id ).stream().map( a -> a.unwrap( AllocationTable.class ).orElseThrow() ).toList() ); + catalog.attachCommitAction( () -> { + for ( LogicalIndex currentIndex : catalog.getSnapshot().rel().getIndexes( table.id, false ) ) { + if ( currentIndex.location == storeId ) { + store.addIndex( statement.getPrepareContext(), currentIndex, catalog.getSnapshot().alloc().getAllocsOfPlacement( placement.id ).stream().map( a -> a.unwrap( AllocationTable.class ).orElseThrow() ).toList() ); + } } - } + } ); + } if ( !removedPartitions.isEmpty() ) { // Remove indexes - for ( LogicalIndex currentIndex : catalog.getSnapshot().rel().getIndexes( table.id, false ) ) { - if ( currentIndex.location == storeId ) { - store.dropIndex( null, currentIndex, removedPartitions.stream().map( p -> p.partitionId ).toList() ); + catalog.attachCommitAction( () -> { + for ( LogicalIndex currentIndex : catalog.getSnapshot().rel().getIndexes( table.id, false ) ) { + if ( currentIndex.location == storeId ) { + store.dropIndex( null, currentIndex, removedPartitions.stream().map( p -> p.partitionId ).toList() ); + } } - } + } ); + for ( AllocationEntity removedPartition : removedPartitions ) { dropAllocation( table.namespaceId, store, statement, removedPartition.id ); } @@ -1460,27 +1501,29 @@ public void createColumnPlacement( LogicalTable table, LogicalColumn logicalColu PlacementType.MANUAL ); } else { - // Create column placement - catalog.getAllocRel( table.namespaceId ).addColumn( - placement.id, - table.id, - logicalColumn.id, - store.adapterId, - PlacementType.MANUAL, - logicalColumn.position ); - - for ( AllocationEntity allocation : catalog.getSnapshot().alloc().getAllocsOfPlacement( placement.id ) ) { - // Add column on storeId - store.addColumn( statement.getPrepareContext(), allocation.id, logicalColumn ); - // Copy the data to the newly added column placements - DataMigrator dataMigrator = statement.getTransaction().getDataMigrator(); - dataMigrator.copyData( - statement.getTransaction(), - catalog.getSnapshot().getAdapter( store.getAdapterId() ).orElseThrow(), - table, - List.of( logicalColumn ), - allocation ); - } + catalog.attachCommitAction( () -> { + // Create column placement + catalog.getAllocRel( table.namespaceId ).addColumn( + placement.id, + table.id, + logicalColumn.id, + store.adapterId, + PlacementType.MANUAL, + logicalColumn.position ); + + for ( AllocationEntity allocation : catalog.getSnapshot().alloc().getAllocsOfPlacement( placement.id ) ) { + // Add column on storeId + store.addColumn( statement.getPrepareContext(), allocation.id, logicalColumn ); + // Copy the data to the newly added column placements + DataMigrator dataMigrator = statement.getTransaction().getDataMigrator(); + dataMigrator.copyData( + statement.getTransaction(), + catalog.getSnapshot().getAdapter( store.getAdapterId() ).orElseThrow(), + table, + List.of( logicalColumn ), + allocation ); + } + } ); } @@ -1522,11 +1565,14 @@ public void dropColumnPlacement( LogicalTable table, LogicalColumn column, DataS if ( primaryKey.fieldIds.contains( column.id ) ) { throw new GenericRuntimeException( "Cannot drop primary key" ); } - for ( AllocationPartition partition : catalog.getSnapshot().alloc().getPartitionsFromLogical( table.id ) ) { - AllocationEntity allocation = catalog.getSnapshot().alloc().getAlloc( optionalPlacement.get().id, partition.id ).orElseThrow(); - // Drop Column on store - store.dropColumn( statement.getPrepareContext(), allocation.id, column.id ); - } + catalog.attachCommitAction( () -> { + for ( AllocationPartition partition : catalog.getSnapshot().alloc().getPartitionsFromLogical( table.id ) ) { + AllocationEntity allocation = catalog.getSnapshot().alloc().getAlloc( optionalPlacement.get().id, partition.id ).orElseThrow(); + // Drop Column on store + store.dropColumn( statement.getPrepareContext(), allocation.id, column.id ); + } + } ); + // Drop column placement catalog.getAllocRel( table.namespaceId ).deleteColumn( optionalPlacement.get().id, column.id ); @@ -1635,17 +1681,17 @@ public void createView( String viewName, long namespaceId, AlgNode algNode, AlgC for ( FieldInformation column : columns ) { catalog.getLogicalRel( namespaceId ).addColumn( - column.name, + column.name(), view.id, - column.position, - column.typeInformation.type, - column.typeInformation.collectionType, - column.typeInformation.precision, - column.typeInformation.scale, - column.typeInformation.dimension, - column.typeInformation.cardinality, - column.typeInformation.nullable, - column.collation ); + column.position(), + column.typeInformation().type(), + column.typeInformation().collectionType(), + column.typeInformation().precision(), + column.typeInformation().scale(), + column.typeInformation().dimension(), + column.typeInformation().cardinality(), + column.typeInformation().nullable(), + column.collation() ); } catalog.updateSnapshot(); @@ -1712,11 +1758,11 @@ public void createMaterializedView( String viewName, long namespaceId, AlgRoot a Map ids = new LinkedHashMap<>(); for ( FieldInformation field : fields ) { - ids.put( field.name, addColumn( namespaceId, field.name, field.typeInformation, field.collation, field.defaultValue, view.id, field.position ) ); + ids.put( field.name(), addColumn( namespaceId, field.name(), field.typeInformation(), field.collation(), field.defaultValue(), view.id, field.position() ) ); } // Sets previously created primary key - long pkId = ids.get( fields.get( fields.size() - 1 ).name ).id; + long pkId = ids.get( fields.get( fields.size() - 1 ).name() ).id; catalog.getLogicalRel( namespaceId ).addPrimaryKey( view.id, List.of( pkId ) ); catalog.getLogicalRel( view.namespaceId ).addConstraint( view.id, ConstraintType.PRIMARY.name(), List.of( pkId ), ConstraintType.PRIMARY ); @@ -1732,9 +1778,12 @@ public void createMaterializedView( String viewName, long namespaceId, AlgRoot a catalog.updateSnapshot(); - // Selected data from tables is added into the newly crated materialized view - MaterializedViewManager materializedManager = MaterializedViewManager.getInstance(); - materializedManager.addData( statement.getTransaction(), stores, algRoot, view ); + List> finalStores = stores; + catalog.attachCommitAction( () -> { + // Selected data from tables is added into the newly crated materialized view + MaterializedViewManager materializedManager = MaterializedViewManager.getInstance(); + materializedManager.addData( statement.getTransaction(), finalStores, algRoot, view ); + } ); } @@ -2020,7 +2069,7 @@ public void createTable( long namespaceId, String name, List f Map ids = new HashMap<>(); for ( FieldInformation information : fields ) { - ids.put( information.name, addColumn( namespaceId, information.name, information.typeInformation, information.collation, information.defaultValue, logical.id, information.position ) ); + ids.put( information.name(), addColumn( namespaceId, information.name(), information.typeInformation(), information.collation(), information.defaultValue(), logical.id, information.position() ) ); } List pkIds = new ArrayList<>(); @@ -2034,6 +2083,7 @@ public void createTable( long namespaceId, String name, List f pkIds = columnIds; } } + if ( constraints.stream().noneMatch( c -> c.type == ConstraintType.PRIMARY ) ) { // no primary was set for now, we attach condition to check on commit catalog.attachCommitConstraint( @@ -2083,7 +2133,7 @@ private List addAllocationsForPlacement( long namespaceId, Stat buildNamespace( namespaceId, logical, adapter ); List tables = new ArrayList<>(); for ( Long partitionId : partitionIds ) { - tables.add( addAllocationTable( namespaceId, statement, logical, lColumns, pkIds, placementId, partitionId, columns, adapter ) ); + tables.add( addAllocationTable( namespaceId, statement, logical, placementId, partitionId, adapter, true ) ); } return tables; } @@ -2104,10 +2154,29 @@ private PartitionProperty addBlankPartition( long namespaceId, long logicalEntit } - private AllocationTable addAllocationTable( long namespaceId, Statement statement, LogicalTable logical, List lColumns, List pkIds, long placementId, long partitionId, List aColumns, Adapter adapter ) { + private AllocationTable addAllocationTable( long namespaceId, Statement statement, LogicalTable logical, long placementId, long partitionId, Adapter adapter, boolean postpone ) { AllocationTable alloc = catalog.getAllocRel( namespaceId ).addAllocation( adapter.adapterId, placementId, partitionId, logical.id ); - adapter.createTable( statement.getPrepareContext(), LogicalTableWrapper.of( logical, sortByPosition( lColumns ), pkIds ), AllocationTableWrapper.of( alloc, aColumns ) ); + Runnable action = () -> { + List refreshedAColumns = Catalog.snapshot().alloc().getColumns( alloc.placementId ); + // we have to fetch them from scratch + LogicalTable refreshedLogical = Catalog.snapshot().rel().getTable( logical.id ).orElseThrow(); + List refreshedLColumns = Catalog.snapshot().rel().getColumns( logical.id ); + catalog.updateSnapshot(); + List refreshedPks = Catalog.snapshot().rel().getKey( refreshedLogical.primaryKey ).orElseThrow().fieldIds; + AllocationTable refreshedAlloc = Catalog.snapshot().alloc().getAlloc( alloc.placementId, alloc.partitionId ).map( e -> e.unwrap( AllocationTable.class ).orElseThrow() ).orElseThrow(); + + adapter.createTable( statement.getPrepareContext(), LogicalTableWrapper.of( refreshedLogical, sortByPosition( refreshedLColumns ), refreshedPks ), AllocationTableWrapper.of( refreshedAlloc, refreshedAColumns ) ); + }; + + if ( postpone ) { + // normally we want to postpone + catalog.attachCommitAction( action ); + } else { + // we are already committing currently + action.run(); + } + return alloc; } @@ -2285,10 +2354,12 @@ public void createTablePartition( PartitionInformation partitionInfo, List pkColumnIds = relSnapshot.getPrimaryKey( pkid ).orElseThrow().fieldIds; // This gets us only one ccp per storeId (first part of PK) - boolean fillStores = false; + boolean fillStores; if ( stores == null ) { stores = new ArrayList<>(); fillStores = true; + } else { + fillStores = false; } // Now get the partitioned table, partitionInfo still contains the basic/unpartitioned table. @@ -2300,43 +2371,48 @@ public void createTablePartition( PartitionInformation partitionInfo, List sourceAllocs = new ArrayList<>( catalog.getSnapshot().alloc().getFromLogical( unPartitionedTable.id ).stream().filter( a -> initialProperty.partitionIds.contains( a.partitionId ) ).toList() ); - for ( AllocationPlacement placement : placements ) { - if ( !fillStores ) { - continue; - } - // Ask router on which storeId(s) the table should be placed - Adapter adapter = AdapterManager.getInstance().getAdapter( placement.adapterId ).orElseThrow(); - if ( !(adapter instanceof DataStore store) ) { - continue; - } - stores.add( store ); + List> finalStores = stores; + catalog.attachCommitAction( () -> { + for ( AllocationPlacement placement : placements ) { + if ( !fillStores ) { + continue; + } + // Ask router on which storeId(s) the table should be placed + Adapter adapter = AdapterManager.getInstance().getAdapter( placement.adapterId ).orElseThrow(); + if ( !(adapter instanceof DataStore store) ) { + continue; + } + finalStores.add( store ); - List columns = catalog.getSnapshot().alloc().getColumns( placement.id ); - List partitionAllocations = new ArrayList<>(); - List logicalColumns = columns.stream().map( c -> catalog.getSnapshot().rel().getColumn( c.columnId ).orElseThrow() ).toList(); - for ( AllocationPartition partition : result.left ) { + List partitionAllocations = new ArrayList<>(); - partitionAllocations.add( addAllocationTable( partitionInfo.table.namespaceId, statement, unPartitionedTable, logicalColumns, pkColumnIds, placement.id, partition.id, columns, store ) ); - } - newAllocations.put( placement, partitionAllocations ); + for ( AllocationPartition partition : result.left ) { + partitionAllocations.add( addAllocationTable( partitionInfo.table.namespaceId, statement, unPartitionedTable, placement.id, partition.id, store, false ) ); + } - // Copy data from the old partition to new partitions - catalog.updateSnapshot(); - dataMigrator.copyAllocationData( - statement.getTransaction(), - catalog.getSnapshot().getAdapter( store.getAdapterId() ).orElseThrow(), - sourceAllocs.stream().map( s -> s.unwrap( AllocationTable.class ).orElseThrow() ).toList(), - result.right, - newAllocations.get( placement ), - unPartitionedTable ); - } + newAllocations.put( placement, partitionAllocations ); + + // Copy data from the old partition to new partitions + catalog.updateSnapshot(); + + dataMigrator.copyAllocationData( + statement.getTransaction(), + catalog.getSnapshot().getAdapter( store.getAdapterId() ).orElseThrow(), + sourceAllocs.stream().map( s -> s.unwrap( AllocationTable.class ).orElseThrow() ).toList(), + result.right, + newAllocations.get( placement ), + unPartitionedTable ); + + + } + } ); // Adjust indexes List indexes = relSnapshot.getIndexes( unPartitionedTable.id, false ); for ( LogicalIndex index : indexes ) { // Remove old index DataStore ds = AdapterManager.getInstance().getStore( index.location ).orElseThrow(); - ds.dropIndex( statement.getPrepareContext(), index, result.right.partitionIds ); + catalog.attachCommitAction( () -> ds.dropIndex( statement.getPrepareContext(), index, result.right.partitionIds ) ); catalog.getLogicalRel( partitionInfo.table.namespaceId ).deleteIndex( index.id ); // Add new index LogicalIndex newIndex = catalog.getLogicalRel( partitionInfo.table.namespaceId ).addIndex( @@ -2351,22 +2427,27 @@ public void createTablePartition( PartitionInformation partitionInfo, List e.getKey().adapterId == ds.adapterId ).findFirst().orElseThrow().getValue() );//catalog.getSnapshot().alloc().getPartitionsOnDataPlacement( ds.getAdapterId(), unPartitionedTable.id ) ); - catalog.getLogicalRel( partitionInfo.table.namespaceId ).setIndexPhysicalName( index.id, physicalName ); + catalog.attachCommitAction( () -> { + String physicalName = ds.addIndex( + statement.getPrepareContext(), + index, newAllocations.entrySet().stream().filter( e -> e.getKey().adapterId == ds.adapterId ).findFirst().orElseThrow().getValue() );//catalog.getSnapshot().alloc().getPartitionsOnDataPlacement( ds.getAdapterId(), unPartitionedTable.id ) ); + catalog.getLogicalRel( partitionInfo.table.namespaceId ).setIndexPhysicalName( index.id, physicalName ); + } ); } } - // Remove old tables - sourceAllocs.forEach( s -> deleteAllocation( statement, s ) ); + catalog.attachCommitAction( () -> { + // Remove old tables + sourceAllocs.forEach( s -> deleteAllocation( statement, s ) ); - catalog.getAllocRel( partitionInfo.table.namespaceId ).deletePartitionGroup( snapshot.alloc().getPartitionProperty( unPartitionedTable.id ).orElseThrow().partitionIds.get( 0 ) ); - initialProperty.partitionIds.forEach( id -> catalog.getAllocRel( partitionInfo.table.namespaceId ).deletePartition( id ) ); + catalog.getAllocRel( partitionInfo.table.namespaceId ).deletePartitionGroup( snapshot.alloc().getPartitionProperty( unPartitionedTable.id ).orElseThrow().partitionIds.get( 0 ) ); + initialProperty.partitionIds.forEach( id -> catalog.getAllocRel( partitionInfo.table.namespaceId ).deletePartition( id ) ); + + catalog.updateSnapshot(); + // Reset plan cache implementation cache & routing cache + statement.getQueryProcessor().resetCaches(); + } ); - catalog.updateSnapshot(); - // Reset plan cache implementation cache & routing cache - statement.getQueryProcessor().resetCaches(); } @@ -2613,17 +2694,18 @@ public void dropTablePartition( LogicalTable table, Statement statement ) throws List columns = snapshot.alloc().getColumns( placement.id ); // First create new tables - AllocationTable targetTable = addAllocationTable( table.namespaceId, statement, table, logicalColumns, pkColumnIds, placement.id, partitionProperty.left.id, columns, store ); + AllocationTable targetTable = addAllocationTable( table.namespaceId, statement, table, placement.id, partitionProperty.left.id, store, true ); catalog.updateSnapshot(); - dataMigrator.copyAllocationData( - statement.getTransaction(), - catalog.getSnapshot().getAdapter( store.getAdapterId() ).orElseThrow(), - sourceTables, - partitionProperty.right, - List.of( targetTable ), - table ); - + catalog.attachCommitAction( () -> { + dataMigrator.copyAllocationData( + statement.getTransaction(), + catalog.getSnapshot().getAdapter( store.getAdapterId() ).orElseThrow(), + sourceTables, + partitionProperty.right, + List.of( targetTable ), + table ); + } ); } @@ -2632,7 +2714,7 @@ public void dropTablePartition( LogicalTable table, Statement statement ) throws for ( LogicalIndex index : indexes ) { // Remove old index DataStore ds = AdapterManager.getInstance().getStore( index.location ).orElseThrow(); - ds.dropIndex( statement.getPrepareContext(), index, property.partitionIds ); + catalog.attachCommitAction( () -> ds.dropIndex( statement.getPrepareContext(), index, property.partitionIds ) ); catalog.getLogicalRel( table.namespaceId ).deleteIndex( index.id ); // Add new index LogicalIndex newIndex = catalog.getLogicalRel( table.namespaceId ).addIndex( @@ -2647,42 +2729,50 @@ public void dropTablePartition( LogicalTable table, Statement statement ) throws if ( index.location < 0 ) { IndexManager.getInstance().addIndex( newIndex, statement ); } else { - AllocationPlacement placement = catalog.getSnapshot().alloc().getPlacement( ds.adapterId, tableId ).orElseThrow(); - ds.addIndex( - statement.getPrepareContext(), - newIndex, - catalog.getSnapshot().alloc().getAllocsOfPlacement( placement.id ).stream().map( e -> e.unwrap( AllocationTable.class ).orElseThrow() ).collect( Collectors.toList() ) );//catalog.getSnapshot().alloc().getPartitionsOnDataPlacement( ds.getAdapterId(), mergedTable.id ) ); + catalog.attachCommitAction( () -> { + AllocationPlacement placement = catalog.getSnapshot().alloc().getPlacement( ds.adapterId, tableId ).orElseThrow(); + ds.addIndex( + statement.getPrepareContext(), + newIndex, + catalog.getSnapshot().alloc().getAllocsOfPlacement( placement.id ).stream().map( e -> e.unwrap( AllocationTable.class ).orElseThrow() ).collect( Collectors.toList() ) );//catalog.getSnapshot().alloc().getPartitionsOnDataPlacement( ds.getAdapterId(), mergedTable.id ) ); + } ); } } - // Needs to be separated from loop above. Otherwise, we loose data - sources.forEach( s -> deleteAllocation( statement, s ) ); - property.partitionIds.forEach( id -> catalog.getAllocRel( table.namespaceId ).deletePartition( id ) ); - // Loop over **old.partitionIds** to delete all partitions which are part of table - // Needs to be done separately because partitionPlacements will be recursively dropped in `deletePartitionGroup` but are needed in dropTable - for ( long partitionGroupId : property.partitionGroupIds ) { - catalog.getAllocRel( table.namespaceId ).deletePartitionGroup( partitionGroupId ); - } + catalog.attachCommitAction( () -> { + // Needs to be separated from loop above. Otherwise, we loose data + sources.forEach( s -> deleteAllocation( statement, s ) ); + property.partitionIds.forEach( id -> catalog.getAllocRel( table.namespaceId ).deletePartition( id ) ); + // Loop over **old.partitionIds** to delete all partitions which are part of table + // Needs to be done separately because partitionPlacements will be recursively dropped in `deletePartitionGroup` but are needed in dropTable + for ( long partitionGroupId : property.partitionGroupIds ) { + catalog.getAllocRel( table.namespaceId ).deletePartitionGroup( partitionGroupId ); + } + + catalog.updateSnapshot(); + // Reset query plan cache, implementation cache & routing cache + statement.getQueryProcessor().resetCaches(); + } ); - catalog.updateSnapshot(); - // Reset query plan cache, implementation cache & routing cache - statement.getQueryProcessor().resetCaches(); } private LogicalColumn addColumn( long namespaceId, String columnName, ColumnTypeInformation typeInformation, Collation collation, PolyValue defaultValue, long tableId, int position ) { columnName = adjustNameIfNeeded( columnName, namespaceId ); + // check arrays to be correctly typed + checkValidType( typeInformation ); + LogicalColumn addedColumn = catalog.getLogicalRel( namespaceId ).addColumn( columnName, tableId, position, - typeInformation.type, - typeInformation.collectionType, - typeInformation.precision, - typeInformation.scale, - typeInformation.dimension, - typeInformation.cardinality, - typeInformation.nullable, + typeInformation.type(), + typeInformation.collectionType(), + typeInformation.precision(), + typeInformation.scale(), + typeInformation.dimension(), + typeInformation.cardinality(), + typeInformation.nullable(), collation ); @@ -2880,7 +2970,7 @@ public void dropTable( LogicalTable table, Statement statement ) { private void deleteAllocation( Statement statement, AllocationEntity allocation ) { AdapterManager manager = AdapterManager.getInstance(); - manager.getStore( allocation.adapterId ).orElseThrow().dropTable( statement.getPrepareContext(), allocation.id ); + catalog.attachCommitAction( () -> manager.getStore( allocation.adapterId ).orElseThrow().dropTable( statement.getPrepareContext(), allocation.id ) ); catalog.getAllocRel( allocation.namespaceId ).deleteAllocation( allocation.id ); diff --git a/dbms/src/main/java/org/polypheny/db/ddl/DefaultInserter.java b/dbms/src/main/java/org/polypheny/db/ddl/DefaultInserter.java index 3f4b6f539c..15f692fc87 100644 --- a/dbms/src/main/java/org/polypheny/db/ddl/DefaultInserter.java +++ b/dbms/src/main/java/org/polypheny/db/ddl/DefaultInserter.java @@ -22,7 +22,7 @@ import org.polypheny.db.catalog.Catalog; import org.polypheny.db.catalog.entity.LogicalAdapter.AdapterType; import org.polypheny.db.catalog.logistic.DataModel; -import org.polypheny.db.iface.QueryInterfaceManager.QueryInterfaceTemplate; +import org.polypheny.db.util.PolyphenyHomeDirManager; import org.polypheny.db.util.RunMode; @Deterministic @@ -51,6 +51,7 @@ public static void resetData( DdlManager ddlManager, RunMode mode ) { restoreAdapters( ddlManager, catalog, mode ); + catalog.executeCommitActions(); catalog.commit(); } @@ -81,6 +82,11 @@ private static void restoreAdapters( DdlManager ddlManager, Catalog catalog, Run private static void restoreUsers( Catalog catalog ) { + if ( catalog.getUsers().values().stream().anyMatch( u -> u.getName().equals( "system" ) ) ) { + catalog.commit(); + return; + } + ////////////// // init users long systemId = catalog.createUser( "system", "" ); @@ -97,18 +103,17 @@ public static void restoreInterfacesIfNecessary() { if ( !Catalog.getInstance().getInterfaces().isEmpty() ) { return; } - Catalog.getInstance().getInterfaceTemplates().values().forEach( i -> Catalog.getInstance().createQueryInterface( i.interfaceName, i.clazz.getName(), i.defaultSettings ) ); + Catalog.getInstance().getInterfaceTemplates().values().forEach( i -> Catalog.getInstance().createQueryInterface( i.interfaceName().toLowerCase(), i.interfaceName(), i.getDefaultSettings() ) ); + // TODO: This is ugly, both because it is racy, and depends on a string (which might be changed) + if ( Catalog.getInstance().getInterfaceTemplates().values().stream().anyMatch( t -> t.interfaceName().equals( "Prism Interface (Unix transport)" ) ) ) { + Catalog.getInstance().createQueryInterface( + "prism interface (unix transport @ .polypheny)", + "Prism Interface (Unix transport)", + Map.of( "path", PolyphenyHomeDirManager.getInstance().registerNewGlobalFile( "polypheny-prism.sock" ).getAbsolutePath() ) + ); + } Catalog.getInstance().commit(); } - - public static void restoreAvatica() { - if ( Catalog.snapshot().getQueryInterface( "avatica" ).isPresent() ) { - return; - } - QueryInterfaceTemplate avatica = Catalog.snapshot().getInterfaceTemplate( "AvaticaInterface" ).orElseThrow(); - Catalog.getInstance().createQueryInterface( "avatica", avatica.clazz.getName(), avatica.defaultSettings ); - } - } diff --git a/dbms/src/main/java/org/polypheny/db/processing/AbstractQueryProcessor.java b/dbms/src/main/java/org/polypheny/db/processing/AbstractQueryProcessor.java index e2eade8f82..05d27225cd 100644 --- a/dbms/src/main/java/org/polypheny/db/processing/AbstractQueryProcessor.java +++ b/dbms/src/main/java/org/polypheny/db/processing/AbstractQueryProcessor.java @@ -558,6 +558,10 @@ private void acquireLock( boolean isAnalyze, AlgRoot logicalRoot, Map> idAccessMap = new ArrayList<>(); diff --git a/dbms/src/main/java/org/polypheny/db/processing/AuthenticatorImpl.java b/dbms/src/main/java/org/polypheny/db/processing/AuthenticatorImpl.java index 4232efbd7b..127839f9d8 100644 --- a/dbms/src/main/java/org/polypheny/db/processing/AuthenticatorImpl.java +++ b/dbms/src/main/java/org/polypheny/db/processing/AuthenticatorImpl.java @@ -17,6 +17,8 @@ package org.polypheny.db.processing; +import java.nio.charset.StandardCharsets; +import org.bouncycastle.util.Arrays; import org.polypheny.db.catalog.Catalog; import org.polypheny.db.catalog.entity.LogicalUser; import org.polypheny.db.iface.AuthenticationException; @@ -30,11 +32,11 @@ public class AuthenticatorImpl implements Authenticator { @Override public LogicalUser authenticate( final String username, final String password ) throws AuthenticationException { - LogicalUser logicalUser = Catalog.getInstance().getSnapshot().getUser( username ).orElseThrow(); - if ( logicalUser.password.equals( password ) ) { + LogicalUser logicalUser = Catalog.getInstance().getSnapshot().getUser( username ).orElseThrow( () -> new AuthenticationException( "Wrong username or password" ) ); + if ( Arrays.constantTimeAreEqual( logicalUser.password.getBytes( StandardCharsets.UTF_8 ), password.getBytes( StandardCharsets.UTF_8 ) ) ) { return logicalUser; } else { - throw new AuthenticationException( "Wrong password for user '" + username + "'!" ); + throw new AuthenticationException( "Wrong username or password" ); } } diff --git a/dbms/src/main/java/org/polypheny/db/processing/DataContextImpl.java b/dbms/src/main/java/org/polypheny/db/processing/DataContextImpl.java index 40d0870306..79aa1daf62 100644 --- a/dbms/src/main/java/org/polypheny/db/processing/DataContextImpl.java +++ b/dbms/src/main/java/org/polypheny/db/processing/DataContextImpl.java @@ -126,12 +126,12 @@ public void addAll( Map map ) { @Override - public void addParameterValues( long index, AlgDataType type, List data ) { + public void addParameterValues( long index, @NotNull AlgDataType type, @NotNull List data ) { if ( parameterTypes.containsKey( index ) ) { throw new GenericRuntimeException( "There are already values assigned to this index" ); } if ( parameterValues.isEmpty() ) { - for ( Object d : data ) { + for ( Object ignored : data ) { parameterValues.add( new HashMap<>() ); } } @@ -141,11 +141,37 @@ public void addParameterValues( long index, AlgDataType type, List da parameterTypes.put( index, type ); int i = 0; for ( PolyValue d : data ) { - parameterValues.get( i++ ).put( index, d ); + parameterValues.get( i++ ).put( index, check( d, type ) ); } } + private PolyValue check( PolyValue value, AlgDataType type ) { + if ( value == null || value.isNull() ) { + return null; + } + + switch ( type.getPolyType() ) { + case DECIMAL -> { + if ( value.asNumber().toString().replace( ".", "" ).replace( "-", "" ).length() > type.getPrecision() ) { + throw new GenericRuntimeException( "Numeric value is too long" ); + } + } + case VARCHAR, CHAR -> { + if ( type.getPrecision() >= 0 && value.asString().value.length() > type.getPrecision() ) { + throw new GenericRuntimeException( "Char value is too long" ); + } + } + case BINARY, VARBINARY -> { + if ( type.getPrecision() >= 0 && value.asBinary().length() > type.getPrecision() ) { + throw new GenericRuntimeException( "Binary value is too long" ); + } + } + } + return value; + } + + @Override public AlgDataType getParameterType( long index ) { return parameterTypes.get( index ); @@ -153,13 +179,13 @@ public AlgDataType getParameterType( long index ) { @Override - public void setParameterValues( List> values ) { + public void setParameterValues( @NotNull List> values ) { parameterValues = new ArrayList<>( values ); } @Override - public void setParameterTypes( Map types ) { + public void setParameterTypes( @NotNull Map types ) { parameterTypes = new HashMap<>( types ); } diff --git a/dbms/src/main/java/org/polypheny/db/transaction/TransactionImpl.java b/dbms/src/main/java/org/polypheny/db/transaction/TransactionImpl.java index c29472892b..81921f3093 100644 --- a/dbms/src/main/java/org/polypheny/db/transaction/TransactionImpl.java +++ b/dbms/src/main/java/org/polypheny/db/transaction/TransactionImpl.java @@ -57,6 +57,7 @@ import org.polypheny.db.processing.Processor; import org.polypheny.db.processing.QueryProcessor; import org.polypheny.db.type.entity.category.PolyNumber; +import org.polypheny.db.util.Pair; import org.polypheny.db.view.MaterializedViewManager; @@ -78,7 +79,7 @@ public class TransactionImpl implements Transaction, Comparable { private final LogicalUser user; @Getter private final LogicalNamespace defaultNamespace; - + @Getter private final TransactionManagerImpl transactionManager; @Getter @@ -157,6 +158,15 @@ public void commit() throws TransactionException { log.trace( "This transaction has already been finished!" ); return; } + + Pair isValid = Catalog.getInstance().checkIntegrity(); + if ( !isValid.left ) { + throw new TransactionException( isValid.right + "\nThere are violated constraints, the transaction was rolled back!" ); + } + + // physical changes + Catalog.getInstance().executeCommitActions(); + // Prepare to commit changes on all involved adapters and the catalog boolean okToCommit = true; if ( RuntimeConfig.TWO_PC_MODE.getBoolean() ) { @@ -215,7 +225,6 @@ public void commit() throws TransactionException { // Handover information about commit to Materialized Manager MaterializedViewManager.getInstance().updateCommittedXid( xid ); - } diff --git a/dbms/src/test/java/org/polypheny/db/TestHelper.java b/dbms/src/test/java/org/polypheny/db/TestHelper.java index 8694e157d8..84fbc1b539 100644 --- a/dbms/src/test/java/org/polypheny/db/TestHelper.java +++ b/dbms/src/test/java/org/polypheny/db/TestHelper.java @@ -40,7 +40,6 @@ import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.Properties; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -225,7 +224,7 @@ public static void executeSQL( Statement statement, String sql ) throws SQLExcep public static void executeSQL( String sql ) throws SQLException { try ( JdbcConnection jdbcConnection = new JdbcConnection( false ) ) { - try ( Statement statement = jdbcConnection.connection.createStatement() ) { + try ( Statement statement = jdbcConnection.getConnection().createStatement() ) { statement.execute( sql ); } } @@ -296,55 +295,9 @@ public static void checkResultSet( ResultSet resultSet, List expected, while ( j < expectedRow.length ) { if ( expectedRow.length >= j + 1 ) { int columnType = rsmd.getColumnType( j + 1 ); // this leads to errors if expected is different aka expected is decimal and actual is integer - if ( columnType == Types.BINARY ) { - if ( expectedRow[j] == null ) { - assertNull( row[j], "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "': " ); - } else { - assertEquals( - new String( (byte[]) expectedRow[j] ), - new String( (byte[]) row[j] ), - "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "'" ); - } - } else if ( columnType != Types.ARRAY ) { - if ( expectedRow[j] != null ) { - if ( columnType == Types.FLOAT || columnType == Types.REAL ) { - float diff = Math.abs( (float) expectedRow[j] - (float) row[j] ); - assertTrue( diff < EPSILON, - "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "': The difference between the expected float and the received float exceeds the epsilon. Difference: " + (diff - EPSILON) ); - } else if ( columnType == Types.DOUBLE ) { - double diff = Math.abs( (double) expectedRow[j] - (double) row[j] ); - assertTrue( diff < EPSILON, - "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "': The difference between the expected double and the received double exceeds the epsilon. Difference: " + (diff - EPSILON) ); - } else if ( columnType == Types.DECIMAL || (expectedRow[j] instanceof Float || expectedRow[j] instanceof Double) ) { // Decimals are exact // but not for calculations? - BigDecimal expectedResult = new BigDecimal( expectedRow[j].toString() ); - BigDecimal actualResult = new BigDecimal( row[j].toString() ); - double diff = Math.abs( expectedResult.doubleValue() - actualResult.doubleValue() ); - if ( isConvertingDecimals ) { - assertTrue( diff < EPSILON, - "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "': The difference between the expected decimal and the received decimal exceeds the epsilon. Difference: " + (diff - EPSILON) ); - } else { - assertEquals( 0, expectedResult.doubleValue() - actualResult.doubleValue(), 0.0, "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "'" ); - } - } else if ( expectedRow[j] != null && row[j] != null && expectedRow[j] instanceof Number && row[j] instanceof Number ) { - assertEquals( ((Number) expectedRow[j]).longValue(), ((Number) row[j]).longValue(), "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "'" ); - } else { - assertEquals( - expectedRow[j], - row[j], - "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "'" - ); - } - } else { - assertEquals( - expectedRow[j], - row[j], - "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "'" - ); - } + String columnName = rsmd.getColumnName( j + 1 ); - } else { - checkArray( rsmd, row, expectedRow, j ); - } + checkValue( isConvertingDecimals, row[j], expectedRow[j], columnType, columnName ); j++; } else { fail( "More data available then expected." ); @@ -355,21 +308,71 @@ public static void checkResultSet( ResultSet resultSet, List expected, } - private static void checkArray( ResultSetMetaData rsmd, Object[] row, Object[] expectedRow, int j ) throws SQLException { - List resultList = (List) row[j]; + private static void checkValue( boolean isConvertingDecimals, Object actualValue, Object expectedValue, int columnType, String columnName ) { + if ( columnType != Types.ARRAY ) { + checkScalar( columnName, actualValue, expectedValue, isConvertingDecimals, columnType ); + } else { + checkArray( columnName, actualValue, expectedValue, isConvertingDecimals, columnType ); + } + } + + + private static void checkScalar( String columnName, Object actualValue, Object expectedValue, boolean isConvertingDecimals, int columnType ) { + if ( expectedValue == null ) { + assertNull( actualValue, "Unexpected data in column '%s'".formatted( columnName ) ); + } else if ( columnType == Types.BINARY ) { + assertEquals( + new String( (byte[]) expectedValue ), + new String( (byte[]) actualValue ), + "Unexpected data in column '%s'".formatted( columnName ) ); + } else if ( columnType == Types.FLOAT || columnType == Types.REAL ) { + float diff = Math.abs( (float) expectedValue - (float) actualValue ); + assertTrue( diff < EPSILON, + "Unexpected data in column '%s': The difference between the expected float and the received float exceeds the epsilon. Difference: %s".formatted( columnName, diff - EPSILON ) ); + } else if ( columnType == Types.DOUBLE ) { + double diff = Math.abs( (double) expectedValue - (double) actualValue ); + assertTrue( diff < EPSILON, + "Unexpected data in column '%s': The difference between the expected double and the received double exceeds the epsilon. Difference: %s".formatted( columnName, diff - EPSILON ) ); + } else if ( columnType == Types.DECIMAL || (expectedValue instanceof Float || expectedValue instanceof Double) ) { // Decimals are exact // but not for calculations? + BigDecimal expectedResult = new BigDecimal( expectedValue.toString() ); + BigDecimal actualResult = new BigDecimal( actualValue.toString() ); + double diff = Math.abs( expectedResult.doubleValue() - actualResult.doubleValue() ); + if ( isConvertingDecimals ) { + assertTrue( diff < EPSILON, + "Unexpected data in column '%s': The difference between the expected decimal and the received decimal exceeds the epsilon. Difference: %s".formatted( columnName, diff - EPSILON ) ); + } else { + assertEquals( 0, expectedResult.doubleValue() - actualResult.doubleValue(), 0.0, "Unexpected data in column '%s'".formatted( columnName ) ); + } + } else if ( expectedValue instanceof Number expectedNumber && actualValue instanceof Number actualNumber ) { + assertEquals( expectedNumber.longValue(), actualNumber.longValue(), "Unexpected data in column '%s'".formatted( columnName ) ); + } else if ( expectedValue instanceof List expectedList && actualValue instanceof List actualList + && (columnType == Types.ARRAY || columnType == Types.OTHER) ) { + for ( int i = 0; i < expectedList.size(); i++ ) { + checkValue( isConvertingDecimals, actualList.get( i ), expectedList.get( i ), Types.OTHER, columnName ); + } + } else { + assertEquals( + expectedValue, + actualValue, + "Unexpected data in column '%s'".formatted( columnName ) + ); + } + + } + + + private static void checkArray( String columnName, Object actualValue, Object expectedValue, boolean isConvertingDecimals, int columnType ) { + List resultList = (List) actualValue; - if ( expectedRow[j] == null ) { - assertNull( resultList, "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "': " ); + if ( expectedValue == null ) { + assertNull( resultList, "Unexpected data in column '%s'".formatted( columnName ) ); return; } - List expectedArray = toList( (Object[]) expectedRow[j] );//(Object[]) expectedRow[j]; + List expectedArray = toList( (Object[]) expectedValue ); for ( int k = 0; k < expectedArray.size(); k++ ) { - assertEquals( - expectedArray.get( k ), - resultList.get( k ), - "Unexpected data in column '" + rsmd.getColumnName( j + 1 ) + "' at position: " + k + 1 ); + checkValue( isConvertingDecimals, resultList.get( k ), expectedArray.get( k ), Types.OTHER, columnName ); // we have rather unspecific component types for arrays } } @@ -713,33 +716,53 @@ private static GraphResult getBody( HttpResponse res ) { public static class JdbcConnection implements AutoCloseable { private final static String dbHost = "localhost"; - private final static int port = 20591; + private final static int port = 20590; - private final Connection connection; + private final Connection conn; + + + public JdbcConnection( boolean autoCommit, boolean strictMode ) throws SQLException { + try { + Class.forName( "org.polypheny.jdbc.PolyphenyDriver" ); + } catch ( ClassNotFoundException e ) { + log.error( "Polypheny JDBC Driver not found", e ); + } + final String url = "jdbc:polypheny://" + dbHost + ":" + port + "/?strict=" + strictMode; + log.debug( "Connecting to database @ {}", url ); + + conn = DriverManager.getConnection( url, "pa", "" ); + conn.setAutoCommit( autoCommit ); + } public JdbcConnection( boolean autoCommit ) throws SQLException { try { - Class.forName( "org.polypheny.jdbc.Driver" ); + Class.forName( "org.polypheny.jdbc.PolyphenyDriver" ); } catch ( ClassNotFoundException e ) { log.error( "Polypheny JDBC Driver not found", e ); } - final String url = "jdbc:polypheny:http://" + dbHost + ":" + port; + final String url = "jdbc:polypheny://" + dbHost + ":" + port + "/?strict=false"; log.debug( "Connecting to database @ {}", url ); - Properties props = new Properties(); - props.setProperty( "user", "pa" ); - props.setProperty( "serialization", "PROTOBUF" ); + conn = DriverManager.getConnection( url, "pa", "" ); + conn.setAutoCommit( autoCommit ); + } + - connection = DriverManager.getConnection( url, props ); - connection.setAutoCommit( autoCommit ); + public Connection getConnection() { + return conn; } @Override public void close() throws SQLException { - connection.commit(); - connection.close(); + if ( conn.isClosed() ) { + return; + } + if ( !conn.getAutoCommit() ) { + conn.commit(); + } + conn.close(); } } diff --git a/dbms/src/test/java/org/polypheny/db/catalog/CatalogTest.java b/dbms/src/test/java/org/polypheny/db/catalog/CatalogTest.java index 1f83d19c9a..d91f322b68 100644 --- a/dbms/src/test/java/org/polypheny/db/catalog/CatalogTest.java +++ b/dbms/src/test/java/org/polypheny/db/catalog/CatalogTest.java @@ -116,15 +116,14 @@ public void testGetCatalogs() { // Check number of columns int totalColumns = rsmd.getColumnCount(); - assertEquals( 3, totalColumns, "Wrong number of columns" ); + assertEquals( 2, totalColumns, "Wrong number of columns" ); // Check column names assertEquals( "TABLE_CAT", rsmd.getColumnName( 1 ), "Wrong column name" ); - assertEquals( "OWNER", rsmd.getColumnName( 2 ), "Wrong column name" ); - assertEquals( "DEFAULT_SCHEMA", rsmd.getColumnName( 3 ), "Wrong column name" ); + assertEquals( "DEFAULT_SCHEMA", rsmd.getColumnName( 2 ), "Wrong column name" ); // Check data - final Object[] databaseApp = new Object[]{ "APP", "system", "public" }; + final Object[] databaseApp = new Object[]{ "APP", "public" }; TestHelper.checkResultSet( connection.getMetaData().getCatalogs(), @@ -141,17 +140,19 @@ public void testGetSchema() { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { Connection connection = polyphenyDbConnection.getConnection(); - final Object[] schemaTest = new Object[]{ "schema1", "APP", "pa", "RELATIONAL" }; - final Object[] schemaPublic = new Object[]{ "public", "APP", "pa", "RELATIONAL" }; - final Object[] schemaPrivate = new Object[]{ "private", "APP", "pa", "DOCUMENT" }; + final Object[] schemaTest = new Object[]{ "schema1", null, "RELATIONAL" }; + final Object[] schemaPublic = new Object[]{ "public", null, "RELATIONAL" }; + final Object[] schemaPrivate = new Object[]{ "private", null, "DOCUMENT" }; TestHelper.checkResultSet( connection.getMetaData().getSchemas( "APP", null ), - ImmutableList.of( schemaPublic, schemaTest, schemaPrivate ), true ); + ImmutableList.of( schemaPublic, schemaTest, schemaPrivate ), + true ); TestHelper.checkResultSet( connection.getMetaData().getSchemas( "APP", "schema1" ), - ImmutableList.of( schemaTest ), true ); + ImmutableList.of( schemaTest ), + true ); } catch ( SQLException e ) { log.error( "Exception while testing getNamespaces()", e ); @@ -164,8 +165,8 @@ public void testGetTable() { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { Connection connection = polyphenyDbConnection.getConnection(); - final Object[] table1 = new Object[]{ "APP", "schema1", "table1", "ENTITY", "", null, null, null, null, null, "pa" }; - final Object[] table2 = new Object[]{ "APP", "schema1", "table2", "ENTITY", "", null, null, null, null, null, "pa" }; + final Object[] table1 = new Object[]{ null, "schema1", "table1", "ENTITY", "", null, null, null, null, null }; + final Object[] table2 = new Object[]{ null, "schema1", "table2", "ENTITY", "", null, null, null, null, null }; TestHelper.checkResultSet( connection.getMetaData().getTables( "APP", "schema1", null, null ), @@ -183,8 +184,10 @@ public void testGetColumn() { Connection connection = polyphenyDbConnection.getConnection(); // Check data - final Object[] column1 = new Object[]{ "APP", "schema1", "table1", "id", 4, "INTEGER", null, null, null, null, 0, "", null, null, null, null, 1, "NO", null }; - final Object[] column2 = new Object[]{ "APP", "schema1", "table1", "name", 12, "VARCHAR", 255, null, null, null, 1, "", null, null, null, null, 2, "YES", "CASE_INSENSITIVE" }; + final Object[] column1 = new Object[]{ null, "schema1", "table1", "id", 4, "INTEGER", null, null, null, null, + 0, "", null, null, null, null, 1, "NO", null, null, null, null, "NO", "NO", null }; + final Object[] column2 = new Object[]{ null, "schema1", "table1", "name", 12, "VARCHAR", 255, null, null, null, + 1, "", null, null, null, null, 2, "YES", null, null, null, null, "NO", "NO", "CASE_INSENSITIVE" }; TestHelper.checkResultSet( connection.getMetaData().getColumns( "APP", "schema1", "table1", null ), @@ -203,7 +206,7 @@ public void testGetIndex() { LogicalAdapter adapter = Catalog.snapshot().getAdapter( "hsqldb" ).orElseThrow(); // Check data - final Object[] index1 = new Object[]{ "APP", "schema1", "table1", false, null, "index1", 0, 1, "id", null, -1, null, null, adapter.id, 1 }; + final Object[] index1 = new Object[]{ null, "schema1", "table1", false, null, "index1", 0, 1, "id", null, -1, null, null, adapter.id, 1 }; if ( helper.storeSupportsIndex() ) { TestHelper.checkResultSet( @@ -226,7 +229,8 @@ public void testGetForeignKeys() { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { Connection connection = polyphenyDbConnection.getConnection(); - final Object[] foreignKeys = new Object[]{ "APP", "schema1", "table1", "id", "APP", "schema1", "table2", "id", 1, 1, 1, "fk_id", null, null }; + final Object[] foreignKeys = new Object[]{ null, "schema1", "table1", /* TODO was "id" */ null, + null, "schema1", "table2", "id", 1, 1, 1, "fk_id", null, null }; TestHelper.checkResultSet( connection.getMetaData().getExportedKeys( "APP", "schema1", "table1" ), diff --git a/dbms/src/test/java/org/polypheny/db/constraints/ForeignKeyConstraintTest.java b/dbms/src/test/java/org/polypheny/db/constraints/ForeignKeyConstraintTest.java index 7833dce8dc..50cbdd4a79 100644 --- a/dbms/src/test/java/org/polypheny/db/constraints/ForeignKeyConstraintTest.java +++ b/dbms/src/test/java/org/polypheny/db/constraints/ForeignKeyConstraintTest.java @@ -22,7 +22,6 @@ import java.sql.SQLException; import java.sql.Statement; import lombok.extern.slf4j.Slf4j; -import org.apache.calcite.avatica.AvaticaClientRuntimeException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -31,6 +30,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.polypheny.db.TestHelper; import org.polypheny.db.TestHelper.JdbcConnection; +import org.polypheny.jdbc.PrismInterfaceServiceException; @SuppressWarnings({ "SqlDialectInspection", "SqlNoDataSourceInspection" }) @@ -158,16 +158,8 @@ public void testInsertConflict( boolean useIndex ) throws SQLException { try { statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 1, 1, 1), (2, 2, 2, 2)" ); connection.commit(); - try { - statement.executeUpdate( "INSERT INTO constraint_test2 VALUES (3, 1), (4, 3)" ); - connection.commit(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaClientRuntimeException e ) { - if ( !(e.getMessage().contains( "Remote driver error:" ) - && e.getMessage().contains( "Transaction violates foreign key constraint" )) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - } + statement.executeUpdate( "INSERT INTO constraint_test2 VALUES (3, 1), (4, 3)" ); + Assertions.assertThrows( PrismInterfaceServiceException.class, connection::commit, "Transaction violates foreign key constraint" ); TestHelper.checkResultSet( statement.executeQuery( "SELECT COUNT(ctid) FROM constraint_test" ), ImmutableList.of( new Object[]{ 2L } ) @@ -249,15 +241,12 @@ public void testInsertSelectConflict( boolean useIndex ) throws SQLException { try { statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 1, 1, 1), (2, 2, 2, 2)" ); - try { - statement.executeUpdate( "INSERT INTO constraint_test2 SELECT ctid + 10 AS ct2id, ctid * 2 AS ctid FROM constraint_test" ); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( Throwable e ) { - if ( !(e.getMessage().contains( "Remote driver error:" ) && e.getMessage().contains( "Transaction violates foreign key constraint" )) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - connection.rollback(); - } + Assertions.assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "INSERT INTO constraint_test2 SELECT ctid + 10 AS ct2id, ctid * 2 AS ctid FROM constraint_test" ), + "Transaction violates foreign key constraint" + ); + connection.rollback(); TestHelper.checkResultSet( statement.executeQuery( "SELECT * FROM constraint_test ORDER BY ctid" ), ImmutableList.of( @@ -345,26 +334,18 @@ public void testUpdateOutConflict( boolean useIndex ) throws SQLException { statement.executeUpdate( "INSERT INTO constraint_test2 VALUES (3, 3), (1, 1)" ); connection.commit(); - try { - statement.executeUpdate( "UPDATE constraint_test2 SET ctid = ctid + 2" ); - - TestHelper.checkResultSet( - statement.executeQuery( "SELECT * FROM constraint_test2 ORDER BY ct2id" ), - ImmutableList.of( - new Object[]{ 1, 3 }, - new Object[]{ 3, 5 } - ), - false - ); - - connection.commit(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( RuntimeException e ) { - if ( !(e.getMessage().contains( "Remote driver error:" ) - && e.getMessage().contains( "Transaction violates foreign key constraint" )) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - } + statement.executeUpdate( "UPDATE constraint_test2 SET ctid = ctid + 2" ); + + TestHelper.checkResultSet( + statement.executeQuery( "SELECT * FROM constraint_test2 ORDER BY ct2id" ), + ImmutableList.of( + new Object[]{ 1, 3 }, + new Object[]{ 3, 5 } + ), + false + ); + + Assertions.assertThrows( PrismInterfaceServiceException.class, connection::commit, "Transaction violates foreign key constraint" ); TestHelper.checkResultSet( statement.executeQuery( "SELECT * FROM constraint_test ORDER BY ctid" ), ImmutableList.of( @@ -457,16 +438,8 @@ public void testUpdateInConflict( boolean useIndex ) throws SQLException { statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 1, 1, 1), (2, 2, 2, 2), (3, 3, 3, 3)" ); statement.executeUpdate( "INSERT INTO constraint_test2 VALUES (1, 1), (3, 3)" ); connection.commit(); - try { - statement.executeUpdate( "UPDATE constraint_test SET ctid = 4 WHERE ctid = 1" ); - connection.commit(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaClientRuntimeException e ) { - if ( !(e.getMessage().contains( "Remote driver error:" ) - && e.getMessage().contains( "Transaction violates foreign key constraint" )) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - } + statement.executeUpdate( "UPDATE constraint_test SET ctid = 4 WHERE ctid = 1" ); + Assertions.assertThrows( PrismInterfaceServiceException.class, connection::commit, "Transaction violates foreign key constraint" ); TestHelper.checkResultSet( statement.executeQuery( "SELECT * FROM constraint_test ORDER BY ctid" ), ImmutableList.of( @@ -556,16 +529,8 @@ public void testDeleteConflict( boolean useIndex ) throws SQLException { statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 1, 1, 1), (2, 2, 2, 2), (3, 3, 3, 3)" ); statement.executeUpdate( "INSERT INTO constraint_test2 VALUES (1, 1), (3, 3)" ); connection.commit(); - try { - statement.executeUpdate( "DELETE FROM constraint_test WHERE ctid = 1" ); - connection.commit(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaClientRuntimeException e ) { - if ( !(e.getMessage().contains( "Remote driver error:" ) - && e.getMessage().contains( "Transaction violates foreign key constraint" )) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - } + statement.executeUpdate( "DELETE FROM constraint_test WHERE ctid = 1" ); + Assertions.assertThrows( PrismInterfaceServiceException.class, connection::commit, "Transaction violates foreign key constraint" ); TestHelper.checkResultSet( statement.executeQuery( "SELECT * FROM constraint_test ORDER BY ctid" ), ImmutableList.of( diff --git a/dbms/src/test/java/org/polypheny/db/constraints/UniqueConstraintTest.java b/dbms/src/test/java/org/polypheny/db/constraints/UniqueConstraintTest.java index a150e6d346..19514b491e 100644 --- a/dbms/src/test/java/org/polypheny/db/constraints/UniqueConstraintTest.java +++ b/dbms/src/test/java/org/polypheny/db/constraints/UniqueConstraintTest.java @@ -23,7 +23,6 @@ import java.sql.SQLException; import java.sql.Statement; import lombok.extern.slf4j.Slf4j; -import org.apache.calcite.avatica.AvaticaSqlException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -32,6 +31,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.polypheny.db.TestHelper; import org.polypheny.db.TestHelper.JdbcConnection; +import org.polypheny.jdbc.PrismInterfaceServiceException; @SuppressWarnings({ "SqlDialectInspection", "SqlNoDataSourceInspection" }) @@ -138,25 +138,19 @@ public void insertExternalConflictTest( boolean useIndex ) throws SQLException { try { statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 1, 1, 1)" ); - try { - statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 2, 3, 4)" ); + Assertions.assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 2, 3, 4)" ), + "Insert violates unique constraint" + ); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { - if ( !e.getMessage().contains( "Insert violates unique constraint" ) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - } - try { - statement.executeUpdate( "INSERT INTO constraint_test VALUES (2, 1, 1, 4)" ); + Assertions.assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "INSERT INTO constraint_test VALUES (2, 1, 1, 4)" ), + "Insert violates unique constraint" + ); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { - if ( !e.getMessage().contains( "Insert violates unique constraint" ) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - connection.rollback(); - } + connection.rollback(); TestHelper.checkResultSet( statement.executeQuery( "SELECT * FROM constraint_test" ), ImmutableList.of( new Object[]{ 1, 1, 1, 1 } ) @@ -187,16 +181,13 @@ public void insertInternalConflictTest( boolean useIndex ) throws SQLException { try { statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 1, 1, 1)" ); - try { - statement.executeUpdate( "INSERT INTO constraint_test VALUES (2, 2, 3, 4), (4, 2, 3, 1)" ); + Assertions.assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "INSERT INTO constraint_test VALUES (2, 2, 3, 4), (4, 2, 3, 1)" ), + "Insert violates unique constraint" + ); + connection.rollback(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { - if ( !e.getMessage().contains( "Insert violates unique constraint" ) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - connection.rollback(); - } TestHelper.checkResultSet( statement.executeQuery( "SELECT * FROM constraint_test" ), ImmutableList.of( new Object[]{ 1, 1, 1, 1 } ) @@ -263,25 +254,13 @@ public void insertSelectExternalConflictTest( boolean useIndex ) throws SQLExcep try { statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 1, 1, 1), (2, 2, 2, 2)" ); - try { - statement.executeUpdate( "INSERT INTO constraint_test SELECT * FROM constraint_test" ); - - TestHelper.checkResultSet( - statement.executeQuery( "SELECT * FROM constraint_test ORDER BY ctid" ), - ImmutableList.of( - new Object[]{ 1, 1, 1, 1 }, - new Object[]{ 2, 2, 2, 2 } - ) - ); + Assertions.assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "INSERT INTO constraint_test SELECT * FROM constraint_test" ), + "Insert violates unique constraint" + ); + connection.rollback(); - connection.commit(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { - if ( !e.getMessage().contains( "Insert violates unique constraint" ) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - connection.rollback(); - } TestHelper.checkResultSet( statement.executeQuery( "SELECT * FROM constraint_test ORDER BY ctid" ), ImmutableList.of( @@ -315,16 +294,12 @@ public void insertSelectInternalConflictTest( boolean useIndex ) throws SQLExcep try { statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 1, 1, 5), (2, 2, 2, 5)" ); - try { - statement.executeUpdate( "INSERT INTO constraint_test SELECT c AS ctid, a + 2 AS a, b, c FROM constraint_test" ); - connection.commit(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { - if ( !e.getMessage().contains( "Insert violates unique constraint" ) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - connection.rollback(); - } + Assertions.assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "INSERT INTO constraint_test SELECT c AS ctid, a + 2 AS a, b, c FROM constraint_test" ), + "Insert violates unique constraint" + ); + connection.rollback(); TestHelper.checkResultSet( statement.executeQuery( "SELECT * FROM constraint_test ORDER BY ctid" ), ImmutableList.of( @@ -369,16 +344,12 @@ public void batchInsertTest( boolean useIndex ) throws SQLException { preparedStatement.setInt( 3, 1 ); preparedStatement.setInt( 4, 1 ); preparedStatement.addBatch(); - try { - preparedStatement.executeBatch(); - connection.commit(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { - if ( !e.getMessage().contains( "Insert violates unique constraint" ) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - connection.rollback(); - } + Assertions.assertThrows( + PrismInterfaceServiceException.class, + preparedStatement::executeBatch, + "Insert violates unique constraint" + ); + connection.rollback(); // This should work for ( int i = 1; i < 5; i++ ) { @@ -403,7 +374,7 @@ public void batchInsertTest( boolean useIndex ) throws SQLException { preparedStatement.executeBatch(); connection.commit(); Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { + } catch ( PrismInterfaceServiceException e ) { if ( !e.getMessage().contains( "Insert violates unique constraint" ) ) { throw new RuntimeException( "Unexpected exception", e ); } @@ -500,7 +471,7 @@ public void batchUpdateTest( boolean useIndex ) throws SQLException { try { preparedStatement.executeBatch(); Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { + } catch ( PrismInterfaceServiceException e ) { if ( !e.getMessage().contains( "Update violates unique constraint" ) ) { throw new RuntimeException( "Unexpected exception", e ); } @@ -570,7 +541,6 @@ public void updateNoConflictTest( boolean useIndex ) throws SQLException { } - } finally { statement.executeUpdate( "DROP TABLE constraint_test" ); } @@ -597,36 +567,12 @@ public void updateConflictTest( boolean useIndex ) throws SQLException { try { statement.executeUpdate( "INSERT INTO constraint_test VALUES (1, 1, 1, 1), (2, 2, 2, 2), (3, 3, 3, 3), (4, 4, 4, 4)" ); - try { - statement.executeUpdate( "UPDATE constraint_test SET ctid = 1" ); - connection.commit(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { - if ( !e.getMessage().contains( "Update violates unique constraint" ) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - connection.rollback(); - } - try { - statement.executeUpdate( "UPDATE constraint_test SET a = 42, b = 73" ); - connection.commit(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { - if ( !e.getMessage().contains( "Update violates unique constraint" ) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - connection.rollback(); - } - try { - statement.executeUpdate( "UPDATE constraint_test SET ctid = 4 WHERE a = 3" ); - connection.commit(); - Assertions.fail( "Expected ConstraintViolationException was not thrown" ); - } catch ( AvaticaSqlException e ) { - if ( !e.getMessage().contains( "Update violates unique constraint" ) ) { - throw new RuntimeException( "Unexpected exception", e ); - } - connection.rollback(); - } + Assertions.assertThrows( PrismInterfaceServiceException.class, () -> statement.executeUpdate( "UPDATE constraint_test SET ctid = 1" ), "Update violates unique constraint" ); + connection.rollback(); + Assertions.assertThrows( PrismInterfaceServiceException.class, () -> statement.executeUpdate( "UPDATE constraint_test SET a = 42, b = 73" ), "Update violates unique constraint" ); + connection.rollback(); + Assertions.assertThrows( PrismInterfaceServiceException.class, () -> statement.executeUpdate( "UPDATE constraint_test SET ctid = 4 WHERE a = 3" ), "Update violates unique constraint" ); + connection.rollback(); TestHelper.checkResultSet( statement.executeQuery( "SELECT * FROM constraint_test ORDER BY ctid" ), ImmutableList.of( diff --git a/dbms/src/test/java/org/polypheny/db/crossmodel/RelationalOnDocumentTest.java b/dbms/src/test/java/org/polypheny/db/crossmodel/RelationalOnDocumentTest.java index c99344abc5..4f450a071b 100644 --- a/dbms/src/test/java/org/polypheny/db/crossmodel/RelationalOnDocumentTest.java +++ b/dbms/src/test/java/org/polypheny/db/crossmodel/RelationalOnDocumentTest.java @@ -20,12 +20,12 @@ import java.sql.ResultSet; import java.util.List; -import org.bson.BsonDocument; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.polypheny.db.TestHelper; import org.polypheny.db.mql.MqlTestTemplate; +import org.polypheny.jdbc.types.PolyDocument; @SuppressWarnings({ "SqlDialectInspection", "SqlNoDataSourceInspection" }) public class RelationalOnDocumentTest extends CrossModelTestTemplate { @@ -55,17 +55,18 @@ public static void tearDown() { @Test public void simpleSelectTest() { - executeStatements( ( s, c ) -> { ResultSet result = s.executeQuery( String.format( "SELECT * FROM %s.%s", DATABASE_NAME, COLLECTION_NAME ) ); List doc = TestHelper.convertResultSetToList( result ); // contents of documents are non-deterministic, and we cannot compare them as usual through TestHelper.checkResultSet - assertEquals( BsonDocument.parse( TEST_DATA ), BsonDocument.parse( (String) doc.get( 0 )[0] ) ); + PolyDocument document = (PolyDocument) doc.get( 0 )[0]; + assertEquals( document.size(), 2 ); + assertEquals( document.get( "_id" ).asString(), "630103687f2e95058018fd9b" ); + assertEquals( document.get( "test" ).asInt(), 3 ); } ); } - @Test public void itemJsonSelectTest() { executeStatements( ( s, c ) -> { diff --git a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcConnectionTest.java b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcConnectionTest.java index 6522748b9a..ee29f6fd87 100644 --- a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcConnectionTest.java +++ b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcConnectionTest.java @@ -25,9 +25,11 @@ import com.google.common.collect.ImmutableList; import java.sql.Array; -import java.sql.CallableStatement; +import java.sql.Blob; +import java.sql.Clob; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.NClob; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; @@ -41,18 +43,20 @@ import java.util.Properties; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.polypheny.db.PolyphenyDb; import org.polypheny.db.TestHelper; import org.polypheny.db.TestHelper.JdbcConnection; +import org.polypheny.jdbc.PolyConnection; +import org.polypheny.jdbc.PrismInterfaceServiceException; +import org.polypheny.jdbc.types.PolyBlob; +import org.polypheny.jdbc.types.PolyClob; @SuppressWarnings({ "SqlDialectInspection", "SqlNoDataSourceInspection" }) @Tag("adapter") @Slf4j -@Disabled public class JdbcConnectionTest { private final static String dbHost = "localhost"; @@ -81,11 +85,10 @@ public Connection jdbcConnect( String url, Properties properties ) throws SQLExc @Test public void timeZoneTestNoTimezone() throws SQLException { - Time expected = Time.valueOf( "11:59:32" ); - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port; - String tableDdl = "CREATE TABLE test(testtime TIME NOT NULL)"; - String insert = "INSERT INTO test VALUES (time '11:59:32')"; - String select = "SELECT * FROM test"; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port; + String tableDdl = "CREATE TABLE test(id INTEGER PRIMARY KEY, testtime TIME NOT NULL)"; + String insert = "INSERT INTO test VALUES (0, time '11:59:32')"; + String select = "SELECT testtime FROM test"; try ( Connection con = jdbcConnect( url ); Statement statement = con.createStatement() ) { statement.execute( tableDdl ); @@ -98,7 +101,7 @@ public void timeZoneTestNoTimezone() throws SQLException { @Test public void autocommitUrlTest() throws SQLException { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port + "/public?autocommit=false"; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port + "/public?autocommit=false"; try ( Connection con = jdbcConnect( url ) ) { assertFalse( con.getAutoCommit() ); } @@ -107,7 +110,7 @@ public void autocommitUrlTest() throws SQLException { @Test public void readOnlyUrlTest() throws SQLException { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port + "/public?readonly=true"; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port + "/public?readonly=true"; try ( Connection con = jdbcConnect( url ) ) { assertTrue( con.isReadOnly() ); } @@ -116,29 +119,21 @@ public void readOnlyUrlTest() throws SQLException { @Test public void isolationSerializableUrlTest() { - assertThrows( SQLException.class, () -> { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port + "/public?isolation=SERIALIZABLE"; - try ( Connection con = jdbcConnect( url ) ) { - fail( "Isolation mode SERIALIZABLE is not jet supported" ); - } - } ); + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port + "/public?isolation=SERIALIZABLE"; + assertThrows( SQLException.class, () -> jdbcConnect( url ) ); } @Test public void isolationDirtyUrlTest() { - assertThrows( SQLException.class, () -> { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port + "/public?isolation=DIRTY"; - try ( Connection con = jdbcConnect( url ) ) { - fail( "Isolation mode DIRTY is not jet supported" ); - } - } ); + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port + "/public?isolation=DIRTY"; + assertThrows( SQLException.class, () -> jdbcConnect( url ) ); } @Test public void isolationCommittedUrlTest() throws SQLException { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port + "/public?isolation=COMMITTED"; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port + "/public?isolation=COMMITTED"; try ( Connection con = jdbcConnect( url ) ) { assertEquals( Connection.TRANSACTION_READ_COMMITTED, con.getTransactionIsolation() ); } @@ -147,19 +142,15 @@ public void isolationCommittedUrlTest() throws SQLException { @Test public void isolationRepeatableReadUrlTest() { - assertThrows( SQLException.class, () -> { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port + "/public?isolation=REPEATABLE_READ"; - try ( Connection con = jdbcConnect( url ) ) { - fail( "Isolation mode REPEATABLE_READ is not jet supported" ); - } - } ); + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port + "/public?isolation=REPEATABLE_READ"; + assertThrows( SQLException.class, () -> jdbcConnect( url ) ); } @Test public void networkTimeoutUrlTest() throws SQLException { int expectedTimeout = 250000; - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port + "/public?nwtimeout=" + expectedTimeout; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port + "/public?nwtimeout=" + expectedTimeout; try ( Connection con = jdbcConnect( url ) ) { assertEquals( expectedTimeout, con.getNetworkTimeout() ); } @@ -168,18 +159,14 @@ public void networkTimeoutUrlTest() throws SQLException { @Test public void resultHoldabilityHoldUrlTest() { - assertThrows( SQLException.class, () -> { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port + "/public?holdability=HOLD"; - try ( Connection con = jdbcConnect( url ) ) { - fail( "Result holdability mode HOLD is not jet supported" ); - } - } ); + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port + "/public?holdability=HOLD"; + assertThrows( SQLException.class, () -> jdbcConnect( url ) ); } @Test public void resultHoldabilityCloseUrlTest() throws SQLException { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port + "/public?holdability=CLOSE"; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port + "/public?holdability=CLOSE"; try ( Connection con = jdbcConnect( url ) ) { assertEquals( ResultSet.CLOSE_CURSORS_AT_COMMIT, con.getHoldability() ); } @@ -188,7 +175,7 @@ public void resultHoldabilityCloseUrlTest() throws SQLException { @Test public void autocommitPropertyTest() throws SQLException { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port; Properties properties = new Properties(); properties.setProperty( "autocommit", "false" ); try ( Connection con = jdbcConnect( url, properties ) ) { @@ -199,7 +186,7 @@ public void autocommitPropertyTest() throws SQLException { @Test public void readOnlyPropertyTest() throws SQLException { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port; Properties properties = new Properties(); properties.setProperty( "readonly", "true" ); try ( Connection con = jdbcConnect( url, properties ) ) { @@ -210,33 +197,25 @@ public void readOnlyPropertyTest() throws SQLException { @Test public void isolationSerializablePropertyTest() { - assertThrows( SQLException.class, () -> { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port; - Properties properties = new Properties(); - properties.setProperty( "isolation", "SERIALIZABLE" ); - try ( Connection con = jdbcConnect( url, properties ) ) { - fail( "Isolation mode SERIALIZABLE is not jet supported" ); - } - } ); + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port; + Properties properties = new Properties(); + properties.setProperty( "isolation", "SERIALIZABLE" ); + assertThrows( SQLException.class, () -> jdbcConnect( url, properties ) ); } @Test public void isolationDirtyPropertyTest() { - assertThrows( SQLException.class, () -> { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port; - Properties properties = new Properties(); - properties.setProperty( "isolation", "DIRTY" ); - try ( Connection con = jdbcConnect( url, properties ) ) { - fail( "Isolation mode DIRTY is not jet supported" ); - } - } ); + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port; + Properties properties = new Properties(); + properties.setProperty( "isolation", "DIRTY" ); + assertThrows( SQLException.class, () -> jdbcConnect( url, properties ) ); } @Test public void isolationCommittedPropertyTest() throws SQLException { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port; Properties properties = new Properties(); properties.setProperty( "isolation", "COMMITTED" ); try ( Connection con = jdbcConnect( url, properties ) ) { @@ -247,21 +226,17 @@ public void isolationCommittedPropertyTest() throws SQLException { @Test public void isolationRepeatableReadPropertyTest() { - assertThrows( SQLException.class, () -> { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port; - Properties properties = new Properties(); - properties.setProperty( "isolation", "REPEATABLE_READ" ); - try ( Connection con = jdbcConnect( url, properties ) ) { - fail( "Isolation mode EPEATABLE_READ is not jet supported" ); - } - } ); + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port; + Properties properties = new Properties(); + properties.setProperty( "isolation", "REPEATABLE_READ" ); + assertThrows( SQLException.class, () -> jdbcConnect( url, properties ) ); } @Test public void networkTimeoutPropertyTest() throws SQLException { int expectedTimeout = 250; - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port; Properties properties = new Properties(); properties.setProperty( "nwtimeout", String.valueOf( expectedTimeout ) ); try ( Connection con = jdbcConnect( url, properties ) ) { @@ -272,20 +247,16 @@ public void networkTimeoutPropertyTest() throws SQLException { @Test public void resultHoldabilityHoldPropertyTest() { - assertThrows( SQLException.class, () -> { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port; - Properties properties = new Properties(); - properties.setProperty( "holdability", "HOLD" ); - try ( Connection con = jdbcConnect( url, properties ) ) { - fail( "Result holdability mode HOLD is not jet supported" ); - } - } ); + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port; + Properties properties = new Properties(); + properties.setProperty( "holdability", "HOLD" ); + assertThrows( SQLException.class, () -> jdbcConnect( url, properties ) ); } @Test public void resultHoldabilityClosePropertyTest() throws SQLException { - String url = "jdbc:polypheny://pa:pa@" + dbHost + ":" + port; + String url = "jdbc:polypheny://pa:@" + dbHost + ":" + port; Properties properties = new Properties(); properties.setProperty( "holdability", "CLOSE" ); try ( Connection con = jdbcConnect( url, properties ) ) { @@ -294,6 +265,22 @@ public void resultHoldabilityClosePropertyTest() throws SQLException { } + @Test + public void isWrapperForTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + PolyConnection polyphenyConnection = jdbcConnection.getConnection().unwrap( PolyConnection.class ); + } + } + + + @Test + public void unwrapTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + PolyConnection polyphenyConnection = jdbcConnection.getConnection().unwrap( PolyConnection.class ); + } + } + + @Test public void isWrapperForFalseTest() throws SQLException { try ( @@ -306,25 +293,21 @@ public void isWrapperForFalseTest() throws SQLException { @Test - public void unwrapExceptionTest() { - assertThrows( SQLException.class, () -> { - try ( - JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); - Connection connection = polyphenyDbConnection.getConnection(); - ) { - PolyphenyDb polyDb = connection.unwrap( PolyphenyDb.class ); - } - } ); + public void unwrapExceptionTest() throws SQLException { + try ( + JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); + Connection connection = polyphenyDbConnection.getConnection(); + ) { + assertThrows( SQLException.class, () -> connection.unwrap( PolyphenyDb.class ) ); + } } @Test - public void illegalTimeoutBelowZeroTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - jdbcConnection.getConnection().setNetworkTimeout( null, -42 ); - } - } ); + public void illegalTimeoutBelowZeroTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + assertThrows( SQLException.class, () -> jdbcConnection.getConnection().setNetworkTimeout( null, -42 ) ); + } } @@ -340,19 +323,29 @@ public void validTimeoutTest() throws SQLException { @Test - public void abortNotSupportedTest() { - assertThrows( SQLFeatureNotSupportedException.class, () -> { + public void abortNotSupportedTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLFeatureNotSupportedException.class, () -> connection.abort( null ) ); + } + } + + + @Test + public void setInvalidNamespaceTest() { + String namespaceName = "nonexistentNamespace"; + assertThrows( PrismInterfaceServiceException.class, () -> { try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { Connection connection = jdbcConnection.getConnection(); - connection.abort( null ); + connection.setSchema( namespaceName ); } } ); } @Test - public void setNamespaceTest() throws SQLException { - String namespaceName = "testSpace"; + public void setValidNamespaceTest() throws SQLException { + String namespaceName = "public"; try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { Connection connection = jdbcConnection.getConnection(); connection.setSchema( namespaceName ); @@ -467,24 +460,50 @@ public void connectionValidTest() throws SQLException { @Test - public void sqlxmlNotSupportedTest() { - assertThrows( SQLFeatureNotSupportedException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.createSQLXML(); - } - } ); + public void sqlxmlNotSupportedTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLFeatureNotSupportedException.class, connection::createSQLXML ); + } } @Test - public void prepareCallWithParamsNotSupportedTest() { - assertThrows( SQLFeatureNotSupportedException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - CallableStatement cs = connection.prepareCall( "CALL testProcedure()" ); - } - } ); + public void createClobTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + Clob clob = connection.createClob(); + assertTrue( clob instanceof PolyClob ); + } + } + + + @Test + public void createBlobTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + Blob blob = connection.createBlob(); + assertTrue( blob instanceof PolyBlob ); + } + } + + + @Test + public void createNClobTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + NClob nclob = connection.createNClob(); + assertTrue( nclob instanceof PolyClob ); + } + } + + + @Test + public void prepareCallWithParamsNotSupportedTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLFeatureNotSupportedException.class, () -> connection.prepareCall( "CALL testProcedure()" ) ); + } } @@ -516,14 +535,12 @@ public void getWarningsWhenOpenTest() throws SQLException { @Test - public void getWarningsWhenClosedTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.close(); - connection.getWarnings(); - } - } ); + public void getWarningsWhenClosedTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + connection.close(); + assertThrows( SQLException.class, connection::getWarnings ); + } } @@ -537,62 +554,50 @@ public void clearWarningsWhenOpenTest() throws SQLException { @Test - public void clearWarningsWhenClosedTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.close(); - connection.clearWarnings(); - } - } ); + public void clearWarningsWhenClosedTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + connection.close(); + assertThrows( SQLException.class, () -> connection.clearWarnings() ); + } } @Test - public void setSavepointTest() { - assertThrows( SQLFeatureNotSupportedException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - - connection.setSavepoint(); - } - } ); + public void setSavepointTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLFeatureNotSupportedException.class, connection::setSavepoint ); + } } @Test - public void setSavepointWithNameTest() { - assertThrows( SQLFeatureNotSupportedException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - - connection.setSavepoint( "testSavepoint" ); - } - } ); + public void setSavepointWithNameTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLFeatureNotSupportedException.class, () -> connection.setSavepoint( "testSavepoint" ) ); + } } @Test - public void rollbackWithSavepointTest() { - assertThrows( SQLFeatureNotSupportedException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - Savepoint savepoint = null; - connection.rollback( savepoint ); - } - } ); + public void rollbackWithSavepointTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + Savepoint savepoint = null; + assertThrows( SQLFeatureNotSupportedException.class, () -> connection.rollback( savepoint ) ); + } } @Test - public void releaseSavepointTest() { - assertThrows( SQLFeatureNotSupportedException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - Savepoint savepoint = null; - connection.releaseSavepoint( savepoint ); - } - } ); + public void releaseSavepointTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + Savepoint savepoint = null; + assertThrows( SQLFeatureNotSupportedException.class, () -> connection.releaseSavepoint( savepoint ) ); + } } @@ -610,25 +615,21 @@ public void setHoldabilityCloseTest() throws SQLException { @Test - public void setHoldabilityHoldThrowsErrorTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.setHoldability( ResultSet.HOLD_CURSORS_OVER_COMMIT ); - } - } ); + public void setHoldabilityHoldThrowsErrorTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLException.class, () -> connection.setHoldability( ResultSet.HOLD_CURSORS_OVER_COMMIT ) ); + } } @Test public void setHoldabilityCloseOnClosedErrorTest() throws SQLException { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.close(); - connection.setHoldability( ResultSet.CLOSE_CURSORS_AT_COMMIT ); - } - } ); + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + connection.close(); + assertThrows( SQLException.class, () -> connection.setHoldability( ResultSet.CLOSE_CURSORS_AT_COMMIT ) ); + } } @@ -644,50 +645,39 @@ public void setTransactionIsolationWithValidValueTest() throws SQLException { @Test - public void setTransactionIsolationWithRepeatableReadTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - - connection.setTransactionIsolation( Connection.TRANSACTION_REPEATABLE_READ ); - } - } ); + public void setTransactionIsolationWithRepeatableReadTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLException.class, () -> connection.setTransactionIsolation( Connection.TRANSACTION_REPEATABLE_READ ) ); + } } @Test - public void setTransactionIsolationWithSerializableTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - - connection.setTransactionIsolation( Connection.TRANSACTION_SERIALIZABLE ); - } - } ); + public void setTransactionIsolationWithSerializableTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLException.class, () -> connection.setTransactionIsolation( Connection.TRANSACTION_SERIALIZABLE ) ); + } } @Test - public void setTransactionIsolationWithReadUncommittedTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - - connection.setTransactionIsolation( Connection.TRANSACTION_READ_UNCOMMITTED ); - } - } ); + public void setTransactionIsolationWithReadUncommittedTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLException.class, () -> connection.setTransactionIsolation( Connection.TRANSACTION_READ_UNCOMMITTED ) ); + } } @Test - public void setTransactionOnClosedTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.close(); - connection.setTransactionIsolation( Connection.TRANSACTION_REPEATABLE_READ ); - } - } ); + public void setTransactionOnClosedTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + connection.close(); + assertThrows( SQLException.class, () -> connection.setTransactionIsolation( Connection.TRANSACTION_REPEATABLE_READ ) ); + } } @@ -711,13 +701,11 @@ public void getCatalogWithoutSettingTest() throws SQLException { @Test - public void setCatalogWhenClosedTest() { - assertThrows( SQLException.class, () -> { - JdbcConnection jdbcConnection = new JdbcConnection( true ); - Connection connection = jdbcConnection.getConnection(); - connection.close(); - connection.setCatalog( "TestCatalog" ); - } ); + public void setCatalogWhenClosedTest() throws SQLException { + JdbcConnection jdbcConnection = new JdbcConnection( true ); + Connection connection = jdbcConnection.getConnection(); + connection.close(); + assertThrows( SQLException.class, () -> connection.setCatalog( "TestCatalog" ) ); } @@ -753,35 +741,29 @@ public void setAndCheckReadOnlyFalseTest() throws SQLException { @Test - public void setReadOnlyWhenClosedTest() { - assertThrows( SQLException.class, () -> { - JdbcConnection jdbcConnection = new JdbcConnection( true ); - Connection connection = jdbcConnection.getConnection(); - connection.close(); - connection.setReadOnly( true ); - } ); + public void setReadOnlyWhenClosedTest() throws SQLException { + JdbcConnection jdbcConnection = new JdbcConnection( true ); + Connection connection = jdbcConnection.getConnection(); + connection.close(); + assertThrows( SQLException.class, () -> connection.setReadOnly( true ) ); } @Test - public void isReadOnlyWhenClosedTest() { - assertThrows( SQLException.class, () -> { - JdbcConnection jdbcConnection = new JdbcConnection( true ); - Connection connection = jdbcConnection.getConnection(); - connection.close(); - connection.isReadOnly(); - } ); + public void isReadOnlyWhenClosedTest() throws SQLException { + JdbcConnection jdbcConnection = new JdbcConnection( true ); + Connection connection = jdbcConnection.getConnection(); + connection.close(); + assertThrows( SQLException.class, connection::isReadOnly ); } @Test - public void nativeSQLNotSupportedTest() { - assertThrows( SQLFeatureNotSupportedException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.nativeSQL( "SELECT * FROM table_name" ); - } - } ); + public void nativeSQLNotSupportedTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLFeatureNotSupportedException.class, () -> connection.nativeSQL( "SELECT * FROM table_name" ) ); + } } @@ -795,13 +777,11 @@ public void commitTransactionTest() throws SQLException { @Test - public void commitTransactionOnAutocommitTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.commit(); - } - } ); + public void commitTransactionOnAutocommitTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true, true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLException.class, connection::commit ); + } } @@ -815,59 +795,49 @@ public void rollbackTransactionTest() throws SQLException { @Test - public void rollbackTransactionOnAutocommitTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.rollback(); - } - } ); + public void rollbackTransactionOnAutocommitTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true, true ) ) { + Connection connection = jdbcConnection.getConnection(); + assertThrows( SQLException.class, connection::rollback ); + } } @Test - public void commitWhenClosedTest() { - assertThrows( SQLException.class, () -> { - JdbcConnection jdbcConnection = new JdbcConnection( true ); - Connection connection = jdbcConnection.getConnection(); - connection.close(); - connection.commit(); - } ); + public void commitWhenClosedTest() throws SQLException { + JdbcConnection jdbcConnection = new JdbcConnection( true, true ); + Connection connection = jdbcConnection.getConnection(); + connection.close(); + assertThrows( SQLException.class, connection::commit ); } @Test - public void rollbackWhenClosedTest() { - assertThrows( SQLException.class, () -> { - JdbcConnection jdbcConnection = new JdbcConnection( true ); - Connection connection = jdbcConnection.getConnection(); - connection.close(); - connection.rollback(); - } ); + public void rollbackWhenClosedTest() throws SQLException { + JdbcConnection jdbcConnection = new JdbcConnection( true, true ); + Connection connection = jdbcConnection.getConnection(); + connection.close(); + assertThrows( SQLException.class, connection::rollback ); } @Test - public void commitWithAutoCommitTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.setAutoCommit( true ); - connection.commit(); - } - } ); + public void commitWithAutoCommitTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true, true ) ) { + Connection connection = jdbcConnection.getConnection(); + connection.setAutoCommit( true ); + assertThrows( SQLException.class, connection::commit ); + } } @Test - public void rollbackWithAutoCommitTest() { - assertThrows( SQLException.class, () -> { - try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { - Connection connection = jdbcConnection.getConnection(); - connection.setAutoCommit( true ); - connection.rollback(); - } - } ); + public void rollbackWithAutoCommitTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true, true ) ) { + Connection connection = jdbcConnection.getConnection(); + connection.setAutoCommit( true ); + assertThrows( SQLException.class, connection::rollback ); + } } } diff --git a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcDdlTest.java b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcDdlTest.java index f672ed86db..1057b888d6 100644 --- a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcDdlTest.java +++ b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcDdlTest.java @@ -29,13 +29,13 @@ import java.sql.Time; import java.sql.Timestamp; import lombok.extern.slf4j.Slf4j; -import org.apache.calcite.avatica.AvaticaSqlException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.polypheny.db.TestHelper; import org.polypheny.db.TestHelper.JdbcConnection; import org.polypheny.db.type.PolyType; +import org.polypheny.jdbc.PrismInterfaceServiceException; @SuppressWarnings({ "SqlDialectInspection", "SqlNoDataSourceInspection" }) @@ -82,10 +82,10 @@ public class JdbcDdlTest { 1.999999, 9876, 0.3333f, - 45, + (short) 45, Time.valueOf( "11:59:32" ), Timestamp.valueOf( "2021-01-01 10:11:15" ), - 22, + (byte) 22, "hallo" }; @@ -208,56 +208,6 @@ public void testTimestampType() throws SQLException { } - @Test - public void testDateType2() throws SQLException { - try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( true ) ) { - Connection connection = polyphenyDbConnection.getConnection(); - try ( Statement statement = connection.createStatement() ) { - // Create ddltest table and insert data - statement.executeUpdate( DDLTEST_SQL ); - statement.executeUpdate( DDLTEST_DATA_SQL ); - - try { - // Checks - TestHelper.checkResultSet( - statement.executeQuery( "SELECT tdate FROM ddltest" ), - ImmutableList.of( new Object[]{ DDLTEST_DATA[2] } ) ); - - connection.commit(); - } finally { - // Drop ddltest table - statement.executeUpdate( "DROP TABLE ddltest" ); - } - } - } - } - - - @Test - public void testTimestampType3() throws SQLException { - try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( true ) ) { - Connection connection = polyphenyDbConnection.getConnection(); - try ( Statement statement = connection.createStatement() ) { - // Create ddltest table and insert data - statement.executeUpdate( DDLTEST_SQL ); - statement.executeUpdate( DDLTEST_DATA_SQL ); - - try { - // Checks - TestHelper.checkResultSet( - statement.executeQuery( "SELECT ttimestamp FROM ddltest" ), - ImmutableList.of( new Object[]{ DDLTEST_DATA[9] } ) ); - - connection.commit(); - } finally { - // Drop ddltest table - statement.executeUpdate( "DROP TABLE ddltest" ); - } - } - } - } - - @Test public void viewTestTypes() throws SQLException { // Check if there are new types missing in this test @@ -496,7 +446,7 @@ public void notNullTest() throws SQLException { boolean failed = false; try { statement.executeUpdate( "INSERT INTO ddltest(tprimary) VALUES ( null, null, null, null, null, 1, null, null, null, null, null )" ); - } catch ( AvaticaSqlException e ) { + } catch ( PrismInterfaceServiceException e ) { failed = true; } assertTrue( failed ); @@ -778,13 +728,13 @@ public void changeColumnTest() throws SQLException { statement.executeUpdate( "ALTER TABLE ddltest MODIFY COLUMN tsmallint SET TYPE INTEGER" ); TestHelper.checkResultSet( statement.executeQuery( "SELECT tsmallint FROM ddltest" ), - ImmutableList.of( new Object[]{ DDLTEST_DATA[7] } ) ); + ImmutableList.of( new Object[]{ (short) DDLTEST_DATA[7] } ) ); // TinyInt --> SmallInt statement.executeUpdate( "ALTER TABLE ddltest MODIFY COLUMN ttinyint SET TYPE SMALLINT" ); TestHelper.checkResultSet( statement.executeQuery( "SELECT ttinyint FROM ddltest" ), - ImmutableList.of( new Object[]{ DDLTEST_DATA[10] } ) ); + ImmutableList.of( new Object[]{ (byte) DDLTEST_DATA[10] } ) ); } finally { // Drop ddltest table statement.executeUpdate( "DROP TABLE ddltest" ); diff --git a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcMetaTest.java b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcMetaTest.java index 62d386a8ef..5899a416d3 100644 --- a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcMetaTest.java +++ b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcMetaTest.java @@ -17,13 +17,20 @@ package org.polypheny.db.jdbc; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import com.google.common.collect.ImmutableList; import java.sql.Connection; +import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; +import java.util.LinkedList; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterAll; @@ -39,6 +46,14 @@ @Slf4j public class JdbcMetaTest { + private static final String CREATE_TEST_TABLE = "CREATE TABLE IF NOT EXISTS my_table (id INT PRIMARY KEY, some_value INT)"; + private static final String INSERT_TEST_DATA = "INSERT INTO my_table (id, some_value) VALUES " + + "(1, 10), " + + "(2, NULL), " + + "(3, 5), " + + "(4, NULL), " + + "(5, 8)"; + private static TestHelper helper; @@ -117,7 +132,7 @@ public void testMetaGetTables() throws SQLException { // Check number of columns int totalColumns = rsmd.getColumnCount(); - assertEquals( 11, totalColumns, "Wrong number of columns" ); + assertEquals( 10, totalColumns, "Wrong number of columns" ); // Check column names assertEquals( "TABLE_CAT", rsmd.getColumnName( 1 ), "Wrong column name" ); @@ -130,11 +145,10 @@ public void testMetaGetTables() throws SQLException { assertEquals( "TYPE_NAME", rsmd.getColumnName( 8 ), "Wrong column name" ); assertEquals( "SELF_REFERENCING_COL_NAME", rsmd.getColumnName( 9 ), "Wrong column name" ); assertEquals( "REF_GENERATION", rsmd.getColumnName( 10 ), "Wrong column name" ); - assertEquals( "OWNER", rsmd.getColumnName( 11 ), "Wrong column name" ); // Check data - final Object[] tableFoo = new Object[]{ "APP", "public", "foo", "ENTITY", "", null, null, null, null, null, "pa" }; - final Object[] tableFoo2 = new Object[]{ "APP", "test", "foo2", "ENTITY", "", null, null, null, null, null, "pa" }; + final Object[] tableFoo = new Object[]{ null, "public", "foo", "ENTITY", "", null, null, null, null, null }; + final Object[] tableFoo2 = new Object[]{ null, "test", "foo2", "ENTITY", "", null, null, null, null, null, }; TestHelper.checkResultSet( connection.getMetaData().getTables( "APP", null, "foo", null ), ImmutableList.of( tableFoo ) ); @@ -154,55 +168,6 @@ public void testMetaGetTables() throws SQLException { } - @Test - public void testMetaGetColumns() throws SQLException { - try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { - Connection connection = polyphenyDbConnection.getConnection(); - ResultSet resultSet = connection.getMetaData().getColumns( null, null, null, null ); - ResultSetMetaData rsmd = resultSet.getMetaData(); - - // Check number of columns - int totalColumns = rsmd.getColumnCount(); - assertEquals( 19, totalColumns, "Wrong number of columns" ); - - // Check column names - assertEquals( "TABLE_CAT", rsmd.getColumnName( 1 ) ); - assertEquals( "TABLE_SCHEM", rsmd.getColumnName( 2 ) ); - assertEquals( "TABLE_NAME", rsmd.getColumnName( 3 ) ); - assertEquals( "COLUMN_NAME", rsmd.getColumnName( 4 ) ); - assertEquals( "DATA_TYPE", rsmd.getColumnName( 5 ) ); - assertEquals( "TYPE_NAME", rsmd.getColumnName( 6 ) ); - assertEquals( "COLUMN_SIZE", rsmd.getColumnName( 7 ) ); - assertEquals( "BUFFER_LENGTH", rsmd.getColumnName( 8 ) ); - assertEquals( "DECIMAL_DIGITS", rsmd.getColumnName( 9 ) ); - assertEquals( "NUM_PREC_RADIX", rsmd.getColumnName( 10 ) ); - assertEquals( "NULLABLE", rsmd.getColumnName( 11 ) ); - assertEquals( "REMARKS", rsmd.getColumnName( 12 ) ); - assertEquals( "COLUMN_DEF", rsmd.getColumnName( 13 ) ); - assertEquals( "SQL_DATA_TYPE", rsmd.getColumnName( 14 ) ); - assertEquals( "SQL_DATETIME_SUB", rsmd.getColumnName( 15 ) ); - assertEquals( "CHAR_OCTET_LENGTH", rsmd.getColumnName( 16 ) ); - assertEquals( "ORDINAL_POSITION", rsmd.getColumnName( 17 ) ); - assertEquals( "IS_NULLABLE", rsmd.getColumnName( 18 ) ); - assertEquals( "COLLATION", rsmd.getColumnName( 19 ) ); - - // Check data - final Object[] columnId = new Object[]{ "APP", "public", "foo", "id", 4, "INTEGER", null, null, null, null, 0, "", null, null, null, null, 1, "NO", null }; - final Object[] columnName = new Object[]{ "APP", "public", "foo", "name", 12, "VARCHAR", 20, null, null, null, 1, "", null, null, null, null, 2, "YES", "CASE_INSENSITIVE" }; - final Object[] columnBar = new Object[]{ "APP", "public", "foo", "bar", 12, "VARCHAR", 33, null, null, null, 1, "", null, null, null, null, 3, "YES", "CASE_SENSITIVE" }; - TestHelper.checkResultSet( - connection.getMetaData().getColumns( "APP", null, "foo", null ), - ImmutableList.of( columnId, columnName, columnBar ) ); - TestHelper.checkResultSet( - connection.getMetaData().getColumns( "APP", null, "foo", "id" ), - ImmutableList.of( columnId ) ); - TestHelper.checkResultSet( - connection.getMetaData().getColumns( "APP", null, "foo", "id%" ), - ImmutableList.of( columnId ) ); - } - } - - @Test public void testMetaGetSchemas() throws SQLException { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { @@ -212,18 +177,17 @@ public void testMetaGetSchemas() throws SQLException { // Check number of columns int totalColumns = rsmd.getColumnCount(); - assertEquals( 4, totalColumns, "Wrong number of columns" ); + assertEquals( 3, totalColumns, "Wrong number of columns" ); // Check column names assertEquals( "TABLE_SCHEM", rsmd.getColumnName( 1 ) ); assertEquals( "TABLE_CATALOG", rsmd.getColumnName( 2 ) ); - assertEquals( "OWNER", rsmd.getColumnName( 3 ) ); - assertEquals( "SCHEMA_TYPE", rsmd.getColumnName( 4 ) ); + assertEquals( "SCHEMA_TYPE", rsmd.getColumnName( 3 ) ); // Check data - final Object[] schemaPublic = new Object[]{ "public", "APP", "pa", "RELATIONAL" }; + final Object[] schemaPublic = new Object[]{ "public", null, "RELATIONAL" }; //final Object[] schemaDoc = new Object[]{ "doc", "APP", "pa", "DOCUMENT" }; - final Object[] schemaTest = new Object[]{ "test", "APP", "pa", "RELATIONAL" }; + final Object[] schemaTest = new Object[]{ "test", null, "RELATIONAL" }; TestHelper.checkResultSet( connection.getMetaData().getSchemas( "APP", null ), @@ -244,6 +208,102 @@ public void testMetaGetSchemas() throws SQLException { } + @Test + public void testColumnPrivilegesThrowsExceptionIfStrict() { + assertThrows( SQLFeatureNotSupportedException.class, () -> { + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false, true ); + Connection connection = polyphenyDbConnection.getConnection() ) { + DatabaseMetaData metadata = connection.getMetaData(); + ResultSet rs = metadata.getColumnPrivileges( null, null, null, null ); + } + } ); + } + + + @Test + public void testColumnPrivilegesReturnsDummy() throws SQLException { + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false, false ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + ResultSet resultSet = connection.getMetaData().getColumnPrivileges( null, "test", null, null ); + ResultSetMetaData rsmd = resultSet.getMetaData(); + + // Check number of columns + int totalColumns = rsmd.getColumnCount(); + assertEquals( 8, totalColumns, "Wrong number of columns" ); + + // Check column names + assertEquals( rsmd.getColumnName( 1 ), "TABLE_CAT", "Wrong column name" ); + assertEquals( rsmd.getColumnName( 2 ), "TABLE_SCHEM", "Wrong column name" ); + assertEquals( rsmd.getColumnName( 3 ), "TABLE_NAME", "Wrong column name" ); + assertEquals( rsmd.getColumnName( 4 ), "COLUMN_NAME", "Wrong column name" ); + assertEquals( rsmd.getColumnName( 5 ), "GRANTOR", "Wrong column name" ); + assertEquals( rsmd.getColumnName( 6 ), "GRANTEE", "Wrong column name" ); + assertEquals( rsmd.getColumnName( 7 ), "PRIVILEGE", "Wrong column name" ); + assertEquals( rsmd.getColumnName( 8 ), "IS_GRANTABLE", "Wrong column name" ); + + // Check data + final List expected = new LinkedList<>(); + expected.add( new Object[]{ null, "test", "foo2", "name", null, "pa", "INSERT", "NO" } ); + expected.add( new Object[]{ null, "test", "foo2", "name", null, "pa", "REFERENCE", "NO" } ); + expected.add( new Object[]{ null, "test", "foo2", "name", null, "pa", "SELECT", "NO" } ); + expected.add( new Object[]{ null, "test", "foo2", "name", null, "pa", "UPDATE", "NO" } ); + + TestHelper.checkResultSet( + connection.getMetaData().getColumnPrivileges( null, "test", "foo2", "name" ), + expected ); + } + } + + + @Test + public void testTablePrivilegesThrowsExceptionIfStrict() { + assertThrows( SQLFeatureNotSupportedException.class, () -> { + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false, true ); + Connection connection = polyphenyDbConnection.getConnection() ) { + DatabaseMetaData metadata = connection.getMetaData(); + ResultSet rs = metadata.getTablePrivileges( null, null, null ); + } + } ); + } + + + @Test + public void testTablePrivilegesReturnsDummy() { + assertThrows( SQLFeatureNotSupportedException.class, () -> { + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false, true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + ResultSet resultSet = connection.getMetaData().getTablePrivileges( null, "test", "foo2" ); + ResultSetMetaData rsmd = resultSet.getMetaData(); + + // Check number of columns + int totalColumns = rsmd.getColumnCount(); + assertEquals( 4, totalColumns, "Wrong number of columns" ); + + // Check column names + assertEquals( "TABLE_CAT", rsmd.getColumnName( 1 ), "Wrong column name" ); + assertEquals( "TABLE_SCHEM", rsmd.getColumnName( 2 ), "Wrong column name" ); + assertEquals( "TABLE_NAME", rsmd.getColumnName( 3 ), "Wrong column name" ); + assertEquals( "GRANTOR", rsmd.getColumnName( 4 ), "Wrong column name" ); + assertEquals( "GRANTEE", rsmd.getColumnName( 5 ), "Wrong column name" ); + assertEquals( "PRIVILEGE", rsmd.getColumnName( 6 ), "Wrong column name" ); + assertEquals( "IS_GRANTABLE", rsmd.getColumnName( 7 ), "Wrong column name" ); + + // Check data + final List expected = new LinkedList<>(); + expected.add( new Object[]{ "APP", "test", "foo2", null, "pa", "DELETE", "NO" } ); + expected.add( new Object[]{ "APP", "test", "foo2", null, "pa", "INSERT", "NO" } ); + expected.add( new Object[]{ "APP", "test", "foo2", null, "pa", "REFERENCE", "NO" } ); + expected.add( new Object[]{ "APP", "test", "foo2", null, "pa", "SELECT", "NO" } ); + expected.add( new Object[]{ "APP", "test", "foo2", null, "pa", "UPDATE", "NO" } ); + + TestHelper.checkResultSet( + connection.getMetaData().getTablePrivileges( null, "test", "foo2" ), + expected ); + } + } ); + } + + @Test public void testGetCatalogs() throws SQLException { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { @@ -253,15 +313,14 @@ public void testGetCatalogs() throws SQLException { // Check number of columns int totalColumns = rsmd.getColumnCount(); - assertEquals( 3, totalColumns, "Wrong number of columns" ); + assertEquals( 2, totalColumns, "Wrong number of columns" ); // Check column names assertEquals( "TABLE_CAT", rsmd.getColumnName( 1 ) ); - assertEquals( "OWNER", rsmd.getColumnName( 2 ) ); - assertEquals( "DEFAULT_SCHEMA", rsmd.getColumnName( 3 ) ); + assertEquals( "DEFAULT_SCHEMA", rsmd.getColumnName( 2 ) ); // Check data - final Object[] databaseApp = new Object[]{ "APP", "system", "public" }; + final Object[] databaseApp = new Object[]{ "APP", "public" }; TestHelper.checkResultSet( connection.getMetaData().getCatalogs(), @@ -294,6 +353,134 @@ public void testGetTableTypes() throws SQLException { } + @Test + public void testSortNullsAtEnd() throws SQLException { + try ( + JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); + Connection connection = polyphenyDbConnection.getConnection(); + Statement statement = connection.createStatement() + ) { + statement.execute( CREATE_TEST_TABLE ); + statement.executeUpdate( INSERT_TEST_DATA ); + + ResultSet rs = statement.executeQuery( "SELECT * FROM my_table ORDER BY some_value IS NULL, some_value" ); + + boolean trigger = false; + while ( rs.next() ) { + Integer value = rs.getInt( "some_value" ); + if ( value == 0 ) { + trigger = true; + } else if ( trigger && value != null ) { + fail( "Values are not sorted correctly." ); + } + } + + ResultSet rs2 = statement.executeQuery( "SELECT * FROM my_table ORDER BY some_value IS NULL, some_value" ); + + trigger = false; + while ( rs2.next() ) { + Integer value = rs2.getInt( "some_value" ); + if ( value == 0 ) { + trigger = true; + } else if ( trigger && value != null ) { + fail( "Values are not sorted correctly." ); + } + } + } + } + + + @Test + public void testNullsAreSortedAtEnd() throws SQLException { + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false, true ); + Connection connection = polyphenyDbConnection.getConnection() ) { + assertTrue( connection.getMetaData().nullsAreSortedAtEnd() ); + } + } + + + @Test + public void testNullsAreSortedStart() throws SQLException { + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false, true ); + Connection connection = polyphenyDbConnection.getConnection() ) { + assertFalse( connection.getMetaData().nullsAreSortedAtStart() ); + } + } + + + @Test + public void testNullsAreSortedHigh() throws SQLException { + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false, true ); + Connection connection = polyphenyDbConnection.getConnection() ) { + assertFalse( connection.getMetaData().nullsAreSortedHigh() ); + } + } + + + @Test + public void testNullsAreSortedLow() throws SQLException { + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false, true ); + Connection connection = polyphenyDbConnection.getConnection() ) { + assertFalse( connection.getMetaData().nullsAreSortedLow() ); + } + } + + + @Test + public void testMetaGetColumns() throws SQLException { + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + ResultSet resultSet = connection.getMetaData().getColumns( null, null, null, null ); + ResultSetMetaData rsmd = resultSet.getMetaData(); + + // Check number of columns + int totalColumns = rsmd.getColumnCount(); + assertEquals( 25, totalColumns, "Wrong number of columns" ); + + // Check column names + assertEquals( "TABLE_CAT", rsmd.getColumnName( 1 ), "Wrong column name" ); + assertEquals( "TABLE_SCHEM", rsmd.getColumnName( 2 ), "Wrong column name" ); + assertEquals( "TABLE_NAME", rsmd.getColumnName( 3 ), "Wrong column name" ); + assertEquals( "COLUMN_NAME", rsmd.getColumnName( 4 ), "Wrong column name" ); + assertEquals( "DATA_TYPE", rsmd.getColumnName( 5 ), "Wrong column name" ); + assertEquals( "TYPE_NAME", rsmd.getColumnName( 6 ), "Wrong column name" ); + assertEquals( "COLUMN_SIZE", rsmd.getColumnName( 7 ), "Wrong column name" ); + assertEquals( "BUFFER_LENGTH", rsmd.getColumnName( 8 ), "Wrong column name" ); + assertEquals( "DECIMAL_DIGITS", rsmd.getColumnName( 9 ), "Wrong column name" ); + assertEquals( "NUM_PREC_RADIX", rsmd.getColumnName( 10 ), "Wrong column name" ); + assertEquals( "NULLABLE", rsmd.getColumnName( 11 ), "Wrong column name" ); + assertEquals( "REMARKS", rsmd.getColumnName( 12 ), "Wrong column name" ); + assertEquals( "COLUMN_DEF", rsmd.getColumnName( 13 ), "Wrong column name" ); + assertEquals( "SQL_DATA_TYPE", rsmd.getColumnName( 14 ), "Wrong column name" ); + assertEquals( "SQL_DATETIME_SUB", rsmd.getColumnName( 15 ), "Wrong column name" ); + assertEquals( "CHAR_OCTET_LENGTH", rsmd.getColumnName( 16 ), "Wrong column name" ); + assertEquals( "ORDINAL_POSITION", rsmd.getColumnName( 17 ), "Wrong column name" ); + assertEquals( "IS_NULLABLE", rsmd.getColumnName( 18 ), "Wrong column name" ); + assertEquals( "SCOPE_CATALOG", rsmd.getColumnName( 19 ), "Wrong column name" ); + assertEquals( "SCOPE_SCHEMA", rsmd.getColumnName( 20 ), "Wrong column name" ); + assertEquals( "SCOPE_TABLE", rsmd.getColumnName( 21 ), "Wrong column name" ); + assertEquals( "SOURCE_DATA_TYPE", rsmd.getColumnName( 22 ), "Wrong column name" ); + assertEquals( "IS_AUTOINCREMENT", rsmd.getColumnName( 23 ), "Wrong column name" ); + assertEquals( "IS_GENERATEDCOLUMN", rsmd.getColumnName( 24 ), "Wrong column name" ); + assertEquals( "COLLATION", rsmd.getColumnName( 25 ), "Wrong column name" ); + + // Check data + final Object[] columnId = new Object[]{ null, "public", "foo", "id", 4, "INTEGER", null, null, null, null, 0, "", null, null, null, null, 1, "NO", null, null, null, null, "NO", "NO", null }; + final Object[] columnName = new Object[]{ null, "public", "foo", "name", 12, "VARCHAR", 20, null, null, null, 1, "", null, null, null, null, 2, "YES", null, null, null, null, "NO", "NO", "CASE_INSENSITIVE" }; + final Object[] columnBar = new Object[]{ null, "public", "foo", "bar", 12, "VARCHAR", 33, null, null, null, 1, "", null, null, null, null, 3, "YES", null, null, null, null, "NO", "NO", "CASE_SENSITIVE" }; + TestHelper.checkResultSet( + connection.getMetaData().getColumns( "APP", null, "foo", null ), + ImmutableList.of( columnId, columnName, columnBar ) ); + TestHelper.checkResultSet( + connection.getMetaData().getColumns( "APP", null, "foo", "id" ), + ImmutableList.of( columnId ) ); + TestHelper.checkResultSet( + connection.getMetaData().getColumns( "APP", null, "foo", "id%" ), + ImmutableList.of( columnId ) ); + } + } + + @Test public void testGetPrimaryKeys() throws SQLException { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { @@ -314,9 +501,9 @@ public void testGetPrimaryKeys() throws SQLException { assertEquals( "PK_NAME", rsmd.getColumnName( 6 ) ); // Check data - final Object[] primaryKey = new Object[]{ "APP", "public", "foo", "id", 1, null }; - final Object[] compositePrimaryKey1 = new Object[]{ "APP", "test", "foo2", "id", 1, null }; - final Object[] compositePrimaryKey2 = new Object[]{ "APP", "test", "foo2", "name", 2, null }; + final Object[] primaryKey = new Object[]{ null, "public", "foo", "id", 1, null }; + final Object[] compositePrimaryKey1 = new Object[]{ null, "test", "foo2", "id", 1, null }; + final Object[] compositePrimaryKey2 = new Object[]{ null, "test", "foo2", "name", 2, null }; TestHelper.checkResultSet( connection.getMetaData().getPrimaryKeys( "APP", "public", "foo" ), @@ -365,9 +552,9 @@ public void testGetImportedKeys() throws SQLException { assertEquals( "DEFERRABILITY", rsmd.getColumnName( 14 ) ); // Check data - final Object[] foreignKey1a = new Object[]{ "APP", "test", "foo2", "name", "APP", "public", "foo", "name", 1, 1, 1, "fk_foo_1", null, null }; - final Object[] foreignKey1b = new Object[]{ "APP", "test", "foo2", "foobar", "APP", "public", "foo", "bar", 2, 1, 1, "fk_foo_1", null, null }; - final Object[] foreignKey2 = new Object[]{ "APP", "public", "foo", "id", "APP", "test", "foo2", "id", 1, 1, 1, "fk_foo_2", null, null }; + final Object[] foreignKey1a = new Object[]{ null, "test", "foo2", null, null, "public", "foo", "name", 1, 1, 1, "fk_foo_1", null, null }; + final Object[] foreignKey1b = new Object[]{ null, "test", "foo2", null, null, "public", "foo", "bar", 2, 1, 1, "fk_foo_1", null, null }; + final Object[] foreignKey2 = new Object[]{ null, "public", "foo", null, null, "test", "foo2", "id", 1, 1, 1, "fk_foo_2", null, null }; TestHelper.checkResultSet( connection.getMetaData().getImportedKeys( "APP", "public", "foo" ), @@ -414,9 +601,9 @@ public void testGetExportedKeys() throws SQLException { assertEquals( "DEFERRABILITY", rsmd.getColumnName( 14 ) ); // Check data - final Object[] foreignKey1a = new Object[]{ "APP", "test", "foo2", "name", "APP", "public", "foo", "name", 1, 1, 1, "fk_foo_1", null, null }; - final Object[] foreignKey1b = new Object[]{ "APP", "test", "foo2", "foobar", "APP", "public", "foo", "bar", 2, 1, 1, "fk_foo_1", null, null }; - final Object[] foreignKey2 = new Object[]{ "APP", "public", "foo", "id", "APP", "test", "foo2", "id", 1, 1, 1, "fk_foo_2", null, null }; + final Object[] foreignKey1a = new Object[]{ null, "test", "foo2", null, null, "public", "foo", "name", 1, 1, 1, "fk_foo_1", null, null }; + final Object[] foreignKey1b = new Object[]{ null, "test", "foo2", null, null, "public", "foo", "bar", 2, 1, 1, "fk_foo_1", null, null }; + final Object[] foreignKey2 = new Object[]{ null, "public", "foo", null, null, "test", "foo2", "id", 1, 1, 1, "fk_foo_2", null, null }; TestHelper.checkResultSet( connection.getMetaData().getExportedKeys( "APP", "public", "foo" ), @@ -498,9 +685,9 @@ public void testGetIndexInfo() throws SQLException { assertEquals( "INDEX_TYPE", rsmd.getColumnName( 15 ) ); // Check data - final Object[] index1 = new Object[]{ "APP", "public", "foo", false, null, "i_foo", 0, 1, "id", null, -1, null, null, 0, 1 }; - final Object[] index2a = new Object[]{ "APP", "test", "foo2", true, null, "i_foo2", 0, 1, "name", null, -1, null, null, 0, 1 }; - final Object[] index2b = new Object[]{ "APP", "test", "foo2", true, null, "i_foo2", 0, 2, "foobar", null, -1, null, null, 0, 1 }; + final Object[] index1 = new Object[]{ null, "public", "foo", false, null, "i_foo", 0, 1, "id", null, -1, null, null, 0, 1 }; + final Object[] index2a = new Object[]{ null, "test", "foo2", true, null, "i_foo2", 0, 1, "name", null, -1, null, null, 0, 1 }; + final Object[] index2b = new Object[]{ null, "test", "foo2", true, null, "i_foo2", 0, 2, "foobar", null, -1, null, null, 0, 1 }; if ( !helper.storeSupportsIndex() ) { return; diff --git a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcPreparedStatementsTest.java b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcPreparedStatementsTest.java index 8de30ced39..11ae175ff9 100644 --- a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcPreparedStatementsTest.java +++ b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcPreparedStatementsTest.java @@ -16,13 +16,14 @@ package org.polypheny.db.jdbc; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.collect.ImmutableList; import java.math.BigDecimal; import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Time; @@ -33,7 +34,9 @@ import org.apache.calcite.avatica.SqlType; import org.apache.calcite.avatica.util.ArrayFactoryImpl; import org.apache.calcite.avatica.util.Unsafe; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.polypheny.db.TestHelper; @@ -44,6 +47,10 @@ @Tag("adapter") public class JdbcPreparedStatementsTest { + private static final String SIMPLE_SCHEMA_SQL = "CREATE TABLE test_table (id INT PRIMARY KEY , name VARCHAR(50))"; + private static final String SIMPLE_INSERT_SQL = "INSERT INTO test_table (id, name) VALUES (?, ?)"; + private static final String SIMPLE_SELECT_SQL = "SELECT * FROM test_table"; + private static final String DROP_TABLE_SQL = "DROP TABLE test_table"; private final static String SCHEMA_SQL = "CREATE TABLE pstest( " + "tbigint BIGINT NULL, " @@ -83,6 +90,358 @@ public static void start() { } + @Test + public void illegalExecuteQueryTest() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.executeQuery( "SELECT * FROM my_table" ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteUpdateTest() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.executeUpdate( "INSERT INTO my_table VALUES (2, 'B')" ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteTest() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.execute( "SELECT * FROM my_table" ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteLargeUpdateTest() { + assertThrows( UnsupportedOperationException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.executeLargeUpdate( "INSERT INTO my_table VALUES (2, 'B')" ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteUpdateGeneratedKeysTest1() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.executeUpdate( "INSERT INTO my_table VALUES (2, 'B')", Statement.RETURN_GENERATED_KEYS ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteLargeUpdateGeneratedKeysTest1() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.executeLargeUpdate( "INSERT INTO my_table VALUES (2, 'B')", Statement.RETURN_GENERATED_KEYS ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteUpdateGeneratedKeysTest2() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.executeUpdate( "INSERT INTO my_table VALUES (2, 'B')", new int[]{ 1 } ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteLargeUpdateGeneratedKeysTest2() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.executeLargeUpdate( "INSERT INTO my_table VALUES (2, 'B')", new int[]{ 1 } ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteUpdateGeneratedKeysTest3() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.executeUpdate( "INSERT INTO my_table VALUES (2, 'B')", new String[]{ "id" } ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteLargeUpdateGeneratedKeysTest3() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.executeLargeUpdate( "INSERT INTO my_table VALUES (2, 'B')", new String[]{ "id" } ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteGeneratedKeysTest1() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.execute( "SELECT * FROM my_table", Statement.RETURN_GENERATED_KEYS ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteGeneratedKeysTest2() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.execute( "SELECT * FROM my_table", new int[]{ 1 } ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void illegalExecuteGeneratedKeysTest3() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection jdbcConnection = new JdbcConnection( false ); + Connection connection = jdbcConnection.getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( SIMPLE_SCHEMA_SQL ); + } + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.execute( "SELECT * FROM my_table", new String[]{ "id" } ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } ); + } + + + @Test + public void simpleUpdateTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ); + Connection connection = jdbcConnection.getConnection() + ) { + try ( Statement statement = connection.createStatement() ) { + statement.executeUpdate( SIMPLE_SCHEMA_SQL ); + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.setInt( 1, 1 ); + preparedStatement.setString( 2, "A" ); + Assertions.assertEquals( 1, preparedStatement.executeUpdate() ); + } + ResultSet rs = statement.executeQuery( SIMPLE_SELECT_SQL ); + TestHelper.checkResultSet( rs, ImmutableList.of( new Object[]{ 1, "A" } ) ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } + + + @Test + public void simpleLargeUpdateTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ); + Connection connection = jdbcConnection.getConnection() + ) { + try ( Statement statement = connection.createStatement() ) { + statement.executeUpdate( SIMPLE_SCHEMA_SQL ); + try ( PreparedStatement preparedStatement = connection.prepareStatement( SIMPLE_INSERT_SQL ) ) { + preparedStatement.setInt( 1, 1 ); + preparedStatement.setString( 2, "A" ); + Assertions.assertEquals( 1, preparedStatement.executeLargeUpdate() ); + } + ResultSet rs = statement.executeQuery( SIMPLE_SELECT_SQL ); + TestHelper.checkResultSet( rs, ImmutableList.of( new Object[]{ 1, "A" } ) ); + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } + + + @Test + public void simpleQueryTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ); + Connection connection = jdbcConnection.getConnection() + ) { + try ( Statement statement = connection.createStatement() ) { + statement.executeUpdate( SIMPLE_SCHEMA_SQL ); + statement.executeUpdate( "INSERT INTO test_table (id, name) VALUES (1, 'A')" ); + + try ( PreparedStatement preparedStatement = connection.prepareStatement( "SELECT * FROM test_table WHERE id = ?" ) ) { + preparedStatement.setInt( 1, 1 ); + ResultSet rs = preparedStatement.executeQuery(); + TestHelper.checkResultSet( rs, ImmutableList.of( new Object[]{ 1, "A" } ) ); + } + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } + + + @Test + public void simpleExecuteTest() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ); + Connection connection = jdbcConnection.getConnection() + ) { + try ( Statement statement = connection.createStatement() ) { + statement.executeUpdate( SIMPLE_SCHEMA_SQL ); + statement.executeUpdate( "INSERT INTO test_table (id, name) VALUES (1, 'A')" ); + + try ( PreparedStatement preparedStatement = connection.prepareStatement( "SELECT * FROM test_table WHERE id = ?" ) ) { + preparedStatement.setInt( 1, 1 ); + Assertions.assertTrue( preparedStatement.execute() ); + TestHelper.checkResultSet( preparedStatement.getResultSet(), ImmutableList.of( new Object[]{ 1, "A" } ) ); + } + } finally { + try ( Statement statement = connection.createStatement() ) { + statement.execute( DROP_TABLE_SQL ); + } + } + } + } + + @Test public void basicTest() throws SQLException { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { @@ -116,6 +475,34 @@ public void basicTest() throws SQLException { } + @Test + public void nullInNotNullableThrowsTest() { + assertThrows( SQLException.class, () -> { + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + statement.executeUpdate( SCHEMA_SQL ); + try { + PreparedStatement preparedInsert2 = connection.prepareStatement( "INSERT INTO pstest(tinteger,tvarchar) VALUES (?, ?)" ); + + preparedInsert2.setInt( 1, 3 ); + preparedInsert2.setNull( 2, SqlType.VARCHAR.id ); + preparedInsert2.execute(); + + preparedInsert2.setInt( 1, 4 ); + preparedInsert2.setNull( 2, SqlType.VARCHAR.id ); + preparedInsert2.execute(); + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE pstest" ); + } + } + } + } ); + } + + @Test public void nullValueTest() throws SQLException { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { @@ -142,23 +529,6 @@ public void nullValueTest() throws SQLException { preparedSelect.executeQuery(), ImmutableList.of( new Object[]{ 1, null, "Alice" } ) ); - boolean exceptionThrown = false; - try { - PreparedStatement preparedInsert2 = connection.prepareStatement( "INSERT INTO pstest(tinteger,tvarchar) VALUES (?, ?)" ); - - preparedInsert2.setInt( 1, 3 ); - preparedInsert2.setNull( 2, SqlType.VARCHAR.id ); - preparedInsert2.execute(); - - preparedInsert2.setInt( 1, 4 ); - preparedInsert2.setNull( 2, SqlType.VARCHAR.id ); - preparedInsert2.execute(); - } catch ( Exception e ) { - exceptionThrown = true; - } - - assertTrue( exceptionThrown, "Excepted null value for a non-nullable column" ); - connection.commit(); } finally { statement.executeUpdate( "DROP TABLE pstest" ); @@ -240,6 +610,130 @@ public void batchInsertDefaultValuesTest() throws SQLException { } + @Test + public void timeValueTest() throws SQLException { + Time expected = Time.valueOf( "11:59:32" ); + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + statement.executeUpdate( "CREATE TABLE time_test (id INT PRIMARY KEY , ttime TIME NULL)" ); + try { + PreparedStatement preparedInsert = connection.prepareStatement( + "INSERT INTO time_test VALUES (?, ?)" ); + + preparedInsert.setInt( 1, 0 ); + preparedInsert.setTime( 2, expected ); + preparedInsert.execute(); + connection.commit(); + + PreparedStatement preparedSelect = connection.prepareStatement( + "SELECT * FROM time_test WHERE id = ?" ); + preparedSelect.setInt( 1, 0 ); + TestHelper.checkResultSet( + preparedSelect.executeQuery(), + ImmutableList.of( new Object[]{ 0, expected } ) ); + + } finally { + statement.executeUpdate( "DROP TABLE time_test" ); + } + } + } + } + + + @Test + public void doubleValueTest() throws SQLException { + double expected = 2.3456; + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + statement.executeUpdate( "CREATE TABLE double_test (id INT PRIMARY KEY , tdouble DOUBLE NULL)" ); + try { + PreparedStatement preparedInsert = connection.prepareStatement( + "INSERT INTO double_test VALUES (?, ?)" ); + + preparedInsert.setInt( 1, 0 ); + preparedInsert.setDouble( 2, expected ); + preparedInsert.execute(); + connection.commit(); + + PreparedStatement preparedSelect = connection.prepareStatement( + "SELECT * FROM double_test WHERE id = ?" ); + preparedSelect.setInt( 1, 0 ); + TestHelper.checkResultSet( + preparedSelect.executeQuery(), + ImmutableList.of( new Object[]{ 0, expected } ) ); + + } finally { + statement.executeUpdate( "DROP TABLE double_test" ); + } + } + } + } + + + @Test + public void dateValueTest() throws SQLException { + Date expected = Date.valueOf( "2020-07-03" ); + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + statement.executeUpdate( "CREATE TABLE date_test (id INT PRIMARY KEY , tdate DATE NULL)" ); + try { + PreparedStatement preparedInsert = connection.prepareStatement( + "INSERT INTO date_test VALUES (?, ?)" ); + + preparedInsert.setInt( 1, 0 ); + preparedInsert.setDate( 2, expected ); + preparedInsert.execute(); + connection.commit(); + + PreparedStatement preparedSelect = connection.prepareStatement( + "SELECT * FROM date_test WHERE id = ?" ); + preparedSelect.setInt( 1, 0 ); + TestHelper.checkResultSet( + preparedSelect.executeQuery(), + ImmutableList.of( new Object[]{ 0, expected } ) ); + + } finally { + statement.executeUpdate( "DROP TABLE date_test" ); + } + } + } + } + + + @Test + public void timestampValueTest() throws SQLException { + Timestamp expected = Timestamp.valueOf( "2021-01-01 10:11:15" ); + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + statement.executeUpdate( "CREATE TABLE timestamp_test (id INT PRIMARY KEY , ttimestamp TIMESTAMP NULL)" ); + try { + PreparedStatement preparedInsert = connection.prepareStatement( + "INSERT INTO timestamp_test VALUES (?, ?)" ); + + preparedInsert.setInt( 1, 0 ); + preparedInsert.setTimestamp( 2, expected ); + preparedInsert.execute(); + connection.commit(); + + PreparedStatement preparedSelect = connection.prepareStatement( + "SELECT * FROM timestamp_test WHERE id = ?" ); + preparedSelect.setInt( 1, 0 ); + TestHelper.checkResultSet( + preparedSelect.executeQuery(), + ImmutableList.of( new Object[]{ 0, expected } ) ); + + } finally { + statement.executeUpdate( "DROP TABLE timestamp_test" ); + } + } + } + } + + @Test public void dataTypesTest() throws SQLException { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { @@ -335,18 +829,18 @@ public void batchDataTypesTest() throws SQLException { PreparedStatement preparedSelect = connection.prepareStatement( "SELECT * FROM pstest WHERE " - + "tbigint = ? AND " - + "tboolean = ? AND " - + "tdate = ? AND " - + "tdecimal = ? AND " - + "tdouble = ? AND " - + "tinteger = ? AND " - + "treal = ? AND " - + "tsmallint = ? AND " - + "ttime = ? AND " - + "ttimestamp = ? AND " - + "ttinyint = ? AND " - + "tvarchar = ?" ); + + "tbigint = ? AND " + + "tboolean = ? AND " + + "tdate = ? AND " + + "tdecimal = ? AND " + + "tdouble = ? AND " + + "tinteger = ? AND " + + "treal = ? AND " + + "tsmallint = ? AND " + + "ttime = ? AND " + + "ttimestamp = ? AND " + + "ttinyint = ? AND " + + "tvarchar = ?" ); preparedSelect.setLong( 1, (long) TEST_DATA[0] ); preparedSelect.setBoolean( 2, (boolean) TEST_DATA[1] ); preparedSelect.setDate( 3, (Date) TEST_DATA[2] ); @@ -416,6 +910,7 @@ public void nullTest() throws SQLException { @Test + @Disabled("Hanging...") public void updateTest() throws SQLException { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { Connection connection = polyphenyDbConnection.getConnection(); @@ -443,6 +938,7 @@ public void updateTest() throws SQLException { preparedUpdate.executeUpdate(); connection.commit(); + // Check PreparedStatement preparedSelect = connection.prepareStatement( "SELECT tinteger,tsmallint,tvarchar FROM pstest WHERE tinteger = ? OR tinteger = ? ORDER BY tinteger" ); preparedSelect.setInt( 1, 1 ); @@ -462,10 +958,10 @@ public void updateTest() throws SQLException { TestHelper.checkResultSet( preparedSelect.executeQuery(), ImmutableList.of( new Object[]{ 1, (short) 13, "Foo" }, new Object[]{ 2, (short) 5, "Bar" } ) ); - connection.commit(); } finally { statement.executeUpdate( "DROP TABLE pstest" ); + connection.commit(); } } } diff --git a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcResultSetTest.java b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcResultSetTest.java index 24f103dd31..c7873ef87e 100644 --- a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcResultSetTest.java +++ b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcResultSetTest.java @@ -26,23 +26,21 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.polypheny.db.PolyphenyDb; import org.polypheny.db.TestHelper.JdbcConnection; +import org.polypheny.jdbc.PolyphenyResultSet; -@SuppressWarnings({ "SqlDialectInspection", "SqlNoDataSourceInspection" }) @Tag("adapter") -@Disabled public class JdbcResultSetTest { - private static final String TABLE_SQL = "CREATE TABLE IF NOT EXISTS resultset_test (id INT, hex_value VARCHAR(2))"; - private static final String DROP_TABLE_SQL = "DROP TABLE resultset_test"; + private static final String TABLE_SQL = "CREATE TABLE resultset_test (id INT PRIMARY KEY, hex_value VARCHAR(2))"; + private static final String DROP_TABLE_SQL = "DROP TABLE IF EXISTS resultset_test"; private static final String DATA_SQL = buildInsertSql(); - private static final String SELECT_SQL = "SELECT * FROM resultset_test"; + private static final String SELECT_SQL = "SELECT * FROM resultset_test ORDER BY id"; private static String buildInsertSql() { @@ -61,6 +59,7 @@ private static String buildInsertSql() { private void createTableWithData( Connection connection ) throws SQLException { try ( Statement statement = connection.createStatement(); ) { + statement.execute( DROP_TABLE_SQL ); statement.execute( TABLE_SQL ); statement.execute( DATA_SQL ); } @@ -122,9 +121,8 @@ public void cursorIncToAfterLast() throws SQLException { Statement statement = connection.createStatement() ) { createTableWithData( connection ); ResultSet rs = statement.executeQuery( SELECT_SQL ); - while ( rs.next() ) { - // intentionally empty, do nothing - } + while ( rs.next() ) + ; assertTrue( rs.isAfterLast() ); rs.close(); statement.executeUpdate( DROP_TABLE_SQL ); @@ -168,7 +166,7 @@ public void lastReadNullTest() throws SQLException { try ( JdbcConnection jdbcConnection = new JdbcConnection( true ); Connection connection = jdbcConnection.getConnection(); Statement statement = connection.createStatement() ) { - statement.executeUpdate( "CREATE TABLE IF NOT EXISTS my_table (id INT, nullvalue VARCHAR(30))" ); + statement.executeUpdate( "CREATE TABLE IF NOT EXISTS my_table (id INT PRIMARY KEY, nullvalue VARCHAR(30))" ); statement.executeUpdate( "INSERT INTO my_table VALUES (1, NULL)" ); ResultSet rs = statement.executeQuery( "SELECT * FROM my_table" ); rs.next(); @@ -500,7 +498,7 @@ public void absoluteAccessFromEndTest() throws SQLException { createTableWithData( connection ); ResultSet rs = statement.executeQuery( SELECT_SQL ); assertTrue( rs.absolute( -5 ) ); - assertEquals( 46, rs.getRow() ); + assertEquals( 21, rs.getRow() ); rs.close(); statement.executeUpdate( DROP_TABLE_SQL ); } @@ -676,7 +674,7 @@ public void reverseIterationWithPreviousTest() throws SQLException { @Test - public void illegalFetchDirectionThrowsExceptionTest() { + public void illegalFetchDirectionThrowsExceptionTest() throws SQLException { assertThrows( SQLException.class, () -> { try ( JdbcConnection jdbcConnection = new JdbcConnection( true ); Connection connection = jdbcConnection.getConnection(); @@ -996,6 +994,38 @@ public void getHoldabilityTest() throws SQLException { } + @Test + public void isWrapperForTest() throws SQLException { + try ( + JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); + Connection connection = polyphenyDbConnection.getConnection(); + Statement statement = connection.createStatement(); + ) { + createTableWithData( connection ); + ResultSet rs = statement.executeQuery( SELECT_SQL ); + assertTrue( rs.isWrapperFor( PolyphenyResultSet.class ) ); + rs.close(); + statement.executeUpdate( DROP_TABLE_SQL ); + } + } + + + @Test + public void unwrapTest() throws SQLException { + try ( + JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); + Connection connection = polyphenyDbConnection.getConnection(); + Statement statement = connection.createStatement(); + ) { + createTableWithData( connection ); + ResultSet rs = statement.executeQuery( SELECT_SQL ); + PolyphenyResultSet polyRs = rs.unwrap( PolyphenyResultSet.class ); + rs.close(); + statement.executeUpdate( DROP_TABLE_SQL ); + } + } + + @Test public void isuWrapperForFalseTest() throws SQLException { try ( diff --git a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcStatementTest.java b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcStatementTest.java index 4d161a1842..368918eced 100644 --- a/dbms/src/test/java/org/polypheny/db/jdbc/JdbcStatementTest.java +++ b/dbms/src/test/java/org/polypheny/db/jdbc/JdbcStatementTest.java @@ -16,6 +16,7 @@ package org.polypheny.db.jdbc; + import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -32,21 +33,18 @@ import java.sql.Statement; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.polypheny.db.PolyphenyDb; import org.polypheny.db.TestHelper; import org.polypheny.db.TestHelper.JdbcConnection; +import org.polypheny.jdbc.PolyphenyStatement; - -@SuppressWarnings({ "SqlDialectInspection", "SqlNoDataSourceInspection" }) -@Tag("adapter") @Slf4j -@Disabled +@Tag("adapter") public class JdbcStatementTest { - private static final String CREATE_TEST_TABLE = "CREATE TABLE IF NOT EXISTS my_table (id INT, name VARCHAR(50))"; + private static final String CREATE_TEST_TABLE = "CREATE TABLE IF NOT EXISTS my_table (id INT PRIMARY KEY, name VARCHAR(50))"; private static final String INSERT_TEST_DATA = "INSERT INTO my_table values(1, 'A'), (2, 'B'), (3, 'C'), (4, 'D')"; @@ -54,14 +52,14 @@ public class JdbcStatementTest { @BeforeAll public static void start() { // Ensures that Polypheny-DB is running - //noinspection ResultOfMethodCallIgnored + // noinspection ResultOfMethodCallIgnored TestHelper.getInstance(); } @Test public void statementClosingTest() throws SQLException { - try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { Connection connection = polyphenyDbConnection.getConnection(); Statement statement = connection.createStatement(); @@ -74,7 +72,7 @@ public void statementClosingTest() throws SQLException { @Test public void connectionClosesOpenStatementsTest() throws SQLException { - try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); + try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ) ) { Connection connection = polyphenyDbConnection.getConnection(); Statement first_statement = connection.createStatement(); @@ -114,9 +112,8 @@ public void simpleValidUpdateReturnsUpdateCountTest() throws SQLException { ) { statement.execute( CREATE_TEST_TABLE ); int rowsChanged = statement.executeUpdate( INSERT_TEST_DATA ); - assertEquals( 0, rowsChanged ); connection.rollback(); - // assertEquals( 4, rowsChanged ); Polypheny currently returns 0 as update count if multiple rows are inserted in one statement + assertEquals( 4, rowsChanged ); } } @@ -177,9 +174,8 @@ public void simpleExecuteUpdateReturnsRowCountTest() throws SQLException { ) { statement.execute( CREATE_TEST_TABLE ); assertFalse( statement.execute( INSERT_TEST_DATA ) ); - assertEquals( 0, statement.getUpdateCount() ); connection.rollback(); - //assertEquals(4, statement.getUpdateCount() ); Polypheny currently returns 0 as update count if multiple rows are inserted in one statement + assertEquals( 4, statement.getUpdateCount() ); } } @@ -225,8 +221,7 @@ public void simpleExecuteUpdateReturnsProperValueForUnusedReturnTest() throws SQ ) { statement.execute( CREATE_TEST_TABLE ); assertFalse( statement.execute( INSERT_TEST_DATA ) ); - assertEquals( 0, statement.getUpdateCount() ); - //assertEquals(4, statement.getUpdateCount() ); Polypheny currently returns 0 as update count if multiple rows are inserted in one statement + assertEquals( 4, statement.getUpdateCount() ); assertNull( statement.getResultSet() ); connection.rollback(); } @@ -296,11 +291,11 @@ public void maxRowsTest() throws SQLException { statement.execute( INSERT_TEST_DATA ); statement.setMaxRows( maxRows ); assertEquals( maxRows, statement.getMaxRows() ); - ResultSet rs = statement.executeQuery( "SELECT * FROM my_table" ); + ResultSet rs = statement.executeQuery( "SELECT * FROM my_table ORDER BY id" ); TestHelper.checkResultSet( rs, ImmutableList.of( new Object[]{ 1, "A" }, new Object[]{ 2, "B" } - ), true ); + ) ); connection.rollback(); } } @@ -318,11 +313,11 @@ public void largeMaxRowsTest() throws SQLException { statement.execute( INSERT_TEST_DATA ); statement.setLargeMaxRows( maxRows ); assertEquals( maxRows, statement.getLargeMaxRows() ); - ResultSet rs = statement.executeQuery( "SELECT * FROM my_table" ); + ResultSet rs = statement.executeQuery( "SELECT * FROM my_table ORDER BY id" ); TestHelper.checkResultSet( rs, ImmutableList.of( new Object[]{ 1, "A" }, new Object[]{ 2, "B" } - ), true ); + ) ); connection.rollback(); } } @@ -435,7 +430,7 @@ public void setIllegalFetchDirectionTest1() { @Test - public void setIllegalFetchDirectionTest2() throws SQLException { + public void setIllegalFetchDirectionTest2() { assertThrows( SQLException.class, () -> { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); @@ -630,7 +625,7 @@ public void getMoreResultsWithBehaviourTest() throws SQLException { @Test public void getMoreResultsWithInvalidBehaviourKeepThrowsExceptionTest() { - assertThrows( SQLFeatureNotSupportedException.class, () -> { + assertThrows( SQLException.class, () -> { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); Connection connection = polyphenyDbConnection.getConnection(); @@ -862,6 +857,30 @@ public void closeOnCompletionTrueTest() throws SQLException { } + @Test + public void isWrapperForTest() throws SQLException { + try ( + JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); + Connection connection = polyphenyDbConnection.getConnection(); + Statement statement = connection.createStatement(); + ) { + assertTrue( statement.isWrapperFor( PolyphenyStatement.class ) ); + } + } + + + @Test + public void unwrapTest() throws SQLException { + try ( + JdbcConnection polyphenyDbConnection = new JdbcConnection( false ); + Connection connection = polyphenyDbConnection.getConnection(); + Statement statement = connection.createStatement(); + ) { + PolyphenyStatement polyphenyStatement = statement.unwrap( PolyphenyStatement.class ); + } + } + + @Test public void isWrapperForFalseTest() throws SQLException { try ( diff --git a/dbms/src/test/java/org/polypheny/db/misc/HorizontalPartitioningTest.java b/dbms/src/test/java/org/polypheny/db/misc/HorizontalPartitioningTest.java index 4eb31cf9e5..2ea77e57b6 100644 --- a/dbms/src/test/java/org/polypheny/db/misc/HorizontalPartitioningTest.java +++ b/dbms/src/test/java/org/polypheny/db/misc/HorizontalPartitioningTest.java @@ -26,7 +26,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; -import org.apache.calcite.avatica.AvaticaSqlException; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; @@ -50,6 +50,7 @@ import org.polypheny.db.partition.properties.PartitionProperty; import org.polypheny.db.partition.properties.TemperaturePartitionProperty; import org.polypheny.db.util.background.BackgroundTask.TaskSchedulingType; +import org.polypheny.jdbc.PrismInterfaceServiceException; @SuppressWarnings({ "SqlNoDataSourceInspection", "SqlDialectInspection" }) @@ -72,7 +73,7 @@ public static void start() { @BeforeEach public void beforeEach() { - //helper.randomizeCatalogIds(); + helper.randomizeCatalogIds(); } @@ -94,30 +95,22 @@ public void basicHorizontalPartitioningTest() throws SQLException { + "PARTITIONS 4" ); // Cannot partition a table that has already been partitioned - boolean failed = false; - try { - statement.executeUpdate( "ALTER TABLE horizontalparttest " - + "PARTITION BY HASH (tinteger) " - + "PARTITIONS 2" ); - } catch ( AvaticaSqlException e ) { - failed = true; - } - assertTrue( failed ); - - // check assert False. Wrong partition column - failed = false; - try { - statement.executeUpdate( "CREATE TABLE horizontalparttestfalsepartition( " - + "tprimary INTEGER NOT NULL, " - + "tinteger INTEGER NULL, " - + "tvarchar VARCHAR(20) NULL, " - + "PRIMARY KEY (tprimary) )" - + "PARTITION BY HASH (othercolumn) " - + "PARTITIONS 3" ); - } catch ( AvaticaSqlException e ) { - failed = true; - } - assertTrue( failed ); + Assertions.assertThrows( PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "ALTER TABLE horizontalparttest " + + "PARTITION BY HASH (tinteger) " + + "PARTITIONS 2" ) + ); + + // Wrong partition column + Assertions.assertThrows( PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "CREATE TABLE horizontalparttestfalsepartition( " + + "tprimary INTEGER NOT NULL, " + + "tinteger INTEGER NULL, " + + "tvarchar VARCHAR(20) NULL, " + + "PRIMARY KEY (tprimary) )" + + "PARTITION BY HASH (othercolumn) " + + "PARTITIONS 3" ) + ); } finally { // Drop tables and stores statement.executeUpdate( "DROP TABLE IF EXISTS horizontalparttest" ); @@ -199,14 +192,11 @@ public void modifyPartitionTest() throws SQLException { // name partitioning can be modified with name statement.executeUpdate( "ALTER TABLE \"horizontalparttestextension\" MODIFY PARTITIONS (name2, name3) ON STORE \"store2\" " ); - // check assert False. modify with false name no partition exists with name22 - failed = false; - try { - statement.executeUpdate( "ALTER TABLE \"horizontalparttestextension\" MODIFY PARTITIONS (name22) ON STORE \"store2\" " ); - } catch ( AvaticaSqlException e ) { - failed = true; - } - assertTrue( failed ); + // modify with false name no partition exists with name22 + Assertions.assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "ALTER TABLE \"horizontalparttestextension\" MODIFY PARTITIONS (name22) ON STORE \"store2\" " ) + ); } finally { // Drop tables and stores statement.executeUpdate( "DROP TABLE IF EXISTS horizontalparttestextension" ); @@ -396,22 +386,17 @@ public void hashPartitioningTest() throws SQLException { + "PARTITIONS 3" ); try { - //AsserTFalse //HASH Partitioning cant be created using values - boolean failed = false; - try { - statement.executeUpdate( "CREATE TABLE hashpartitioning( " - + "tprimary INTEGER NOT NULL, " - + "tinteger INTEGER NULL, " - + "tvarchar VARCHAR(20) NULL, " - + "PRIMARY KEY (tprimary) )" - + "PARTITION BY HASH (tvarchar) " - + "( PARTITION parta VALUES('abc'), " - + "PARTITION partb VALUES('def'))" ); - } catch ( AvaticaSqlException e ) { - failed = true; - } - assertTrue( failed ); + Assertions.assertThrows( PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "CREATE TABLE hashpartitioning( " + + "tprimary INTEGER NOT NULL, " + + "tinteger INTEGER NULL, " + + "tvarchar VARCHAR(20) NULL, " + + "PRIMARY KEY (tprimary) )" + + "PARTITION BY HASH (tvarchar) " + + "( PARTITION parta VALUES('abc'), " + + "PARTITION partb VALUES('def'))" ) + ); // ADD adapter TestHelper.addHsqldb( "storehash", statement ); @@ -425,20 +410,16 @@ public void hashPartitioningTest() throws SQLException { statement.executeUpdate( "ALTER TABLE \"hashpartition\" MERGE PARTITIONS" ); // You can't change the distribution unless there exists at least one full partition placement of each column as a fallback - failed = false; - try { - statement.executeUpdate( "CREATE TABLE hashpartitioningValidate( " - + "tprimary INTEGER NOT NULL, " - + "tinteger INTEGER NULL, " - + "tvarchar VARCHAR(20) NULL, " - + "PRIMARY KEY (tprimary) )" - + "PARTITION BY HASH (tvarchar) " - + "( PARTITION parta VALUES('abc'), " - + "PARTITION partb VALUES('def'))" ); - } catch ( AvaticaSqlException e ) { - failed = true; - } - assertTrue( failed ); + Assertions.assertThrows( PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "CREATE TABLE hashpartitioningValidate( " + + "tprimary INTEGER NOT NULL, " + + "tinteger INTEGER NULL, " + + "tvarchar VARCHAR(20) NULL, " + + "PRIMARY KEY (tprimary) )" + + "PARTITION BY HASH (tvarchar) " + + "( PARTITION parta VALUES('abc'), " + + "PARTITION partb VALUES('def'))" ) + ); } finally { statement.executeUpdate( "DROP TABLE IF EXISTS hashpartition" ); statement.executeUpdate( "DROP TABLE IF EXISTS hashpartitioning" ); @@ -550,7 +531,6 @@ public void rangePartitioningTest() throws SQLException { + "( PARTITION parta VALUES(5,4), " + "PARTITION partb VALUES(10,6))" ); - // RANGE partitioning can't be created without specifying ranges boolean failed = false; try { @@ -584,7 +564,6 @@ public void partitionFilterTest() throws SQLException { try ( JdbcConnection polyphenyDbConnection = new JdbcConnection( true ) ) { Connection connection = polyphenyDbConnection.getConnection(); - try ( Statement statement = connection.createStatement() ) { statement.executeUpdate( "CREATE TABLE physicalPartitionFilter( " + "tprimary INTEGER NOT NULL, " diff --git a/dbms/src/test/java/org/polypheny/db/misc/VerticalPartitioningTest.java b/dbms/src/test/java/org/polypheny/db/misc/VerticalPartitioningTest.java index 14ad2e3795..dba3596378 100644 --- a/dbms/src/test/java/org/polypheny/db/misc/VerticalPartitioningTest.java +++ b/dbms/src/test/java/org/polypheny/db/misc/VerticalPartitioningTest.java @@ -17,8 +17,6 @@ package org.polypheny.db.misc; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.common.collect.ImmutableList; import java.sql.Connection; @@ -26,7 +24,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; -import org.apache.calcite.avatica.AvaticaSqlException; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; @@ -37,6 +35,7 @@ import org.polypheny.db.catalog.entity.allocation.AllocationPlacement; import org.polypheny.db.catalog.entity.logical.LogicalTable; import org.polypheny.db.catalog.logistic.Pattern; +import org.polypheny.jdbc.PrismInterfaceServiceException; @SuppressWarnings({ "SqlDialectInspection", "SqlNoDataSourceInspection" }) @@ -261,13 +260,9 @@ public void dataPlacementTest() throws SQLException { // By executing the following statement, technically the column tprimary would not be present // on any DataPlacement anymore. Therefore, it has to fail and all placements should remain - boolean failed = false; - try { - statement.executeUpdate( "ALTER TABLE \"verticalDataPlacementTest\" MODIFY PLACEMENT (tprimary) ON STORE hsqldb" ); - } catch ( AvaticaSqlException e ) { - failed = true; - } - assertTrue( failed ); + Assertions.assertThrows( PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "ALTER TABLE \"verticalDataPlacementTest\" MODIFY PLACEMENT (tprimary) ON STORE hsqldb" ) + ); // ADD single column on second storeId statement.executeUpdate( "ALTER TABLE \"verticalDataPlacementTest\" MODIFY PLACEMENT ADD COLUMN tinteger ON STORE anotherstore" ); @@ -344,22 +339,17 @@ public void dataDistributionTest() throws SQLException { // By executing the following statement, technically the column tinteger would not be present // on any of the partitions of the placement anymore. Therefore, it has to fail and all placements should remain - boolean failed = false; - try { - statement.executeUpdate( "ALTER TABLE \"verticalDataPlacementTest\" MODIFY PLACEMENT DROP COLUMN tvarchar ON STORE hsqldb" ); - fail(); - } catch ( AvaticaSqlException e ) { - // empty on purpose - } + Assertions.assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "ALTER TABLE \"verticalDataPlacementTest\" MODIFY PLACEMENT DROP COLUMN tvarchar ON STORE hsqldb" ) + ); statement.executeUpdate( "ALTER TABLE \"verticalDataPlacementTest\" MODIFY PLACEMENT (tprimary,tvarchar) ON STORE hsqldb" ); - try { - statement.executeUpdate( "ALTER TABLE \"verticalDataPlacementTest\" DROP PLACEMENT ON STORE anotherstore" ); - fail(); - } catch ( AvaticaSqlException e ) { - // empty on purpose - } + Assertions.assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "ALTER TABLE \"verticalDataPlacementTest\" DROP PLACEMENT ON STORE anotherstore" ) + ); } finally { // Drop tables and stores diff --git a/dbms/src/test/java/org/polypheny/db/prisminterface/MqlTest.java b/dbms/src/test/java/org/polypheny/db/prisminterface/MqlTest.java new file mode 100644 index 0000000000..ec6b2f0b99 --- /dev/null +++ b/dbms/src/test/java/org/polypheny/db/prisminterface/MqlTest.java @@ -0,0 +1,71 @@ +/* + * 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.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.junit.jupiter.api.Test; +import org.polypheny.db.TestHelper.JdbcConnection; +import org.polypheny.jdbc.PolyConnection; +import org.polypheny.jdbc.PrismInterfaceServiceException; +import org.polypheny.jdbc.multimodel.PolyStatement; +import org.polypheny.jdbc.multimodel.Result; +import org.polypheny.jdbc.multimodel.Result.ResultType; + +public class MqlTest { + + private static final String MQL_LANGUAGE_NAME = "mongo"; + private static final String TEST_QUERY = "db.students.find();"; + + + @Test + public void connectionUnwrapTest() throws SQLException { + try ( Connection connection = new JdbcConnection( true ).getConnection() ) { + if ( !connection.isWrapperFor( PolyConnection.class ) ) { + fail( "Driver must support unwrapping to PolyphenyConnection" ); + } + } + } + + + @Test + public void simpleMqlTest() throws SQLException { + try ( Connection connection = new JdbcConnection( true ).getConnection() ) { + try ( Statement statement = connection.createStatement() ) { + statement.execute( "DROP NAMESPACE IF EXISTS mqltest" ); + statement.execute( "CREATE DOCUMENT NAMESPACE mqltest" ); + } + if ( !connection.isWrapperFor( PolyConnection.class ) ) { + fail( "Driver must support unwrapping to PolyphenyConnection" ); + } + PolyStatement polyStatement = connection.unwrap( PolyConnection.class ).createPolyStatement(); + assertThrows( PrismInterfaceServiceException.class, () -> { + // fails due to autogenerated query (would create students collection) + Result result = polyStatement.execute( "mqltest", MQL_LANGUAGE_NAME, TEST_QUERY ); + assertEquals( ResultType.DOCUMENT, result.getResultType() ); + } ); + + } + } + +} diff --git a/dbms/src/test/java/org/polypheny/db/rex/RexProgramBuilderBase.java b/dbms/src/test/java/org/polypheny/db/rex/RexProgramBuilderBase.java index 169dcbce6f..27a5136daa 100644 --- a/dbms/src/test/java/org/polypheny/db/rex/RexProgramBuilderBase.java +++ b/dbms/src/test/java/org/polypheny/db/rex/RexProgramBuilderBase.java @@ -42,6 +42,7 @@ import java.util.Map; import java.util.TimeZone; import org.apache.calcite.linq4j.QueryProvider; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeAll; import org.polypheny.db.TestHelper; import org.polypheny.db.adapter.DataContext; @@ -154,7 +155,7 @@ public Statement getStatement() { @Override - public void addParameterValues( long index, AlgDataType type, List data ) { + public void addParameterValues( long index, @NotNull AlgDataType type, List data ) { throw new UnsupportedOperationException( "This operation is not supported for " + getClass().getSimpleName() ); } diff --git a/dbms/src/test/java/org/polypheny/db/sql/clause/SelectTest.java b/dbms/src/test/java/org/polypheny/db/sql/clause/SelectTest.java index 9ca64eeaea..d77dd6c1c2 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/clause/SelectTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/clause/SelectTest.java @@ -83,6 +83,19 @@ private static void addTestData() throws SQLException { } + @Test + public void selectNullTest() throws SQLException { + String select = "SELECT NULL"; + + try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + statement.execute( select ); + } + } + } + + @AfterAll public static void stop() throws SQLException { try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { @@ -252,7 +265,8 @@ public void databaseTest() throws SQLException { @Test public void insertUpdateTest() throws SQLException { - String ddl = "CREATE TABLE my_table (column1 INT NOT NULL, column2 VARCHAR(255), column3 INT, PRIMARY KEY (column1))"; + String ddl1 = "DROP TABLE IF EXISTS my_table"; + String ddl2 = "CREATE TABLE my_table (column1 INT NOT NULL, column2 VARCHAR(255), column3 INT, PRIMARY KEY (column1))"; String insert = "INSERT INTO my_table (column1, column2, column3) VALUES (1, 'v1', 100), (2, 'v2', 200), (3, 'v3', 300)"; String update = "UPDATE my_table SET column2 = 'foo' WHERE column1 = 1 AND column3 = 100"; @@ -260,7 +274,8 @@ public void insertUpdateTest() throws SQLException { Connection connection = polyphenyDbConnection.getConnection(); try ( Statement statement = connection.createStatement() ) { - statement.executeUpdate( ddl ); + statement.executeUpdate( ddl1 ); + statement.executeUpdate( ddl2 ); statement.executeUpdate( insert ); statement.executeUpdate( update ); diff --git a/dbms/src/test/java/org/polypheny/db/sql/clause/SimpleSqlTest.java b/dbms/src/test/java/org/polypheny/db/sql/clause/SimpleSqlTest.java index 07671cc247..4f7298b20c 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/clause/SimpleSqlTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/clause/SimpleSqlTest.java @@ -16,13 +16,17 @@ package org.polypheny.db.sql.clause; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.polypheny.db.TestHelper; @SuppressWarnings({ "SqlDialectInspection", "SqlNoDataSourceInspection" }) +@Tag("adapter") public class SimpleSqlTest { @BeforeAll @@ -126,4 +130,33 @@ public void likeTest() { } + + @Test + public void tooLargeNumberTest() { + TestHelper.executeSql( + ( c, s ) -> s.executeUpdate( "DROP TABLE IF EXISTS t" ), + ( c, s ) -> s.executeUpdate( "CREATE TABLE t(i INTEGER NOT NULL, a DECIMAL(1) NOT NULL, PRIMARY KEY(i))" ), + ( c, s ) -> Assertions.assertThrows( Exception.class, () -> s.executeUpdate( "INSERT INTO t(i, a) VALUES (0, 2555)" ) ), + ( c, s ) -> Assertions.assertThrows( Exception.class, () -> { + PreparedStatement prepared = c.prepareStatement( "INSERT INTO t(i, a) VALUES (0, ?)" ); + prepared.setInt( 1, 2555 ); + prepared.execute(); + } ), + ( c, s ) -> c.commit() + ); + + } + + + @Test + public void tooLargeVarCharTest() { + TestHelper.executeSql( + ( c, s ) -> s.executeUpdate( "DROP TABLE IF EXISTS t" ), + ( c, s ) -> s.executeUpdate( "CREATE TABLE t(i INTEGER NOT NULL, a VARCHAR(1) NOT NULL, PRIMARY KEY(i))" ), + ( c, s ) -> Assertions.assertThrows( Exception.class, () -> s.executeUpdate( "INSERT INTO t(i, a) VALUES (0, 'test')" ) ), + ( c, s ) -> c.commit() + ); + + } + } diff --git a/dbms/src/test/java/org/polypheny/db/sql/fun/ComparisonOperatorTest.java b/dbms/src/test/java/org/polypheny/db/sql/fun/ComparisonOperatorTest.java index 4f2a3e8ee8..b35ff5b2f8 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/fun/ComparisonOperatorTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/fun/ComparisonOperatorTest.java @@ -242,10 +242,6 @@ public void testIsNotNullOperator() throws SQLException { @Test - @Tag("mongodbExcluded") - @Tag("postgresqlExcluded") - @Tag("monetdbExcluded") - @Tag("cottontailExcluded") public void testIsDistinctFromOperator() throws SQLException { try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { Connection connection = polyphenyDbConnection.getConnection(); @@ -254,7 +250,7 @@ public void testIsDistinctFromOperator() throws SQLException { new Object[]{ 5 } // Assuming one value is NULL and four other distinct non-NULL values from 10 ); TestHelper.checkResultSet( - statement.executeQuery( "SELECT COUNT(*) FROM ComparisonOperatorTestTable WHERE comparisonColumn IS DISTINCT FROM 10" ), + statement.executeQuery( "SELECT COUNT(*) FROM ComparisonOperatorTestTable WHERE comparisonColumn IS DISTINCT FROM 10 AND comparisonColumn IS NOT NULL" ), // should we include null in distinct or not expectedResult, true ); @@ -392,7 +388,6 @@ public void testInOperator() throws SQLException { @Test @Tag("mongodbExcluded") @Tag("cottontailExcluded") - @Tag("neo4jExcluded") public void testNotInOperator() throws SQLException { try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { Connection connection = polyphenyDbConnection.getConnection(); @@ -536,7 +531,6 @@ public void testExistsWithSubQuery() throws SQLException { @Test - @Tag("neo4jExcluded") public void complexLogicalTestOne() throws SQLException { try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { Connection connection = polyphenyDbConnection.getConnection(); @@ -557,7 +551,6 @@ public void complexLogicalTestOne() throws SQLException { @Test - @Tag("neo4jExcluded") public void complexLogicalTestTwo() throws SQLException { try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { Connection connection = polyphenyDbConnection.getConnection(); diff --git a/dbms/src/test/java/org/polypheny/db/sql/type/ArrayTest.java b/dbms/src/test/java/org/polypheny/db/sql/type/ArrayTest.java new file mode 100644 index 0000000000..90e528d14c --- /dev/null +++ b/dbms/src/test/java/org/polypheny/db/sql/type/ArrayTest.java @@ -0,0 +1,42 @@ +/* + * 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.sql.type; + +import java.sql.SQLException; +import java.sql.Statement; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.polypheny.db.TestHelper.JdbcConnection; +import org.polypheny.jdbc.PrismInterfaceServiceException; + +public class ArrayTest { + + @Test + public void NoTypeTest() throws SQLException { + try ( JdbcConnection con = new JdbcConnection( true ) ) { + try ( Statement s = con.getConn().createStatement() ) { + s.executeUpdate( "DROP TABLE IF EXISTS t" ); + Assertions.assertThrows( + PrismInterfaceServiceException.class, + () -> s.executeUpdate( "CREATE TABLE t(i INTEGER NOT NULL, a ARRAY, PRIMARY KEY(i))" ), + "Array type must specify a collection type" + ); + } + } + } + +} diff --git a/gradle.properties b/gradle.properties index 29d002835f..c4de5c8bd7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -68,8 +68,8 @@ jsr305_version = 3.0.1 junit_jupiter_version = 5.10.1 junit_version = 5.10.1 junit_suite_version = 1.10.1 -licensee_version = 1.8.0 -license_report_version = 0.9.3 +licensee_version = 1.10.0 +license_report_version = 0.9.7 log4j_api_version = 2.22.0 log4j_core_version = 2.22.0 log4j_slf4j_impl_version = 2.22.0 @@ -80,16 +80,17 @@ metadata_extractor_version = 2.15.0 mockito_core_version = 5.11.0 monetdb_version = 2.37 mongodb_driver_sync_version = 4.11.0 -neo4j_version = 5.17.0 +neo4j_version = 5.20.0 opencsv_version = 5.9 oshi_core_version = 6.4.9 oauth_client_version = 1.34.1 poi_version = 5.2.3 poi_ooxml_version = 5.2.3 -polypheny_jdbc_driver_version = 1.5.3 +polypheny_jdbc_driver_version = 2.0 polypheny_ui_version = 2.0-SNAPSHOT postgresql_version = 42.2.19 pf4j_version = 3.11.0 +prism_api_version = 1.2 protobuf_version = 3.23.4 protobuf_plugin_version = 0.9.4 reflections_version = 0.10.2 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa7..d64cd49177 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index eb32fc14b4..45e9ea4598 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase = GRADLE_USER_HOME distributionPath = wrapper/dists -distributionUrl = https\://services.gradle.org/distributions/gradle-8.5-all.zip +distributionUrl = https\://services.gradle.org/distributions/gradle-8.7-all.zip networkTimeout = 10000 +validateDistributionUrl = true zipStoreBase = GRADLE_USER_HOME zipStorePath = wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d65..1aa94a4269 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/monitoring/src/main/java/org/polypheny/db/monitoring/statistics/AlphabeticStatisticColumn.java b/monitoring/src/main/java/org/polypheny/db/monitoring/statistics/AlphabeticStatisticColumn.java index 9f21e598f9..a9759b7836 100644 --- a/monitoring/src/main/java/org/polypheny/db/monitoring/statistics/AlphabeticStatisticColumn.java +++ b/monitoring/src/main/java/org/polypheny/db/monitoring/statistics/AlphabeticStatisticColumn.java @@ -67,7 +67,13 @@ public void insert( List values ) { } for ( PolyValue val : values ) { - insert( val == null ? null : val.asString() ); + if ( val == null ) { + insert( (PolyValue) null ); + } else if ( val.isString() ) { + insert( val.asString() ); + } else { + log.warn( "Value is not a string: {}", val.toString() ); + } } } diff --git a/plugins/avatica-interface/src/main/java/org/polypheny/db/avatica/AvaticaInterfacePlugin.java b/plugins/avatica-interface/src/main/java/org/polypheny/db/avatica/AvaticaInterfacePlugin.java index c8bb65d1f2..fc16754822 100644 --- a/plugins/avatica-interface/src/main/java/org/polypheny/db/avatica/AvaticaInterfacePlugin.java +++ b/plugins/avatica-interface/src/main/java/org/polypheny/db/avatica/AvaticaInterfacePlugin.java @@ -61,13 +61,13 @@ public void afterCatalogInit() { Map settings = new HashMap<>(); settings.put( "port", "20591" ); settings.put( "serialization", "PROTOBUF" ); - QueryInterfaceManager.addInterfaceType( "avatica", AvaticaInterface.class, settings ); + QueryInterfaceManager.addInterfaceTemplate( AvaticaInterface.INTERFACE_NAME, AvaticaInterface.INTERFACE_DESCRIPTION, AvaticaInterface.AVAILABLE_SETTINGS, AvaticaInterface::new ); } @Override public void stop() { - QueryInterfaceManager.removeInterfaceType( AvaticaInterface.class ); + QueryInterfaceManager.removeInterfaceType( AvaticaInterface.INTERFACE_NAME ); } @@ -82,7 +82,7 @@ public static class AvaticaInterface extends QueryInterface implements PropertyC @SuppressWarnings("WeakerAccess") public static final List AVAILABLE_SETTINGS = ImmutableList.of( new QueryInterfaceSettingInteger( "port", false, true, false, 20591 ), - new QueryInterfaceSettingList( "serialization", false, true, false, ImmutableList.of( "PROTOBUF", "JSON" ) ) + new QueryInterfaceSettingList( "serialization", false, true, false, ImmutableList.of( "PROTOBUF", "JSON" ), "PROTOBUF" ) ); @@ -94,8 +94,8 @@ public static class AvaticaInterface extends QueryInterface implements PropertyC private final HttpServerDispatcher httpServerDispatcher; - public AvaticaInterface( TransactionManager transactionManager, Authenticator authenticator, long ifaceId, String uniqueName, Map settings ) { - super( transactionManager, authenticator, ifaceId, uniqueName, settings, true, true ); + public AvaticaInterface( TransactionManager transactionManager, Authenticator authenticator, String uniqueName, Map settings ) { + super( transactionManager, authenticator, uniqueName, settings, true, true ); metricsSystemConfiguration = NoopMetricsSystemConfiguration.getInstance(); metricsSystem = NoopMetricsSystem.getInstance(); diff --git a/plugins/avatica-interface/src/main/java/org/polypheny/db/avatica/DbmsMeta.java b/plugins/avatica-interface/src/main/java/org/polypheny/db/avatica/DbmsMeta.java index 63e07011f0..274b4a7590 100644 --- a/plugins/avatica-interface/src/main/java/org/polypheny/db/avatica/DbmsMeta.java +++ b/plugins/avatica-interface/src/main/java/org/polypheny/db/avatica/DbmsMeta.java @@ -1254,36 +1254,22 @@ private PolyType toPolyType( Rep type ) { if ( type == Rep.ARRAY ) { return PolyType.ARRAY; } - switch ( type ) { - case SHORT: - case BYTE: - return PolyType.TINYINT; // cache this - case LONG: - return PolyType.BIGINT; - case NUMBER: - return PolyType.DECIMAL; - case JAVA_SQL_TIME: - return PolyType.TIME; - case JAVA_SQL_DATE: - return PolyType.DATE; - case JAVA_SQL_TIMESTAMP: - return PolyType.TIMESTAMP; - case BOOLEAN: - return PolyType.BOOLEAN; - case DOUBLE: - return PolyType.DOUBLE; - case INTEGER: - return PolyType.INTEGER; - case FLOAT: - return PolyType.FLOAT; - case STRING: - return PolyType.VARCHAR; - case BYTE_STRING: - return PolyType.BINARY; - case OBJECT: - return PolyType.OTHER; - } - throw new NotImplementedException( "sql to polyType " + type ); + return switch ( type ) { + case SHORT, BYTE -> PolyType.TINYINT; // cache this + case LONG -> PolyType.BIGINT; + case NUMBER -> PolyType.DECIMAL; + case JAVA_SQL_TIME -> PolyType.TIME; + case JAVA_SQL_DATE -> PolyType.DATE; + case JAVA_SQL_TIMESTAMP -> PolyType.TIMESTAMP; + case BOOLEAN -> PolyType.BOOLEAN; + case DOUBLE -> PolyType.DOUBLE; + case INTEGER -> PolyType.INTEGER; + case FLOAT -> PolyType.FLOAT; + case STRING -> PolyType.VARCHAR; + case BYTE_STRING -> PolyType.BINARY; + case OBJECT -> PolyType.OTHER; + default -> throw new NotImplementedException( "sql to polyType " + type ); + }; } diff --git a/plugins/cottontail-adapter/build.gradle b/plugins/cottontail-adapter/build.gradle index 440e81129a..be012739d0 100644 --- a/plugins/cottontail-adapter/build.gradle +++ b/plugins/cottontail-adapter/build.gradle @@ -46,13 +46,13 @@ delombok { dependsOn(":plugins:sql-language:processResources") } -inspectClassesForKotlinIC { +/*inspectClassesForKotlinIC { dependsOn(":plugins:cottontail-adapter:compileTestKotlin") } plugin { dependsOn(":plugins:cottontail-adapter:compileTestKotlin") -} +}*/ test.dependsOn(":dbms:shadowJar") @@ -89,7 +89,7 @@ jar { attributes "Copyright": "The Polypheny Project (polypheny.org)" attributes "Version": "$project.version" } - dependsOn(":plugins:cottontail-adapter:compileTestKotlin") + //dependsOn(":plugins:cottontail-adapter:compileTestKotlin") } java { withJavadocJar() @@ -97,7 +97,7 @@ java { } javadoc { - dependsOn(":plugins:cottontail-adapter:compileTestKotlin") + //dependsOn(":plugins:cottontail-adapter:compileTestKotlin") } licensee { diff --git a/plugins/cottontail-adapter/src/main/java/org/polypheny/db/adapter/cottontail/rules/CottontailToEnumerableConverterRule.java b/plugins/cottontail-adapter/src/main/java/org/polypheny/db/adapter/cottontail/rules/CottontailToEnumerableConverterRule.java index 3cc70d7b7e..0c9976d698 100644 --- a/plugins/cottontail-adapter/src/main/java/org/polypheny/db/adapter/cottontail/rules/CottontailToEnumerableConverterRule.java +++ b/plugins/cottontail-adapter/src/main/java/org/polypheny/db/adapter/cottontail/rules/CottontailToEnumerableConverterRule.java @@ -24,6 +24,7 @@ import org.polypheny.db.algebra.enumerable.EnumerableConvention; import org.polypheny.db.plan.AlgTraitSet; import org.polypheny.db.tools.AlgBuilderFactory; +import org.polypheny.db.util.Util; public class CottontailToEnumerableConverterRule extends ConverterRule { @@ -32,11 +33,11 @@ public class CottontailToEnumerableConverterRule extends ConverterRule { public CottontailToEnumerableConverterRule( AlgBuilderFactory algBuilderFactory ) { super( AlgNode.class, - r -> true, + Util::containsEntity, CottontailConvention.INSTANCE, EnumerableConvention.INSTANCE, algBuilderFactory, - "CottontailToEnumerableConverterRule" ); + CottontailToEnumerableConverterRule.class.getSimpleName() ); } diff --git a/plugins/cottontail-adapter/src/main/java/org/polypheny/db/adapter/cottontail/util/CottontailTypeUtil.java b/plugins/cottontail-adapter/src/main/java/org/polypheny/db/adapter/cottontail/util/CottontailTypeUtil.java index e61ea301d3..c35a3ca997 100644 --- a/plugins/cottontail-adapter/src/main/java/org/polypheny/db/adapter/cottontail/util/CottontailTypeUtil.java +++ b/plugins/cottontail-adapter/src/main/java/org/polypheny/db/adapter/cottontail/util/CottontailTypeUtil.java @@ -296,13 +296,21 @@ public static CottontailGrpc.Literal toData( PolyValue value, PolyType actualTyp } break; } + case VARBINARY: + if ( value.isBinary() ) { + return builder.setStringData( value.asBinary().as64String() ).build(); + } + break; case FILE: case IMAGE: case AUDIO: case VIDEO: if ( value.isBlob() ) { return builder.setStringData( value.asBlob().as64String() ).build(); + } else if ( value.isBinary() ) { + return builder.setStringData( value.asBinary().as64String() ).build(); } + break; } log.error( "Conversion not possible! value: {}, type: {}", value.getClass().getCanonicalName(), actualType ); @@ -515,4 +523,4 @@ private static Expression knnCallDistance( RexNode node, ParameterExpression dyn throw new GenericRuntimeException( "Argument is neither an array call nor a dynamic parameter" ); } -} \ No newline at end of file +} diff --git a/plugins/cql-language/src/test/java/org/polypheny/db/cql/utils/helper/CqlTestHelper.java b/plugins/cql-language/src/test/java/org/polypheny/db/cql/utils/helper/CqlTestHelper.java index 9623eda799..da8e7db91b 100644 --- a/plugins/cql-language/src/test/java/org/polypheny/db/cql/utils/helper/CqlTestHelper.java +++ b/plugins/cql-language/src/test/java/org/polypheny/db/cql/utils/helper/CqlTestHelper.java @@ -61,7 +61,7 @@ private static void deployCqlInterface() throws SQLException { try ( JdbcConnection jdbcConnection = new JdbcConnection( false ) ) { Connection connection = jdbcConnection.getConnection(); try ( Statement statement = connection.createStatement() ) { - statement.executeUpdate( "ALTER INTERFACES ADD \"cql\" USING 'org.polypheny.db.http.HttpInterface' WITH '{\"port\":\"8087\"}'" ); + statement.executeUpdate( "ALTER INTERFACES ADD \"cql\" USING 'HTTP Interface' WITH '{\"port\":\"8087\"}'" ); connection.commit(); } } diff --git a/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/FileEnumerator.java b/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/FileEnumerator.java index 1c03e4480e..520bcf9b9b 100644 --- a/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/FileEnumerator.java +++ b/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/FileEnumerator.java @@ -36,6 +36,7 @@ import org.apache.calcite.linq4j.Enumerator; import org.apache.calcite.linq4j.QueryProvider; import org.apache.commons.io.FileUtils; +import org.jetbrains.annotations.NotNull; import org.polypheny.db.adapter.DataContext; import org.polypheny.db.adapter.file.FileAlg.FileImplementor.Operation; import org.polypheny.db.adapter.file.Value.InputValue; @@ -515,7 +516,7 @@ public void addAll( Map map ) { @Override - public void addParameterValues( long index, AlgDataType type, List data ) { + public void addParameterValues( long index, @NotNull AlgDataType type, List data ) { throw new GenericRuntimeException( "Not supported" ); } diff --git a/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/algebra/FileRules.java b/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/algebra/FileRules.java index 0c3375f795..b18816468b 100644 --- a/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/algebra/FileRules.java +++ b/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/algebra/FileRules.java @@ -52,6 +52,7 @@ import org.polypheny.db.schema.types.ModifiableTable; import org.polypheny.db.tools.AlgBuilderFactory; import org.polypheny.db.util.UnsupportedRexCallVisitor; +import org.polypheny.db.util.Util; @Slf4j @@ -154,7 +155,7 @@ static class FileToEnumerableConverterRule extends ConverterRule { public FileToEnumerableConverterRule( FileConvention convention, AlgBuilderFactory algBuilderFactory, Method enumeratorMethod, FileSchema fileSchema ) { - super( AlgNode.class, r -> true, convention, EnumerableConvention.INSTANCE, algBuilderFactory, "FileToEnumerableConverterRule:" + convention.getName() ); + super( AlgNode.class, Util::containsEntity, convention, EnumerableConvention.INSTANCE, algBuilderFactory, "FileToEnumerableConverterRule:" + convention.getName() ); this.enumeratorMethod = enumeratorMethod; this.fileSchema = fileSchema; } @@ -182,7 +183,6 @@ public FileProjectRule( FileConvention out, AlgBuilderFactory algBuilderFactory } - /** * Needed for the {@link FileUnionRule}. * A FileUnion should only be generated during a multi-insert with arrays. diff --git a/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/algebra/FileToEnumerableConverter.java b/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/algebra/FileToEnumerableConverter.java index 9623ae1eac..2352d62f64 100644 --- a/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/algebra/FileToEnumerableConverter.java +++ b/plugins/file-adapter/src/main/java/org/polypheny/db/adapter/file/algebra/FileToEnumerableConverter.java @@ -30,7 +30,6 @@ import org.polypheny.db.adapter.DataContext; import org.polypheny.db.adapter.file.FileAlg.FileImplementor; import org.polypheny.db.adapter.file.FileAlg.FileImplementor.Operation; -import org.polypheny.db.adapter.file.FileConvention; import org.polypheny.db.adapter.file.FileMethod; import org.polypheny.db.adapter.file.FileSchema; import org.polypheny.db.adapter.file.FileTranslatableEntity; @@ -82,7 +81,6 @@ public AlgOptCost computeSelfCost( AlgPlanner planner, AlgMetadataQuery mq ) { @Override public Result implement( EnumerableAlgImplementor implementor, Prefer pref ) { final BlockBuilder list = new BlockBuilder(); - FileConvention convention = (FileConvention) getInput().getConvention(); PhysType physType = PhysTypeImpl.of( implementor.getTypeFactory(), getTupleType(), pref.preferArray() ); FileImplementor fileImplementor = new FileImplementor(); diff --git a/plugins/hsqldb-adapter/src/main/java/org/polypheny/db/hsqldb/stores/HsqldbSqlDialect.java b/plugins/hsqldb-adapter/src/main/java/org/polypheny/db/hsqldb/stores/HsqldbSqlDialect.java index ff4f9bf3b2..8ed3826ccc 100644 --- a/plugins/hsqldb-adapter/src/main/java/org/polypheny/db/hsqldb/stores/HsqldbSqlDialect.java +++ b/plugins/hsqldb-adapter/src/main/java/org/polypheny/db/hsqldb/stores/HsqldbSqlDialect.java @@ -19,8 +19,10 @@ import com.google.common.io.CharStreams; import java.util.Objects; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.apache.calcite.linq4j.tree.Expression; +import org.apache.calcite.linq4j.tree.ParameterExpression; import org.hsqldb.jdbc.JDBCClobClient; import org.polypheny.db.algebra.constant.Kind; import org.polypheny.db.algebra.constant.NullCollation; @@ -41,6 +43,7 @@ import org.polypheny.db.sql.language.SqlWriter; import org.polypheny.db.sql.language.fun.SqlCase; import org.polypheny.db.sql.language.fun.SqlFloorFunction; +import org.polypheny.db.type.PolyType; /** @@ -53,6 +56,7 @@ public class HsqldbSqlDialect extends SqlDialect { EMPTY_CONTEXT .withNullCollation( NullCollation.HIGH ) .withIdentifierQuoteString( "\"" ) ); + public static final int SUBSTITUTION_LENGTH = 20000; /** @@ -86,17 +90,25 @@ public SqlNode getCastSpec( AlgDataType type ) { String castSpec; switch ( type.getPolyType() ) { case ARRAY: - // We need to flag the type with a underscore to flag the type (the underscore is removed in the unparse method) + // We need to flag the type with an underscore to flag the type (the underscore is removed in the unparse method) castSpec = "_LONGVARCHAR"; break; case TEXT: - castSpec = "_VARCHAR(20000)"; + castSpec = "_VARCHAR(" + SUBSTITUTION_LENGTH + ")"; break; + case VARCHAR: + case VARBINARY: + if ( type.getPrecision() == -1 ) { + castSpec = "_" + type.getPolyType().getName() + "(" + SUBSTITUTION_LENGTH + ")"; + break; + } else { + return super.getCastSpec( type ); + } case FILE: case IMAGE: case VIDEO: case AUDIO: - // We need to flag the type with a underscore to flag the type (the underscore is removed in the unparse method) + // We need to flag the type with an underscore to flag the type (the underscore is removed in the unparse method) castSpec = "_BLOB"; break; case INTERVAL: @@ -131,8 +143,8 @@ public void unparseCall( SqlWriter writer, SqlCall call, int leftPrec, int right @Override - public Expression getExpression( AlgDataType fieldType, Expression child ) { - return super.getExpression( fieldType, child ); + public Expression handleRetrieval( AlgDataType fieldType, Expression child, ParameterExpression resultSet_, int index ) { + return super.handleRetrieval( fieldType, child, resultSet_, index ); } @@ -155,6 +167,15 @@ public void unparseOffsetFetch( SqlWriter writer, SqlNode offset, SqlNode fetch } + @Override + public Optional handleMissingLength( PolyType type ) { + return switch ( type ) { + case VARCHAR, VARBINARY, BINARY -> Optional.of( "(" + SUBSTITUTION_LENGTH + ")" ); + default -> Optional.empty(); + }; + } + + @Override public SqlNode rewriteSingleValueExpr( SqlNode aggCall ) { final SqlNode operand = ((SqlBasicCall) aggCall).operand( 0 ); diff --git a/plugins/http-interface/build.gradle b/plugins/http-interface/build.gradle index f72d3fa76a..cc6b510d04 100644 --- a/plugins/http-interface/build.gradle +++ b/plugins/http-interface/build.gradle @@ -35,7 +35,7 @@ delombok { } javadoc { - dependsOn(":plugins:http-interface:compileTestJava") + //dependsOn(":plugins:http-interface:compileTestJava") } @@ -48,7 +48,7 @@ jar { attributes "Copyright": "The Polypheny Project (polypheny.org)" attributes "Version": "$project.version" } - dependsOn(compileTestJava) + //dependsOn(compileTestJava) } java { withJavadocJar() diff --git a/plugins/http-interface/src/main/java/org/polypheny/db/http/HttpInterfacePlugin.java b/plugins/http-interface/src/main/java/org/polypheny/db/http/HttpInterfacePlugin.java index 7631e88b9d..62386588eb 100644 --- a/plugins/http-interface/src/main/java/org/polypheny/db/http/HttpInterfacePlugin.java +++ b/plugins/http-interface/src/main/java/org/polypheny/db/http/HttpInterfacePlugin.java @@ -76,16 +76,14 @@ public HttpInterfacePlugin( PluginContext context ) { @Override public void afterCatalogInit() { // Add HTTP interface - Map httpSettings = new HashMap<>(); - httpSettings.put( "port", "13137" ); - httpSettings.put( "maxUploadSizeMb", "10000" ); - QueryInterfaceManager.addInterfaceType( "http", HttpInterface.class, httpSettings ); + QueryInterfaceManager.addInterfaceTemplate( HttpInterface.INTERFACE_NAME, HttpInterface.INTERFACE_DESCRIPTION, + HttpInterface.AVAILABLE_SETTINGS, HttpInterface::new ); } @Override public void stop() { - QueryInterfaceManager.removeInterfaceType( HttpInterface.class ); + QueryInterfaceManager.removeInterfaceType( HttpInterface.INTERFACE_NAME ); } @@ -116,8 +114,8 @@ public static class HttpInterface extends QueryInterface { private static Javalin server; - public HttpInterface( TransactionManager transactionManager, Authenticator authenticator, long ifaceId, String uniqueName, Map settings ) { - super( transactionManager, authenticator, ifaceId, uniqueName, settings, true, false ); + public HttpInterface( TransactionManager transactionManager, Authenticator authenticator, String uniqueName, Map settings ) { + super( transactionManager, authenticator, uniqueName, settings, true, false ); this.uniqueName = uniqueName; this.port = Integer.parseInt( settings.get( "port" ) ); if ( !Util.checkIfPortIsAvailable( port ) ) { diff --git a/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/JdbcToEnumerableConverter.java b/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/JdbcToEnumerableConverter.java index e171312dc9..f834d92436 100644 --- a/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/JdbcToEnumerableConverter.java +++ b/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/JdbcToEnumerableConverter.java @@ -91,7 +91,6 @@ import org.polypheny.db.type.entity.PolyList; import org.polypheny.db.type.entity.PolyString; import org.polypheny.db.type.entity.PolyValue; -import org.polypheny.db.type.entity.category.PolyBlob; import org.polypheny.db.type.entity.numerical.PolyBigDecimal; import org.polypheny.db.type.entity.numerical.PolyDouble; import org.polypheny.db.type.entity.numerical.PolyFloat; @@ -418,17 +417,17 @@ private static Expression getOfPolyExpression( AlgDataType fieldType, Expression if ( dialect.supportsComplexBinary() ) { poly = Expressions.call( PolyBinary.class, methodName, Expressions.convert_( source, byte[].class ) ); } else { - poly = Expressions.call( PolyBinary.class, "fromTypedJson", Expressions.convert_( source, String.class ), Expressions.constant( PolyBinary.class ) ); + poly = dialect.handleRetrieval( fieldType, source, resultSet_, i + 1 ); } break; case TEXT: - poly = dialect.getExpression( fieldType, source ); + poly = dialect.handleRetrieval( fieldType, source, resultSet_, i + 1 ); break; case FILE: case AUDIO: case IMAGE: case VIDEO: - poly = Expressions.call( PolyBlob.class, methodName, Expressions.convert_( source, byte[].class ) ); + poly = dialect.handleRetrieval( fieldType, source, resultSet_, i + 1 ); break; default: log.warn( "potentially unhandled polyValue" ); diff --git a/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/ResultSetEnumerable.java b/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/ResultSetEnumerable.java index 45995eb139..d60dbf4ece 100644 --- a/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/ResultSetEnumerable.java +++ b/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/ResultSetEnumerable.java @@ -299,11 +299,7 @@ private static void setDynamicParam( PreparedStatement preparedStatement, int i, break; case VARBINARY: case BINARY: - if ( !connectionHandler.getDialect().supportsComplexBinary() ) { - preparedStatement.setString( i, value.asBinary().toTypedJson() ); - } else { - preparedStatement.setBytes( i, value.asBinary().value ); - } + handleBinary( preparedStatement, i, value, connectionHandler ); break; case ARRAY: if ( (type.getComponentType().getPolyType() == PolyType.ARRAY && connectionHandler.getDialect().supportsNestedArrays()) || (type.getComponentType().getPolyType() != PolyType.ARRAY) && connectionHandler.getDialect().supportsArrays() ) { @@ -319,9 +315,18 @@ private static void setDynamicParam( PreparedStatement preparedStatement, int i, case FILE: case VIDEO: if ( connectionHandler.getDialect().supportsBinaryStream() ) { - preparedStatement.setBinaryStream( i, value.asBlob().asBinaryStream() ); + if ( value.isBlob() ) { + preparedStatement.setBinaryStream( i, value.asBlob().asBinaryStream() ); + } else { + handleBinary( preparedStatement, i, value, connectionHandler ); + } + } else { - preparedStatement.setBytes( i, value.asBlob().asByteArray() ); + if ( value.isBlob() ) { + preparedStatement.setBytes( i, value.asBlob().asByteArray() ); + } else { + handleBinary( preparedStatement, i, value, connectionHandler ); + } } break; case TEXT: @@ -334,18 +339,48 @@ private static void setDynamicParam( PreparedStatement preparedStatement, int i, } + private static void handleBinary( PreparedStatement preparedStatement, int i, PolyValue value, ConnectionHandler connectionHandler ) throws SQLException { + if ( !connectionHandler.getDialect().supportsComplexBinary() ) { + preparedStatement.setString( i, value.asBinary().toTypedJson() ); + } else { + preparedStatement.setBytes( i, value.asBinary().value ); + } + } + + private static Array getArray( PolyValue value, AlgDataType type, ConnectionHandler connectionHandler ) throws SQLException { SqlType componentType; AlgDataType t = type; while ( t.getComponentType().getPolyType() == PolyType.ARRAY ) { t = t.getComponentType(); } + componentType = SqlType.valueOf( t.getComponentType().getPolyType().getJdbcOrdinal() ); + if ( t.getComponentType().getPolyType() == PolyType.ANY ) { + componentType = estimateFittingType( value.asList().value ); + } Object[] array = (Object[]) PolyValue.wrapNullableIfNecessary( PolyValue.getPolyToJava( type, false ), type.isNullable() ).apply( value ); return connectionHandler.createArrayOf( connectionHandler.getDialect().getArrayComponentTypeString( componentType ), array ); } + public static SqlType estimateFittingType( List value ) { + if ( value.isEmpty() ) { + return SqlType.NULL; + } else if ( value.stream().allMatch( PolyValue::isBoolean ) ) { + return SqlType.BOOLEAN; + } else if ( value.stream().allMatch( PolyValue::isNumber ) ) { + return SqlType.NUMERIC; + } else if ( value.stream().allMatch( PolyValue::isBinary ) ) { + return SqlType.BINARY; + } else if ( value.stream().allMatch( PolyValue::isString ) ) { + return SqlType.VARCHAR; + } + + return SqlType.ANY; + } + + @Override public Enumerator enumerator() { if ( preparedStatementEnricher == null ) { diff --git a/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/rel2sql/AlgToSqlConverter.java b/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/rel2sql/AlgToSqlConverter.java index 0ea24c1ee9..082a96a97a 100644 --- a/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/rel2sql/AlgToSqlConverter.java +++ b/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/rel2sql/AlgToSqlConverter.java @@ -496,7 +496,11 @@ private SqlNodeList identifierList( List names ) { * Converts a list of names expressions to a list of single-part {@link SqlIdentifier}s. */ private SqlNodeList physicalIdentifierList( JdbcTable entity, List columnNames ) { - return new SqlNodeList( entity.columns.stream().filter( c -> columnNames.contains( c.logicalName ) ).map( c -> new SqlIdentifier( c.name, ParserPos.ZERO ) ).toList(), POS ); + List columns = entity.columns.stream().filter( c -> columnNames.contains( c.logicalName ) ).map( c -> new SqlIdentifier( c.name, ParserPos.ZERO ) ).toList(); + if ( columns.isEmpty() ) { + columns = entity.columns.stream().map( c -> new SqlIdentifier( c.name, ParserPos.ZERO ) ).toList(); + } + return new SqlNodeList( columns, POS ); } @@ -640,7 +644,7 @@ private void parseCorrelTable( AlgNode algNode, Result x ) { /** * Stack frame. */ - private record Frame(int ordinalInParent, AlgNode r) { + private record Frame( int ordinalInParent, AlgNode r ) { } diff --git a/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/stores/AbstractJdbcStore.java b/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/stores/AbstractJdbcStore.java index 2aa1890ad6..953343b626 100644 --- a/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/stores/AbstractJdbcStore.java +++ b/plugins/jdbc-adapter-framework/src/main/java/org/polypheny/db/adapter/jdbc/stores/AbstractJdbcStore.java @@ -35,7 +35,6 @@ import org.polypheny.db.adapter.jdbc.JdbcTable; import org.polypheny.db.adapter.jdbc.JdbcUtils; import org.polypheny.db.adapter.jdbc.connection.ConnectionFactory; -import org.polypheny.db.adapter.jdbc.connection.ConnectionHandlerException; import org.polypheny.db.catalog.catalogs.RelAdapterCatalog; import org.polypheny.db.catalog.entity.allocation.AllocationTable; import org.polypheny.db.catalog.entity.allocation.AllocationTableWrapper; @@ -263,12 +262,17 @@ protected void createColumnDefinition( PhysicalColumn column, StringBuilder buil PolyType collectionsType = column.collectionsType == PolyType.ARRAY ? null : column.collectionsType; // nested array was not suppored builder.append( " " ).append( getTypeString( type ) ); - if ( column.length != null && doesTypeUseLength( type ) ) { - builder.append( "(" ).append( column.length ); - if ( column.scale != null ) { - builder.append( "," ).append( column.scale ); + if ( doesTypeUseLength( type ) ) { + if ( column.length == null && dialect.handleMissingLength( type ).isPresent() ) { + builder.append( dialect.handleMissingLength( type ).get() ); + } else if ( column.length != null ) { + builder.append( "(" ).append( column.length ); + if ( column.scale != null ) { + builder.append( "," ).append( column.scale ); + } + builder.append( ")" ); } - builder.append( ")" ); + } if ( collectionsType != null ) { builder.append( " " ).append( getTypeString( column.collectionsType ) ); @@ -403,7 +407,7 @@ protected void executeUpdate( StringBuilder builder, Context context ) { try { context.getStatement().getTransaction().registerInvolvedAdapter( this ); connectionFactory.getOrCreateConnectionHandler( context.getStatement().getTransaction().getXid() ).executeUpdate( builder.toString() ); - } catch ( SQLException | ConnectionHandlerException e ) { + } catch ( Exception e ) { throw new GenericRuntimeException( e ); } } diff --git a/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/MonetdbSqlDialect.java b/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/MonetdbSqlDialect.java index 79d1a20e6a..c0f9e43060 100644 --- a/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/MonetdbSqlDialect.java +++ b/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/MonetdbSqlDialect.java @@ -17,11 +17,13 @@ package org.polypheny.db.adapter.monetdb; +import javax.annotation.Nullable; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import nl.cwi.monetdb.jdbc.MonetClob; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; +import org.apache.calcite.linq4j.tree.ParameterExpression; import org.apache.calcite.linq4j.tree.UnaryExpression; import org.polypheny.db.algebra.AlgNode; import org.polypheny.db.algebra.constant.Kind; @@ -48,6 +50,8 @@ import org.polypheny.db.sql.language.SqlNode; import org.polypheny.db.sql.language.SqlWriter; import org.polypheny.db.type.PolyType; +import org.polypheny.db.type.entity.PolyBinary; +import org.polypheny.db.type.entity.PolyValue; /** @@ -103,18 +107,21 @@ public SqlNode getCastSpec( AlgDataType type ) { castSpec = "_SMALLINT"; break; case ARRAY: - // We need to flag the type with a underscore to flag the type (the underscore is removed in the unparse method) + // We need to flag the type with an underscore to flag the type (the underscore is removed in the unparse method) castSpec = "_TEXT"; break; - case VARBINARY: + case VARBINARY, BINARY: castSpec = "_TEXT"; break; + case BIGINT: + castSpec = "_HUGEINT"; + break; case FILE: case IMAGE: case VIDEO: case AUDIO: - // We need to flag the type with a underscore to flag the type (the underscore is removed in the unparse method) - castSpec = "_BLOB"; + // We need to flag the type with an underscore to flag the type (the underscore is removed in the unparse method) + castSpec = "_TEXT"; break; default: return super.getCastSpec( type ); @@ -125,17 +132,34 @@ public SqlNode getCastSpec( AlgDataType type ) { @Override - public Expression getExpression( AlgDataType fieldType, Expression child ) { + public Expression handleRetrieval( AlgDataType fieldType, Expression child, ParameterExpression resultSet_, int index ) { return switch ( fieldType.getPolyType() ) { - case TEXT -> { + case FILE, AUDIO, IMAGE, VIDEO -> { + UnaryExpression client = Expressions.convert_( Expressions.call( resultSet_, "getObject", Expressions.constant( index ) ), MonetClob.class ); + yield Expressions.call( + MonetdbSqlDialect.class, + "nullableFromTypedJson", + Expressions.convert_( Expressions.call( MonetdbSqlDialect.class, "toString", client ), String.class ), + Expressions.constant( PolyBinary.class ) ); + } + case TEXT, VARBINARY -> { UnaryExpression client = Expressions.convert_( child, MonetClob.class ); - yield super.getExpression( fieldType, Expressions.call( MonetdbSqlDialect.class, "toString", client ) ); + yield super.handleRetrieval( fieldType, Expressions.call( MonetdbSqlDialect.class, "toString", client ), resultSet_, index ); } - default -> super.getExpression( fieldType, child ); + default -> super.handleRetrieval( fieldType, child, resultSet_, index ); }; } + @SuppressWarnings("unused") + public static PolyValue nullableFromTypedJson( @Nullable String value, Class type ) { + if ( value == null ) { + return null; + } + return PolyValue.fromTypedJson( value, type ); + } + + @SuppressWarnings("unused") public static String toString( MonetClob clob ) { if ( clob == null ) { diff --git a/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/sources/MonetdbSource.java b/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/sources/MonetdbSource.java index b156dc16b7..859e666b16 100644 --- a/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/sources/MonetdbSource.java +++ b/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/sources/MonetdbSource.java @@ -46,7 +46,7 @@ description = "MonetDB is an execute-source column-oriented database management system. It is based on an optimistic concurrency control.", usedModes = DeployMode.REMOTE, defaultMode = DeployMode.REMOTE) -@AdapterSettingString(name = "hose", defaultValue = "localhost", description = "Hostname or IP address of the remote MonetDB instance.", position = 1) +@AdapterSettingString(name = "host", defaultValue = "localhost", description = "Hostname or IP address of the remote MonetDB instance.", position = 1) @AdapterSettingInteger(name = "port", defaultValue = 50000, description = "JDBC port number on the remote MonetDB instance.", position = 2) @AdapterSettingString(name = "database", defaultValue = "polypheny", description = "JDBC port number on the remote MonetDB instance.", position = 3) @AdapterSettingString(name = "username", defaultValue = "polypheny", description = "Name of the database to connect to.", position = 4) diff --git a/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/stores/MonetdbStore.java b/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/stores/MonetdbStore.java index d3252b81d3..7b4d948c87 100644 --- a/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/stores/MonetdbStore.java +++ b/plugins/monetdb-adapter/src/main/java/org/polypheny/db/adapter/monetdb/stores/MonetdbStore.java @@ -300,15 +300,15 @@ protected void reloadSettings( List updatedSettings ) { @Override protected String getTypeString( PolyType type ) { if ( type.getFamily() == PolyTypeFamily.MULTIMEDIA ) { - return "BLOB"; + return "TEXT"; } return switch ( type ) { case BOOLEAN -> "BOOLEAN"; - case VARBINARY -> "VARCHAR";//throw new GenericRuntimeException( "Unsupported datatype: " + type.name() ); + case VARBINARY, BINARY -> "TEXT"; case TINYINT -> "SMALLINT"; // there seems to be an issue with tinyints and the jdbc driver case SMALLINT -> "SMALLINT"; case INTEGER -> "INT"; - case BIGINT -> "BIGINT"; + case BIGINT -> "HUGEINT"; case REAL -> "REAL"; case DOUBLE -> "DOUBLE"; case DECIMAL -> "DECIMAL"; diff --git a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/MongoEnumerator.java b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/MongoEnumerator.java index 734d7e8c04..c97a7db98b 100644 --- a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/MongoEnumerator.java +++ b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/MongoEnumerator.java @@ -196,8 +196,9 @@ private static PolyValue convert( BsonValue o, MongoTupleType type ) { throw new NotImplementedException(); } } + case FILE, AUDIO, IMAGE, VIDEO -> handleMultimedia( o, type ); case INTERVAL -> new PolyInterval( o.asDocument().get( BsonUtil.DOC_MILLIS_KEY ).asNumber().longValue(), o.asDocument().get( BsonUtil.DOC_MONTH_KEY ).asNumber().longValue() ); - case BINARY -> PolyBinary.of( o.asBinary().getData() ); + case BINARY, VARBINARY -> PolyBinary.of( o.asBinary().getData() ); case TIMESTAMP -> PolyTimestamp.of( o.asNumber().longValue() ); case TIME -> PolyTime.of( o.asNumber().longValue() ); case DATE -> PolyDate.of( o.asNumber().longValue() ); @@ -209,6 +210,21 @@ private static PolyValue convert( BsonValue o, MongoTupleType type ) { } + /** + * This only requires to handle binary files, interaction with bucket is handled before. + * + * @param value the data + * @param type the describing type of the data + * @return the transformed PolyValue + */ + private static PolyValue handleMultimedia( BsonValue value, MongoTupleType type ) { + if ( value.isBinary() ) { + return PolyBinary.of( value.asBinary().getData() ); + } + throw new GenericRuntimeException( "Multimedia type " + type.type + " is not correctly handled." ); + } + + private static PolyDocument polyDocumentFromBson( BsonDocument document ) { PolyDocument doc = new PolyDocument(); for ( String key : document.keySet() ) { diff --git a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/bson/BsonDynamic.java b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/bson/BsonDynamic.java index d233caad78..3b47d045e5 100644 --- a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/bson/BsonDynamic.java +++ b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/bson/BsonDynamic.java @@ -22,13 +22,14 @@ import org.bson.BsonInt64; import org.bson.BsonNull; import org.bson.BsonString; +import org.polypheny.db.adapter.mongodb.util.MongoDynamic; import org.polypheny.db.algebra.type.AlgDataType; import org.polypheny.db.rex.RexDynamicParam; import org.polypheny.db.type.PolyType; +@Getter public class BsonDynamic extends BsonDocument { - @Getter private final long id; @@ -38,58 +39,58 @@ public BsonDynamic( RexDynamicParam rexNode ) { private static String getTypeString( AlgDataType type ) { - return type.getPolyType() != PolyType.ARRAY + String textualType = type.getPolyType() != PolyType.ARRAY ? type.getPolyType().getTypeName() : "ARRAY$" + getTypeString( type.getComponentType() ); + return textualType + (type.getPrecision() == AlgDataType.PRECISION_NOT_SPECIFIED ? "" : "|" + (type.getPolyType() == PolyType.DECIMAL ? type.getScale() : type.getPrecision())); } public BsonDynamic( long id, String polyTypeName ) { - super(); this.id = id; - append( "_dyn", new BsonInt64( id ) ); - append( "_type", new BsonString( polyTypeName ) ); - append( "_reg", new BsonBoolean( false ) ); - append( "_func", new BsonBoolean( false ) ); - append( "_functionName", BsonNull.VALUE ); + append( MongoDynamic.MONGO_DYNAMIC_INDEX_KEY, new BsonInt64( id ) ); + append( MongoDynamic.MONGO_DYNAMIC_TYPE_KEY, new BsonString( polyTypeName ) ); + append( MongoDynamic.MONGO_DYNAMIC_REGEX_KEY, new BsonBoolean( false ) ); + append( MongoDynamic.MONGO_DYNAMIC_FUNC_BODY_KEY, new BsonBoolean( false ) ); + append( MongoDynamic.MONGO_DYNAMIC_FUNC_NAME_KEY, BsonNull.VALUE ); } public static BsonDocument addFunction( BsonDocument doc, String functionName ) { - return doc.append( "_functionName", new BsonString( functionName ) ); + return doc.append( MongoDynamic.MONGO_DYNAMIC_FUNC_NAME_KEY, new BsonString( functionName ) ); } public static BsonDocument changeType( BsonDocument doc, PolyType polyType ) { - return doc.append( "_type", new BsonString( polyType.getTypeName() ) ); + return doc.append( MongoDynamic.MONGO_DYNAMIC_TYPE_KEY, new BsonString( polyType.getTypeName() ) ); } public BsonDynamic setIsRegex( boolean isRegex ) { - append( "_reg", new BsonBoolean( isRegex ) ); + append( MongoDynamic.MONGO_DYNAMIC_REGEX_KEY, new BsonBoolean( isRegex ) ); return this; } public boolean isRegex() { - return getBoolean( "_reg" ).getValue(); + return getBoolean( MongoDynamic.MONGO_DYNAMIC_REGEX_KEY ).getValue(); } public BsonDynamic setIsFunc( boolean isFunc ) { - append( "_func", new BsonBoolean( isFunc ) ); + append( MongoDynamic.MONGO_DYNAMIC_FUNC_BODY_KEY, new BsonBoolean( isFunc ) ); return this; } public BsonDynamic setPolyFunction( String functionName ) { - append( "_functionName", new BsonString( functionName ) ); + append( MongoDynamic.MONGO_DYNAMIC_FUNC_NAME_KEY, new BsonString( functionName ) ); return this; } public BsonDynamic adjustType( PolyType polyType ) { - append( "_type", new BsonString( polyType.getTypeName() ) ); + append( MongoDynamic.MONGO_DYNAMIC_TYPE_KEY, new BsonString( polyType.getTypeName() ) ); return this; } diff --git a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/bson/BsonFunctionHelper.java b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/bson/BsonFunctionHelper.java index 67ba5707b5..c45224ef95 100644 --- a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/bson/BsonFunctionHelper.java +++ b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/bson/BsonFunctionHelper.java @@ -33,16 +33,17 @@ public class BsonFunctionHelper extends BsonDocument { - static String decimalCast = "if( arr1.length > 1) {\n" - + " if ( arr1[0].toString().includes('NumberDecimal')) {\n" - + " arr1 = arr1.map( a => parseFloat(a.toString().replace('NumberDecimal(\\\"','').replace('\\\")', '')))\n" - + " }\n" - + " }\n" - + " if( arr1.length > 1) {\n" - + " if ( arr2[0].toString().includes('NumberDecimal')) {\n" - + " arr2 = arr2.map( a => parseFloat(a.toString().replace('NumberDecimal(\\\"','').replace('\\\")', '')))\n" - + " }\n" - + " }"; + static String decimalCast = """ + if( arr1.length > 1) { + if ( arr1[0].toString().includes('NumberDecimal')) { + arr1 = arr1.map( a => parseFloat(a.toString().replace('NumberDecimal(\\"','').replace('\\")', ''))) + } + } + if( arr1.length > 1) { + if ( arr2[0].toString().includes('NumberDecimal')) { + arr2 = arr2.map( a => parseFloat(a.toString().replace('NumberDecimal(\\"','').replace('\\")', ''))) + } + }"""; static String l2Function = "function(arr1, arr2){" + decimalCast diff --git a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoFilter.java b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoFilter.java index 5434ba433b..4fb0f49946 100644 --- a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoFilter.java +++ b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoFilter.java @@ -575,7 +575,9 @@ private void translateExists( RexCall node ) { if ( node.operands.get( 1 ).isA( Kind.DYNAMIC_PARAM ) ) { BsonDynamic identifier = new BsonDynamic( node.operands.get( 2 ).unwrap( RexDynamicParam.class ) - .orElseThrow() ).setPolyFunction( "joinPoint" ).adjustType( PolyType.VARCHAR ); + .orElseThrow() ) + .setPolyFunction( "joinPoint" ) + .adjustType( PolyType.VARCHAR ); BsonKeyValue keyValue = new BsonKeyValue( identifier, new BsonDocument().append( "$exists", new BsonDynamic( node.operands.get( 1 ).unwrap( RexDynamicParam.class ).orElseThrow() ) ) ); attachCondition( null, keyValue.placeholderKey, keyValue ); } else if ( node.operands.get( 2 ).unwrap( RexCall.class ).isPresent() ) { @@ -1035,16 +1037,17 @@ private boolean translateFunction( String op, RexCall right, RexNode left ) { String randomName = getRandomName(); this.preProjections.put( randomName, BsonFunctionHelper.getFunction( right, rowType, implementor ) ); - switch ( left.getKind() ) { - case LITERAL: + return switch ( left.getKind() ) { + case LITERAL -> { attachCondition( op, randomName, BsonUtil.getAsBson( (RexLiteral) left, bucket ) ); - return true; - case DYNAMIC_PARAM: + yield true; + } + case DYNAMIC_PARAM -> { attachCondition( op, randomName, new BsonDynamic( (RexDynamicParam) left ) ); - return true; - default: - return false; - } + yield true; + } + default -> false; + }; } diff --git a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoTableModify.java b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoTableModify.java index 16b12c792f..cc5d89d7db 100644 --- a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoTableModify.java +++ b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoTableModify.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,6 +42,8 @@ import org.polypheny.db.algebra.type.AlgDataType; import org.polypheny.db.algebra.type.AlgDataTypeField; import org.polypheny.db.catalog.entity.physical.PhysicalCollection; +import org.polypheny.db.catalog.entity.physical.PhysicalColumn; +import org.polypheny.db.catalog.entity.physical.PhysicalField; import org.polypheny.db.catalog.exceptions.GenericRuntimeException; import org.polypheny.db.plan.AlgCluster; import org.polypheny.db.plan.AlgOptCost; @@ -240,7 +243,7 @@ private void attachUpdateStep( BsonDocument doc, RexCall el, AlgDataType rowType doc.putAll( getRenameUpdate( keys, key, (RexCall) el.operands.get( 2 ) ) ); break; default: - throw new RuntimeException( "The used update operation is not supported by the MongoDB adapter." ); + throw new GenericRuntimeException( "The used update operation is not supported by the MongoDB adapter." ); } } @@ -370,7 +373,7 @@ private BsonValue visitCall( Implementor implementor, RexCall call, Kind op, Pol doc.append( "$divide", array ); break; default: - throw new RuntimeException( "Not implemented yet" ); + throw new GenericRuntimeException( "Not implemented yet" ); } return doc; @@ -394,7 +397,7 @@ private void handlePreparedInsert( Implementor implementor, MongoProject input ) String physicalName = entity.getPhysicalName( input.getTupleType().getFields().get( pos ).getName() ); if ( rexNode instanceof RexDynamicParam ) { // preparedInsert - doc.append( physicalName, new BsonDynamic( (RexDynamicParam) rexNode ) ); + doc.append( physicalName == null ? implementor.getEntity().fields.stream().sorted( Comparator.comparingInt( ( PhysicalField a ) -> a.unwrap( PhysicalColumn.class ).orElseThrow().position ) ).toList().get( pos ).name : physicalName, new BsonDynamic( (RexDynamicParam) rexNode ) ); } else if ( rexNode instanceof RexLiteral ) { doc.append( physicalName, BsonUtil.getAsBson( (RexLiteral) rexNode, bucket ) ); } else if ( rexNode instanceof RexCall ) { @@ -437,11 +440,11 @@ private BsonValue getBsonArray( RexCall el, PolyType type, GridFSBucket bucket ) } else if ( operand instanceof RexCall ) { return getBsonArray( (RexCall) operand, type, bucket ); } - throw new RuntimeException( "The given RexCall could not be transformed correctly." ); + throw new GenericRuntimeException( "The given RexCall could not be transformed correctly." ); } ).toList() ); return array; } - throw new RuntimeException( "The given RexCall could not be transformed correctly." ); + throw new GenericRuntimeException( "The given RexCall could not be transformed correctly." ); } diff --git a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoToEnumerableConverterRule.java b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoToEnumerableConverterRule.java index afeac16147..f462e7deb1 100644 --- a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoToEnumerableConverterRule.java +++ b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/rules/MongoToEnumerableConverterRule.java @@ -24,6 +24,7 @@ import org.polypheny.db.algebra.enumerable.EnumerableConvention; import org.polypheny.db.plan.AlgTraitSet; import org.polypheny.db.tools.AlgBuilderFactory; +import org.polypheny.db.util.Util; /** @@ -42,7 +43,7 @@ public class MongoToEnumerableConverterRule extends ConverterRule { public MongoToEnumerableConverterRule( AlgBuilderFactory algBuilderFactory ) { super( AlgNode.class, - r -> true, + Util::containsEntity, MongoAlg.CONVENTION, EnumerableConvention.INSTANCE, algBuilderFactory, diff --git a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/util/MongoDynamic.java b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/util/MongoDynamic.java index 6a3b6fea63..7c0e5ba84d 100644 --- a/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/util/MongoDynamic.java +++ b/plugins/mongodb-adapter/src/main/java/org/polypheny/db/adapter/mongodb/util/MongoDynamic.java @@ -25,11 +25,14 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Queue; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import lombok.Setter; +import lombok.Value; +import lombok.experimental.NonFinal; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonString; @@ -43,6 +46,7 @@ import org.polypheny.db.type.PolyType; import org.polypheny.db.type.entity.PolyValue; import org.polypheny.db.util.BsonUtil; +import org.polypheny.db.util.Pair; /** @@ -50,34 +54,44 @@ * they initial prepare the correct places to insert later provided dynamic parameters according to the * initial provided BsonDocument */ +@Value +@NonFinal public class MongoDynamic { - private final Map> docHandles = new HashMap<>(); // parent, key, - private final Map> arrayHandles = new HashMap<>(); // parent, index, + public static final String MONGO_DYNAMIC_INDEX_KEY = "_dyn"; + public static final String MONGO_DYNAMIC_REGEX_KEY = "_reg"; + public static final String MONGO_DYNAMIC_FUNC_BODY_KEY = "_func"; + public static final String MONGO_DYNAMIC_FUNC_NAME_KEY = "_functionName"; + public static final String MONGO_DYNAMIC_TYPE_KEY = "_type"; - private final Map> keyHandles = new HashMap<>(); // parent, index, - private final Map> transformers = new HashMap<>(); - protected final GridFSBucket bucket; - private final BsonDocument document; - private final Map isRegexMap = new HashMap<>(); - private final Map isFuncMap = new HashMap<>(); - private final boolean isProject; + public static final String DOCUMENT_PROJECT_KEY = "$project"; - private final Map keyMap = new HashMap<>(); - private final Map functionNames = new HashMap<>(); + Map> docHandles = new HashMap<>(); // parent, key, + Map> arrayHandles = new HashMap<>(); // parent, index, + + Map> keyHandles = new HashMap<>(); // parent, index, + Map> transformers = new HashMap<>(); + GridFSBucket bucket; + BsonDocument document; + Map isRegexMap = new HashMap<>(); + Map isFuncMap = new HashMap<>(); + boolean isProject; + + Map keyMap = new HashMap<>(); + Map functionNames = new HashMap<>(); public MongoDynamic( BsonDocument document, GridFSBucket bucket ) { this.document = document.clone(); this.bucket = bucket; - this.isProject = !document.isEmpty() && document.getFirstKey().equals( "$project" ); + this.isProject = !document.isEmpty() && document.getFirstKey().equals( DOCUMENT_PROJECT_KEY ); this.document.forEach( ( k, bsonValue ) -> replaceDynamic( bsonValue, this.document, k, true, false ) ); } public static MongoDynamic create( BsonDocument document, GridFSBucket bucket, DataModel dataModel ) { if ( dataModel == DataModel.DOCUMENT ) { - return new MongoDocumentDynamic( document, bucket, DataModel.DOCUMENT ); + return new MongoDocumentDynamic( document, bucket ); } return new MongoDynamic( document, bucket ); } @@ -94,7 +108,7 @@ public static MongoDynamic create( BsonDocument document, GridFSBucket bucket, D */ public void replaceDynamic( BsonValue preDocument, BsonValue parent, Object key, boolean isDoc, boolean isKey ) { if ( preDocument.getBsonType() == BsonType.DOCUMENT ) { - if ( ((BsonDocument) preDocument).containsKey( "_dyn" ) ) { + if ( ((BsonDocument) preDocument).containsKey( MONGO_DYNAMIC_INDEX_KEY ) ) { // prepared handleDynamic( (BsonDocument) preDocument, parent, key, isDoc, isKey ); } else if ( isDynamic( preDocument ) ) { @@ -119,17 +133,17 @@ public void replaceDynamic( BsonValue preDocument, BsonValue parent, Object key, private void handleDynamic( BsonDocument preDocument, BsonValue parent, Object key, boolean isDoc, boolean isKey ) { - BsonValue bsonIndex = preDocument.get( "_dyn" ); - Boolean isRegex = preDocument.get( "_reg" ).asBoolean().getValue(); - Boolean isFunction = preDocument.get( "_func" ).asBoolean().getValue(); - String functionName = preDocument.get( "_functionName" ).isNull() ? null : preDocument.get( "_functionName" ).asString().getValue(); + BsonValue bsonIndex = preDocument.get( MONGO_DYNAMIC_INDEX_KEY ); + Boolean isRegex = preDocument.get( MONGO_DYNAMIC_REGEX_KEY ).asBoolean().getValue(); + Boolean isFunction = preDocument.get( MONGO_DYNAMIC_FUNC_BODY_KEY ).asBoolean().getValue(); + String functionName = preDocument.get( MONGO_DYNAMIC_FUNC_NAME_KEY ).isNull() ? null : preDocument.get( MONGO_DYNAMIC_FUNC_NAME_KEY ).asString().getValue(); long pos; if ( bsonIndex.isInt64() ) { pos = bsonIndex.asInt64().getValue(); } else { pos = bsonIndex.asInt32().getValue(); } - Queue polyTypes = Arrays.stream( preDocument.get( "_type" ).asString().getValue().split( "\\$" ) ).map( PolyType::valueOf ).collect( Collectors.toCollection( LinkedList::new ) ); + Queue>> polyTypes = Arrays.stream( preDocument.get( MONGO_DYNAMIC_TYPE_KEY ).asString().getValue().split( "\\$" ) ).map( type -> type.split( "\\|" ) ).map( p -> Pair.of( PolyType.valueOf( p[0] ), (p.length > 1 ? Optional.of( Integer.parseInt( p[1] ) ) : Optional.ofNullable( (Integer) null )) ) ).collect( Collectors.toCollection( LinkedList::new ) ); if ( isDoc ) { addDocHandle( pos, (BsonDocument) parent, (String) key, polyTypes, isRegex, isFunction, functionName ); @@ -168,9 +182,9 @@ private boolean isDynamic( BsonValue preDocument ) { * @param types type of the object itself, to retrieve the correct MongoDB type * @param isRegex flag if the BsonDynamic is a regex, which needs to adjusted * @param isFunction flag if the BsonDynamic is defined function, which has to be retrieved uniquely - * @param functionName + * @param functionName name of the function */ - public void addDocHandle( long index, BsonDocument doc, String key, Queue types, Boolean isRegex, Boolean isFunction, String functionName ) { + public void addDocHandle( long index, BsonDocument doc, String key, Queue>> types, Boolean isRegex, Boolean isFunction, String functionName ) { if ( !arrayHandles.containsKey( index ) ) { initMaps( index, types, isRegex, isFunction, functionName ); } @@ -178,7 +192,7 @@ public void addDocHandle( long index, BsonDocument doc, String key, Queue types, Boolean isRegex, Boolean isFunction, String functionName ) { + public void addKeyHandle( long index, BsonDocument doc, Queue>> types, Boolean isRegex, Boolean isFunction, String functionName ) { if ( !arrayHandles.containsKey( index ) ) { initMaps( index, types, isRegex, isFunction, functionName ); } @@ -203,9 +217,9 @@ public void addKeyHandle( long index, BsonDocument doc, Queue types, B * @param types type of the object itself, to retrieve the correct MongoDB type * @param isRegex flag if the BsonDynamic is a regex, which needs to adjusted * @param isFunction flag if the BsonDynamic is defined function, which has to be retrieved uniquely - * @param functionName + * @param functionName name of the function */ - public void addArrayHandle( long index, BsonArray array, int pos, Queue types, Boolean isRegex, Boolean isFunction, String functionName ) { + public void addArrayHandle( long index, BsonArray array, int pos, Queue>> types, Boolean isRegex, Boolean isFunction, String functionName ) { if ( !arrayHandles.containsKey( index ) ) { initMaps( index, types, isRegex, isFunction, functionName ); } @@ -213,7 +227,7 @@ public void addArrayHandle( long index, BsonArray array, int pos, Queue types, Boolean isRegex, Boolean isFunction, String functionName ) { + private void initMaps( long index, Queue>> types, Boolean isRegex, Boolean isFunction, String functionName ) { this.transformers.put( index, BsonUtil.getBsonTransformer( types, bucket ) ); this.isRegexMap.put( index, isRegex ); this.isFuncMap.put( index, isFunction ); @@ -314,7 +328,7 @@ public List> getAll( public List getAll( List> parameterValues ) { - return parameterValues.stream().map( value -> BsonUtil.asDocument( insert( value ) ) ).collect( Collectors.toList() ); + return parameterValues.stream().map( value -> BsonUtil.asDocument( insert( value ) ) ).toList(); } @@ -355,7 +369,7 @@ public void insert( BsonValue value ) { * Helper class which holds replace information for a BsonDocument, which has one or multiple dynamic children * and defines how the child can be replaced. */ - record ArrayWrapper(int index, BsonArray array) implements Wrapper { + record ArrayWrapper( int index, BsonArray array ) implements Wrapper { @Override @@ -366,7 +380,7 @@ public void insert( BsonValue value ) { } - record KeyWrapper(int index, BsonDocument document, List children) implements Wrapper { + record KeyWrapper( int index, BsonDocument document, List children ) implements Wrapper { @Override @@ -384,14 +398,14 @@ public void insert( BsonValue value ) { public static class MongoDocumentDynamic extends MongoDynamic { - public MongoDocumentDynamic( BsonDocument document, GridFSBucket bucket, DataModel dataModel ) { + public MongoDocumentDynamic( BsonDocument document, GridFSBucket bucket ) { super( document, bucket ); } @Override public List getAll( List> parameterValues ) { - return parameterValues.stream().flatMap( e -> e.entrySet().stream().map( v -> Document.parse( v.getValue().asDocument().toJson() ) ) ).collect( Collectors.toList() ); + return parameterValues.stream().flatMap( e -> e.values().stream().map( polyValue -> Document.parse( polyValue.asDocument().toJson() ) ) ).collect( Collectors.toList() ); } diff --git a/plugins/mql-language/src/main/java/org/polypheny/db/languages/MongoLanguagePlugin.java b/plugins/mql-language/src/main/java/org/polypheny/db/languages/MongoLanguagePlugin.java index 685e092852..56db38b764 100644 --- a/plugins/mql-language/src/main/java/org/polypheny/db/languages/MongoLanguagePlugin.java +++ b/plugins/mql-language/src/main/java/org/polypheny/db/languages/MongoLanguagePlugin.java @@ -124,6 +124,7 @@ private static List anyQuerySplitter( QueryContext context ) .namespaceId( context.getNamespaceId() ) .transactionManager( context.getTransactionManager() ) .origin( context.getOrigin() ) + .isAutoGenerated( true ) .informationTarget( context.getInformationTarget() ) .build() ).map( LanguageManager::toQueryNodes ).toList(); @@ -137,11 +138,6 @@ private static List anyQuerySplitter( QueryContext context ) } - public String preprocessing( String query, QueryContext context ) { - return query; - } - - public static void registerOperators() { if ( isInit ) { throw new GenericRuntimeException( "Mql operators were already registered." ); diff --git a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/Neo4jPlugin.java b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/Neo4jPlugin.java index e999c599d5..1b98fa8a0c 100644 --- a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/Neo4jPlugin.java +++ b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/Neo4jPlugin.java @@ -35,9 +35,9 @@ import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Result; import org.neo4j.driver.Session; import org.neo4j.driver.Transaction; -import org.neo4j.driver.exceptions.Neo4jException; import org.pf4j.Extension; import org.polypheny.db.adapter.AdapterManager; import org.polypheny.db.adapter.DataStore; @@ -262,11 +262,12 @@ public void executeDdlTrx( PolyXid xid, List queries ) { Transaction trx = transactionProvider.getDdlTransaction(); try { for ( String query : queries ) { - trx.run( query ); + Result result = trx.run( query ); + result.consume(); } transactionProvider.commitDdlTransaction(); - } catch ( Neo4jException e ) { + } catch ( Exception e ) { transactionProvider.rollbackDdlTransaction(); throw new GenericRuntimeException( e ); } @@ -311,6 +312,7 @@ public List refreshTable( long allocId ) { @Override public void dropTable( Context context, long allocId ) { + transactionProvider.commitAll(); context.getStatement().getTransaction().registerInvolvedAdapter( this ); PhysicalEntity physical = adapterCatalog.fromAllocation( allocId, PhysicalEntity.class ); diff --git a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoEntity.java b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoEntity.java index a89e28e70a..42c027f60e 100644 --- a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoEntity.java +++ b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoEntity.java @@ -187,9 +187,10 @@ public NeoQueryable( DataContext dataContext, Snapshot snapshot, NeoEntity entit @Override public Enumerator enumerator() { return execute( - String.format( "MATCH (n:%s) RETURN %s", entity.name, buildAllQuery() ), + "MATCH (n:%s) RETURN %s".formatted( entity.name, buildAllQuery() ), getTypes(), - Map.of() ).enumerator(); + Map.of(), + false ).enumerator(); } @@ -210,7 +211,7 @@ private String buildAllQuery() { * @param prepared mapping of parameters and their components if they are collections */ @SuppressWarnings("UnusedDeclaration") - public Enumerable execute( String query, NestedPolyType types, Map prepared ) { + public Enumerable execute( String query, NestedPolyType types, Map prepared, boolean needsPreparedReturn ) { Transaction trx = getTrx(); dataContext.getStatement().getTransaction().registerInvolvedAdapter( entity.namespace.store ); @@ -221,12 +222,15 @@ public Enumerable execute( String query, NestedPolyType types, Map< List results = new ArrayList<>(); if ( dataContext.getParameterValues().size() == 1 ) { - results.add( trx.run( query, toParameters( dataContext.getParameterValues().get( 0 ), prepared, false ) ) ); + String adjustedQuery = needsPreparedReturn ? query + " RETURN 1" : query; + results.add( trx.run( adjustedQuery, toParameters( dataContext.getParameterValues().get( 0 ), prepared, false ) ) ); } else if ( !dataContext.getParameterValues().isEmpty() ) { + String adjustedQuery = needsPreparedReturn ? query + " RETURN " + dataContext.getParameterValues().size() : query; for ( Map value : dataContext.getParameterValues() ) { - results.add( trx.run( query, toParameters( value, prepared, false ) ) ); + results.add( trx.run( adjustedQuery, toParameters( value, prepared, false ) ) ); } } else { + String adjustedQuery = needsPreparedReturn ? query + " RETURN 1" : query; results.add( trx.run( query ) ); } diff --git a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoEnumerator.java b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoEnumerator.java index cc9035cf74..73f4bdd922 100644 --- a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoEnumerator.java +++ b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoEnumerator.java @@ -17,6 +17,7 @@ package org.polypheny.db.adapter.neo4j; import java.util.List; +import lombok.extern.slf4j.Slf4j; import org.apache.calcite.linq4j.Enumerator; import org.apache.calcite.linq4j.function.Function1; import org.neo4j.driver.Record; @@ -25,9 +26,9 @@ /** - * Neo4j representation of a {@link Enumerator}. - * + * Neo4j's representation of a {@link Enumerator}. */ +@Slf4j public class NeoEnumerator implements Enumerator { private final List results; diff --git a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoMethod.java b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoMethod.java index d71e50536f..619347a4f9 100644 --- a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoMethod.java +++ b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoMethod.java @@ -24,7 +24,7 @@ public enum NeoMethod { - EXECUTE( NeoEntity.NeoQueryable.class, "execute", String.class, NestedPolyType.class, Map.class ), + EXECUTE( NeoEntity.NeoQueryable.class, "execute", String.class, NestedPolyType.class, Map.class, boolean.class ), GRAPH_EXECUTE( NeoGraph.NeoQueryable.class, "execute", String.class, NestedPolyType.class, Map.class ), GRAPH_ALL( NeoGraph.NeoQueryable.class, "executeAll", String.class, String.class ); diff --git a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoRelationalImplementor.java b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoRelationalImplementor.java index f00c6191f8..a414fce480 100644 --- a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoRelationalImplementor.java +++ b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoRelationalImplementor.java @@ -100,6 +100,9 @@ public class NeoRelationalImplementor extends AlgShuttleImpl { @Getter private AlgNode last; + @Getter + private boolean needsPreparedReturn; + public void add( OperatorStatement statement ) { this.statements.add( statement ); @@ -165,7 +168,7 @@ private void addRowCountEntity() { public void addPreparedValues() { if ( last instanceof NeoProject ) { add( createProjectValues( (NeoProject) last, entity, this ) ); - addRowCount( 1 ); + this.needsPreparedReturn = true; return; } throw new GenericRuntimeException( "" ); @@ -278,7 +281,7 @@ public void addReturnIfNecessary() { OperatorStatement statement = statements.get( statements.size() - 1 ); if ( statements.get( statements.size() - 1 ).type != StatementType.RETURN ) { if ( isDml ) { - addRowCountEntity(); + needsPreparedReturn = true; } else if ( statement.type == StatementType.WITH ) { // can replace statements.remove( statements.size() - 1 ); diff --git a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoToEnumerableConverter.java b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoToEnumerableConverter.java index 767867946a..abcc566a19 100644 --- a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoToEnumerableConverter.java +++ b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoToEnumerableConverter.java @@ -78,7 +78,7 @@ public Result implement( EnumerableAlgImplementor implementor, Prefer pref ) { /** - * This methods generates the code snippet, which is used for the graph model logic for the Neo4j adapter. + * This method generates the code snippet, which is used for the graph model logic for the Neo4j adapter. * * @param implementor is used build the code snippets by recursively moving through them * @param pref preferred result format, e.g. when SCALAR -> single result gets returned as single element, if ARRAY it is wrapped in an array @@ -158,7 +158,7 @@ private Result getRelationalImplement( EnumerableAlgImplementor implementor, Pre blockBuilder.newName( "enumerable" ), Expressions.call( entity, - NeoMethod.EXECUTE.method, Expressions.constant( query ), types, parameterClasses ) ); + NeoMethod.EXECUTE.method, Expressions.constant( query ), types, parameterClasses, Expressions.constant( neoImplementor.isNeedsPreparedReturn() ) ) ); blockBuilder.add( Expressions.return_( null, enumerable ) ); diff --git a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoToEnumerableConverterRule.java b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoToEnumerableConverterRule.java index 5caedef77a..7086250bec 100644 --- a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoToEnumerableConverterRule.java +++ b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/NeoToEnumerableConverterRule.java @@ -22,6 +22,7 @@ import org.polypheny.db.algebra.enumerable.EnumerableConvention; import org.polypheny.db.plan.AlgTraitSet; import org.polypheny.db.tools.AlgBuilderFactory; +import org.polypheny.db.util.Util; /** * {@link ConverterRule}, which registers the Neo4j converter operator to push the algebra into the Neo4j adapter. @@ -34,7 +35,7 @@ public class NeoToEnumerableConverterRule extends ConverterRule { public NeoToEnumerableConverterRule( AlgBuilderFactory algBuilderFactory ) { super( AlgNode.class, - r -> true, + Util::containsEntity, NeoConvention.INSTANCE, EnumerableConvention.INSTANCE, algBuilderFactory, diff --git a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/TransactionProvider.java b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/TransactionProvider.java index 2ed19e0f70..f2abcff63c 100644 --- a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/TransactionProvider.java +++ b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/TransactionProvider.java @@ -73,6 +73,8 @@ synchronized void commit( PolyXid xid ) { pair.right.close(); pair.left.close(); + + register.remove( xid ); } } @@ -90,6 +92,9 @@ synchronized void rollback( PolyXid xid ) { pair.right.close(); } pair.left.close(); + pair.right.close(); + + register.remove( xid ); } } @@ -103,6 +108,7 @@ synchronized void rollback( PolyXid xid ) { synchronized public Transaction startSession( PolyXid xid ) { Session session = db.session(); Transaction trx = session.beginTransaction(); + register.put( xid, Pair.of( session, trx ) ); return trx; } diff --git a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/util/NeoUtil.java b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/util/NeoUtil.java index 2ddf2adc85..b200213f13 100644 --- a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/util/NeoUtil.java +++ b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/util/NeoUtil.java @@ -109,12 +109,12 @@ static Function1 getUnnullableTypeFunction( NestedPolyType typ case BIGINT: return v -> PolyBigDecimal.of( v.asLong() ); case DECIMAL: - return v -> PolyBigDecimal.of( v.asDouble() ); + return v -> v instanceof StringValue ? PolyBigDecimal.of( v.asString() ) : PolyBigDecimal.of( v.asDouble() ); case FLOAT: case REAL: - return v -> PolyFloat.of( v.asNumber() ); + return v -> v instanceof StringValue ? PolyFloat.of( Float.valueOf( v.asString() ) ) : PolyFloat.of( v.asNumber() ); case DOUBLE: - return v -> PolyDouble.of( v.asNumber() ); + return v -> v instanceof StringValue ? PolyDouble.of( Double.valueOf( v.asString() ) ) : PolyDouble.of( v.asNumber() ); case INTERVAL: break; case ANY: @@ -301,6 +301,7 @@ static String rexAsString( RexLiteral literal, String mappingLabel, boolean isLi return literal.value.asList().toString(); case BINARY: case VARBINARY: + return literal.value.asBinary().as64String(); case FILE: case IMAGE: case VIDEO: @@ -349,10 +350,6 @@ static Function1, String> getOpAsNeo( OperatorName operatorName, Li return o -> String.format( "%s > %s", o.get( 0 ), o.get( 1 ) ); case GREATER_THAN_OR_EQUAL: return o -> String.format( "%s >= %s", o.get( 0 ), o.get( 1 ) ); - case IN: - return o -> String.format( "%s IN %s", o.get( 0 ), o.get( 1 ) ); - case NOT_IN: - return o -> String.format( "%s NOT IN %s", o.get( 0 ), o.get( 1 ) ); case LESS_THAN: return o -> String.format( "%s < %s", o.get( 0 ), o.get( 1 ) ); case LESS_THAN_OR_EQUAL: @@ -543,6 +540,7 @@ static boolean supports( Project p ) { if ( p.getTraitSet().contains( ModelTrait.GRAPH ) ) { return false; } + if ( p.getTupleType().getFieldNames().stream().anyMatch( n -> n.matches( "^[0-9].*" ) ) ) { return false; } @@ -563,7 +561,9 @@ static Object fixParameterValue( PolyValue value, NestedPolyType type, boolean i } if ( value.isNumber() ) { - return value.asNumber().DoubleValue(); + if ( type.getType() == PolyType.DECIMAL ) { + return value.asNumber().bigDecimalValue().toPlainString(); + } } if ( value.isList() ) { if ( isNested ) { @@ -573,23 +573,22 @@ static Object fixParameterValue( PolyValue value, NestedPolyType type, boolean i } return switch ( type.getType() ) { - case DATE -> value.asTemporal().getDaysSinceEpoch(); - case TIME -> value.asTemporal().getMillisSinceEpoch(); - case TIMESTAMP -> value.asTemporal().getMillisSinceEpoch(); + case DATE, TIME, TIMESTAMP -> value.asTemporal().getMillisSinceEpoch(); case DOCUMENT -> value.asDocument().toTypedJson(); - case INTEGER -> value.asNumber().IntValue(); + case TINYINT, INTEGER, SMALLINT -> value.asNumber().IntValue(); case BIGINT -> value.asNumber().LongValue(); case VARCHAR, TEXT, CHAR -> value.asString().value; case BOOLEAN -> value.asBoolean().value; - case BINARY, VARBINARY, FILE, IMAGE, VIDEO, AUDIO -> value.asBlob().asByteArray(); - case FLOAT, REAL, DOUBLE, DECIMAL -> value.asNumber().doubleValue(); + case BINARY, VARBINARY, FILE, IMAGE, VIDEO, AUDIO -> value.asBinary().value; + case FLOAT, REAL, DOUBLE -> value.asNumber().doubleValue(); + case DECIMAL -> value.asNumber().bigDecimalValue(); case ARRAY -> value.asList().value.stream().map( e -> { if ( isNested ) { return e.toTypedJson(); } return fixParameterValue( e, type.asList().types.get( 0 ), true ); } ).toList(); - default -> throw new NotImplementedException(); + default -> throw new NotImplementedException( "Poly to Neo4j value" ); }; } diff --git a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/util/Translator.java b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/util/Translator.java index 52195e480a..3cf67a8924 100644 --- a/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/util/Translator.java +++ b/plugins/neo4j-adapter/src/main/java/org/polypheny/db/adapter/neo4j/util/Translator.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.Nullable; import org.apache.calcite.linq4j.function.Function1; @@ -37,7 +38,6 @@ import org.polypheny.db.rex.RexIndexRef; import org.polypheny.db.rex.RexLiteral; import org.polypheny.db.rex.RexLocalRef; -import org.polypheny.db.rex.RexNode; import org.polypheny.db.rex.RexVisitorImpl; import org.polypheny.db.type.PathType; import org.polypheny.db.type.PolyType; @@ -170,10 +170,11 @@ public String visitCall( RexCall call ) { private String getFinalFunction( RexCall call, List ops ) { Function1, String> getter = NeoUtil.getOpAsNeo( call.op.getOperatorName(), call.operands, call.type ); assert getter != null : "Function is not supported by the Neo4j adapter."; + String adjusted = getter.apply( IntStream.range( 0, ops.size() ).mapToObj( i -> call.operands.get( i ).getType().getPolyType() == PolyType.DECIMAL ? "toFloat(" + ops.get( i ) + ")" : ops.get( i ) ).toList() ); if ( useBrackets ) { - return "(" + getter.apply( ops ) + ")"; + return "(" + adjusted + ")"; } - return " " + getter.apply( ops ) + " "; + return " " + adjusted + " "; } @@ -187,12 +188,9 @@ public String visitFieldAccess( RexFieldAccess fieldAccess ) { private String handleBinaries( RexCall call ) { - RexNode leftRex = call.operands.get( 0 ); - RexNode rightRex = call.operands.get( 1 ); - String left = leftRex.accept( this ); - String right = rightRex.accept( this ); + List nodes = call.operands.stream().map( o -> o.accept( this ) ).toList(); - return getFinalFunction( call, List.of( left, right ) ); + return getFinalFunction( call, nodes ); } diff --git a/plugins/notebooks/src/main/java/org/polypheny/db/notebooks/NotebooksPlugin.java b/plugins/notebooks/src/main/java/org/polypheny/db/notebooks/NotebooksPlugin.java index 5f5a5d605d..189973864e 100644 --- a/plugins/notebooks/src/main/java/org/polypheny/db/notebooks/NotebooksPlugin.java +++ b/plugins/notebooks/src/main/java/org/polypheny/db/notebooks/NotebooksPlugin.java @@ -40,7 +40,6 @@ import org.polypheny.db.plugins.PolyPlugin; import org.polypheny.db.transaction.TransactionManager; import org.polypheny.db.webui.ConfigService.HandlerType; -import org.polypheny.db.webui.Crud; import org.polypheny.db.webui.HttpServer; @Slf4j diff --git a/plugins/pig-language/src/main/java/org/polypheny/db/PigLanguagePlugin.java b/plugins/pig-language/src/main/java/org/polypheny/db/PigLanguagePlugin.java index d02996a4e4..d2e4868433 100644 --- a/plugins/pig-language/src/main/java/org/polypheny/db/PigLanguagePlugin.java +++ b/plugins/pig-language/src/main/java/org/polypheny/db/PigLanguagePlugin.java @@ -52,7 +52,7 @@ public void start() { null, PigProcessor::new, null, - LanguageManager::toQueryNodes, + LanguageManager::toUnsplitQueryNodes, c -> c ); LanguageManager.getINSTANCE().addQueryLanguage( language ); PolyPluginManager.AFTER_INIT.add( () -> LanguageCrud.addToResult( language, LanguageCrud::getRelResult ) ); diff --git a/plugins/postgres-adapter/src/main/java/org/polypheny/db/adapter/postgres/PostgresqlSqlDialect.java b/plugins/postgres-adapter/src/main/java/org/polypheny/db/adapter/postgres/PostgresqlSqlDialect.java index cdee394ccb..000472557c 100644 --- a/plugins/postgres-adapter/src/main/java/org/polypheny/db/adapter/postgres/PostgresqlSqlDialect.java +++ b/plugins/postgres-adapter/src/main/java/org/polypheny/db/adapter/postgres/PostgresqlSqlDialect.java @@ -18,6 +18,7 @@ import java.util.Objects; +import java.util.Optional; import org.apache.calcite.avatica.SqlType; import org.polypheny.db.algebra.constant.FunctionCategory; import org.polypheny.db.algebra.constant.Kind; @@ -97,6 +98,15 @@ public boolean supportsArrays() { } + @Override + public Optional handleMissingLength( PolyType type ) { + return switch ( type ) { + case VARBINARY, VARCHAR, BINARY -> Optional.of( "VARYING" ); + default -> Optional.empty(); + }; + } + + @Override public SqlNode getCastSpec( AlgDataType type ) { String castSpec; diff --git a/plugins/prism-interface/build.gradle b/plugins/prism-interface/build.gradle new file mode 100644 index 0000000000..9e767bdc39 --- /dev/null +++ b/plugins/prism-interface/build.gradle @@ -0,0 +1,141 @@ +plugins { + id 'java' + id "com.google.protobuf" version "$protobuf_plugin_version" +} + + +apply plugin: "com.google.protobuf" + +group "org.polypheny" + +configurations { + tests { + extendsFrom testRuntimeOnly + } +} + +repositories { + mavenCentral() +} + +dependencies { + compileOnly project(path: ':core') + compileOnly project(path: ':dbms') + compileOnly project(path: ':plugins:sql-language') + compileOnly project(path: ':plugins:mql-language') + + // Prism API files (protobuf files), needs to be implementation due to the prism-api-version.properties + implementation group: 'org.polypheny', name: 'prism', version: prism_api_version + + // Protobuf + implementation group: 'com.google.protobuf', name: 'protobuf-java', version: protobuf_version + + + // --- Test Compile --- + testImplementation project(path: ':dbms', configuration: 'test') + testImplementation project(path: ':core', configuration: 'tests') + testImplementation project(path: ':core') + testRuntimeOnly project(path: ':dbms') + //testImplementation project(path: ':plugins:mql-language') + +} + +// compile protos on build +compileJava.dependsOn(generateProto) +compileTestJava.mustRunAfter(javadoc) +compileTestJava.mustRunAfter(jar) +compileTestJava.mustRunAfter(plugin) +processResources.dependsOn(extractIncludeProto) + +test.dependsOn(":dbms:shadowJar") + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${protobuf_version}" + } + + generateProtoTasks { + all().each { task -> + task.builtins { + java {} + } + } + } +} + +sourceSets { + main { + java { + srcDirs = ["src/main/java", "build/generated/source/proto/main/java"] + } + proto { + srcDirs = ["build/extracted-include-protos/main"] + } + resources { + srcDirs = ["src/main/resources"] + } + output.resourcesDir = file(project.buildDir.absolutePath + "/classes") + + } + test { + java { + srcDirs = ["src/test/java"] + } + resources { + srcDirs = ["src/test/resources"] + } + output.resourcesDir = file(project.buildDir.absolutePath + "/test-classes") + } +} + +delombok { + dependsOn(':plugins:prism-interface:generateProto') + dependsOn(':plugins:sql-language:processResources') + dependsOn(':plugins:mql-language:processResources') +} + +compileJava { + dependsOn(":core:processResources") +} + +extractIncludeProto { + dependsOn(':core:generateVersionProperties') + dependsOn(':core:compileJava') + dependsOn(':dbms:compileJava') + dependsOn(':plugins:sql-language:compileJava') + dependsOn(':plugins:mql-language:compileJava') +} + + +/** + * Tests + */ +test { + maxHeapSize = "2g" // Increase heap size (default is 512MB) +} + +/** + * JARs + */ +jar { + manifest { + attributes "Manifest-Version": "1.0" + attributes "Copyright": "The Polypheny Project (polypheny.org)" + attributes "Version": "$project.version" + } +} + +java { + withJavadocJar() + withSourcesJar() + sourcesJar.dependsOn(generateProto) +} + + +/** + * LICENSEE + */ +licensee { + allow('Apache-2.0') + allow('BSD-3-Clause') +} diff --git a/plugins/prism-interface/gradle.properties b/plugins/prism-interface/gradle.properties new file mode 100644 index 0000000000..aca5be4453 --- /dev/null +++ b/plugins/prism-interface/gradle.properties @@ -0,0 +1,27 @@ +# +# Copyright 2019-2023 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. +# + +pluginVersion = 0.0.1 + +pluginId = prism-interface +pluginClass = org.polypheny.db.prisminterface.PIPlugin +pluginProvider = The Polypheny Project +pluginDependencies = mql-language, sql-language +pluginUrlPath = +pluginCategories = interface +pluginPolyDependencies = +pluginIsSystemComponent = false +pluginIsUiVisible = true diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/ClientManager.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/ClientManager.java new file mode 100644 index 0000000000..93b3956a6e --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/ClientManager.java @@ -0,0 +1,153 @@ +/* + * 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 java.security.SecureRandom; +import java.util.Base64; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; +import lombok.extern.slf4j.Slf4j; +import org.polypheny.db.catalog.Catalog; +import org.polypheny.db.catalog.entity.LogicalUser; +import org.polypheny.db.catalog.entity.logical.LogicalNamespace; +import org.polypheny.db.iface.AuthenticationException; +import org.polypheny.db.iface.Authenticator; +import org.polypheny.db.prisminterface.PIPlugin.PrismInterface; +import org.polypheny.db.prisminterface.transport.Transport; +import org.polypheny.db.prisminterface.utils.PropertyUtils; +import org.polypheny.db.transaction.Transaction; +import org.polypheny.db.transaction.TransactionException; +import org.polypheny.db.transaction.TransactionManager; +import org.polypheny.prism.ConnectionRequest; + +@Slf4j +class ClientManager { + + private final ConcurrentHashMap clients; + private final Authenticator authenticator; + private final TransactionManager transactionManager; + private final MonitoringPage monitoringPage; + + + ClientManager( PrismInterface prismInterface ) { + this.clients = new ConcurrentHashMap<>(); + this.authenticator = prismInterface.getAuthenticator(); + this.transactionManager = prismInterface.getTransactionManager(); + this.monitoringPage = prismInterface.getMonitoringPage(); + monitoringPage.setClientManager( this ); + } + + + public void unregisterConnection( PIClient client ) { + client.prepareForDisposal(); + clients.remove( client.getClientUUID() ); + } + + + private LogicalUser getUser( ConnectionRequest connectionRequest, Transport t ) throws AuthenticationException { + if ( connectionRequest.hasUsername() ) { + String username = connectionRequest.getUsername(); + if ( !connectionRequest.hasPassword() ) { + throw new AuthenticationException( "A password is required" ); + } + String password = connectionRequest.getPassword(); + return authenticator.authenticate( username, password ); + } else if ( t.getPeer().isPresent() ) { + String username = t.getPeer().get(); + Optional catalogUser = Catalog.getInstance().getSnapshot().getUser( username ); + if ( catalogUser.isPresent() ) { + return catalogUser.get(); + } else { + if ( username.equals( System.getProperty( "user.name" ) ) ) { + return Catalog.getInstance().getSnapshot().getUser( Catalog.USER_NAME ).orElseThrow(); + } else { + throw new AuthenticationException( "Peer authentication failed: No user with that name" ); + } + } + } + throw new AuthenticationException( "Authentication failed" ); + } + + + String registerConnection( ConnectionRequest connectionRequest, Transport t ) throws AuthenticationException, TransactionException, PIServiceException { + byte[] raw = new byte[32]; + new SecureRandom().nextBytes( raw ); + String uuid = Base64.getUrlEncoder().encodeToString( raw ); + + if ( log.isTraceEnabled() ) { + log.trace( "User {} tries to establish connection via prism interface.", uuid ); + } + final LogicalUser user = getUser( connectionRequest, t ); + Transaction transaction = transactionManager.startTransaction( user.id, false, "prism-interface" ); + transaction.commit(); + LogicalNamespace namespace = getNamespaceOrDefault( connectionRequest ); + boolean isAutocommit = getAutocommitOrDefault( connectionRequest ); + PIClient client = new PIClient( + uuid, + user, + transactionManager, + namespace, + monitoringPage, + isAutocommit + ); + clients.put( uuid, client ); + if ( log.isTraceEnabled() ) { + log.trace( "prism-interface established connection to user {}.", uuid ); + } + return uuid; + } + + + Stream> getClients() { + return clients.entrySet().stream(); + } + + + int getClientCount() { + return clients.size(); + } + + + private LogicalNamespace getNamespaceOrDefault( ConnectionRequest connectionRequest ) { + String namespaceName = PropertyUtils.DEFAULT_NAMESPACE_NAME; + if ( connectionRequest.hasConnectionProperties() && connectionRequest.getConnectionProperties().hasNamespaceName() ) { + namespaceName = connectionRequest.getConnectionProperties().getNamespaceName(); + } + Optional optionalNamespace = Catalog.getInstance().getSnapshot().getNamespace( namespaceName ); + if ( optionalNamespace.isEmpty() ) { + throw new PIServiceException( "Getting namespace " + namespaceName + " failed." ); + } + + return optionalNamespace.get(); + } + + + private boolean getAutocommitOrDefault( ConnectionRequest connectionRequest ) { + if ( connectionRequest.hasConnectionProperties() && connectionRequest.getConnectionProperties().hasIsAutoCommit() ) { + return connectionRequest.getConnectionProperties().getIsAutoCommit(); + } + return PropertyUtils.AUTOCOMMIT_DEFAULT; + } + + + PIClient getClient( String clientUUID ) throws PIServiceException { + return clients.get( clientUUID ); + } + +} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/DbMetaRetriever.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/DbMetaRetriever.java similarity index 79% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/DbMetaRetriever.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/DbMetaRetriever.java index 261ace7a6a..23ce1361e2 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/DbMetaRetriever.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/DbMetaRetriever.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.polypheny.db.protointerface; +package org.polypheny.db.prisminterface; import java.sql.DatabaseMetaData; import java.util.Arrays; @@ -39,33 +39,32 @@ import org.polypheny.db.catalog.logistic.Pattern; import org.polypheny.db.languages.OperatorRegistry; import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.protointerface.proto.ClientInfoPropertyMeta; -import org.polypheny.db.protointerface.proto.ClientInfoPropertyMetaResponse; -import org.polypheny.db.protointerface.proto.Column; -import org.polypheny.db.protointerface.proto.Database; -import org.polypheny.db.protointerface.proto.DatabasesResponse; -import org.polypheny.db.protointerface.proto.DbmsVersionResponse; -import org.polypheny.db.protointerface.proto.EntitiesResponse; -import org.polypheny.db.protointerface.proto.Entity; -import org.polypheny.db.protointerface.proto.ForeignKey; -import org.polypheny.db.protointerface.proto.Function; -import org.polypheny.db.protointerface.proto.FunctionsResponse; -import org.polypheny.db.protointerface.proto.Index; -import org.polypheny.db.protointerface.proto.Namespace; -import org.polypheny.db.protointerface.proto.NamespacesResponse; -import org.polypheny.db.protointerface.proto.PrimaryKey; -import org.polypheny.db.protointerface.proto.ProceduresResponse; -import org.polypheny.db.protointerface.proto.Table; -import org.polypheny.db.protointerface.proto.TableType; -import org.polypheny.db.protointerface.proto.TableTypesResponse; -import org.polypheny.db.protointerface.proto.Type; -import org.polypheny.db.protointerface.proto.TypesResponse; import org.polypheny.db.type.PolyType; - -public class DbMetaRetriever { +import org.polypheny.prism.ClientInfoPropertyMeta; +import org.polypheny.prism.ClientInfoPropertyMetaResponse; +import org.polypheny.prism.Column; +import org.polypheny.prism.DbmsVersionResponse; +import org.polypheny.prism.DefaultNamespaceResponse; +import org.polypheny.prism.EntitiesResponse; +import org.polypheny.prism.Entity; +import org.polypheny.prism.ForeignKey; +import org.polypheny.prism.Function; +import org.polypheny.prism.FunctionsResponse; +import org.polypheny.prism.Index; +import org.polypheny.prism.Namespace; +import org.polypheny.prism.NamespacesResponse; +import org.polypheny.prism.PrimaryKey; +import org.polypheny.prism.ProceduresResponse; +import org.polypheny.prism.Table; +import org.polypheny.prism.TableType; +import org.polypheny.prism.TableTypesResponse; +import org.polypheny.prism.Type; +import org.polypheny.prism.TypesResponse; + +class DbMetaRetriever { // Namespace search by name and type - public static NamespacesResponse searchNamespaces( String namespacePattern, String namespaceType ) { + static NamespacesResponse searchNamespaces( String namespacePattern, String namespaceType ) { List namespaces = getLogicalNamespaces( namespacePattern, namespaceType ); NamespacesResponse.Builder responseBuilder = NamespacesResponse.newBuilder(); namespaces.forEach( namespace -> responseBuilder.addNamespaces( getNamespaceMeta( namespace ) ) ); @@ -87,8 +86,6 @@ private static List getLogicalNamespaces( String namespacePatt private static Namespace getNamespaceMeta( LogicalNamespace logicalNamespace ) { Namespace.Builder namespaceBuilder = Namespace.newBuilder(); namespaceBuilder.setNamespaceName( logicalNamespace.getName() ); - namespaceBuilder.setDatabaseName( logicalNamespace.getDatabaseName() ); - namespaceBuilder.setOwnerName( logicalNamespace.getOwnerName() ); Optional.ofNullable( logicalNamespace.getDataModel() ).ifPresent( p -> namespaceBuilder.setNamespaceType( p.name() ) ); return namespaceBuilder.build(); } @@ -105,7 +102,7 @@ public static Namespace getNamespace( String namespaceName ) { // Entity search by namespace - public static EntitiesResponse searchEntities( String namespaceName, String entityPattern ) { + static EntitiesResponse searchEntities( String namespaceName, String entityPattern ) { EntitiesResponse.Builder responseBuilder = EntitiesResponse.newBuilder(); LogicalNamespace namespace = Catalog.getInstance().getSnapshot().getNamespace( namespaceName ).orElseThrow(); switch ( namespace.getDataModel() ) { @@ -140,18 +137,16 @@ private static Entity buildEntityFromTable( Table table ) { private static Table getTableMeta( LogicalTable logicalTable ) { Table.Builder tableBuilder = Table.newBuilder(); - tableBuilder.setSourceDatabaseName( logicalTable.getDatabaseName() ); tableBuilder.setNamespaceName( logicalTable.getNamespaceName() ); tableBuilder.setTableName( logicalTable.getName() ); tableBuilder.setTableType( logicalTable.getEntityType().name() ); - tableBuilder.setOwnerName( logicalTable.getOwnerName() ); tableBuilder.addAllColumns( getColumns( logicalTable ) ); if ( hasPrimaryKey( logicalTable ) ) { tableBuilder.setPrimaryKey( getPrimaryKeyMeta( logicalTable ) ); } tableBuilder.addAllForeignKeys( getForeignKeys( logicalTable ) ); tableBuilder.addAllExportedKeys( getExportedKeys( logicalTable ) ); - tableBuilder.addAllIndexes( getIndexes( logicalTable, true ) ); + tableBuilder.addAllIndexes( getIndexes( logicalTable, false ) ); return tableBuilder.build(); } @@ -181,13 +176,12 @@ private static PrimaryKey getPrimaryKeyMeta( LogicalTable logicalTable ) { private static List getColumns( LogicalKey logicalKey ) { - return logicalKey.getColumns().stream().map( DbMetaRetriever::getColumnMeta ).collect( Collectors.toList() ); + return logicalKey.fieldIds.stream().map( id -> Catalog.snapshot().rel().getColumn( id ).orElseThrow() ).map( DbMetaRetriever::getColumnMeta ).toList(); } private static Column getColumnMeta( LogicalColumn logicalColumn ) { Column.Builder columnBuilder = Column.newBuilder(); - columnBuilder.setDatabaseName( logicalColumn.getDatabaseName() ); columnBuilder.setNamespaceName( logicalColumn.getNamespaceName() ); columnBuilder.setTableName( logicalColumn.getTableName() ); columnBuilder.setColumnName( logicalColumn.getName() ); @@ -212,9 +206,8 @@ private static List getForeignKeys( LogicalTable logicalTable ) { private static ForeignKey getForeignKeyMeta( LogicalForeignKey logicalForeignKey ) { ForeignKey.Builder foreignKeyBuilder = ForeignKey.newBuilder(); - foreignKeyBuilder.setReferencedNamespaceName( logicalForeignKey.getReferencedKeySchemaName() ); - foreignKeyBuilder.setReferencedDatabaseName( logicalForeignKey.getReferencedKeySchemaName() ); - foreignKeyBuilder.setReferencedTableName( logicalForeignKey.getReferencedKeyTableName() ); + foreignKeyBuilder.setReferencedNamespaceName( logicalForeignKey.getReferencedKeyNamespaceName() ); + foreignKeyBuilder.setReferencedTableName( logicalForeignKey.getReferencedKeyEntityName() ); foreignKeyBuilder.setUpdateRule( logicalForeignKey.getUpdateRule().getId() ); foreignKeyBuilder.setDeleteRule( logicalForeignKey.getDeleteRule().getId() ); Optional.ofNullable( logicalForeignKey.getName() ).ifPresent( foreignKeyBuilder::setKeyName ); @@ -225,7 +218,7 @@ private static ForeignKey getForeignKeyMeta( LogicalForeignKey logicalForeignKey private static List getReferencedColumns( LogicalForeignKey logicalForeignKey ) { - return logicalForeignKey.getReferencedKeyColumnIds().stream().map( id -> Catalog.snapshot().rel().getColumn( id ).orElseThrow() ).map( DbMetaRetriever::getColumnMeta ).collect( Collectors.toList() ); + return logicalForeignKey.referencedKeyFieldIds.stream().map( id -> Catalog.snapshot().rel().getColumn( id ).orElseThrow() ).map( DbMetaRetriever::getColumnMeta ).collect( Collectors.toList() ); } @@ -243,7 +236,6 @@ private static List getIndexes( LogicalTable logicalTable, boolean unique private static Index getIndexMeta( LogicalIndex logicalIndex ) { Index.Builder importedKeyBuilder = Index.newBuilder(); - importedKeyBuilder.setDatabaseName( logicalIndex.getDatabaseName() ); importedKeyBuilder.setNamespaceName( logicalIndex.getKey().getSchemaName() ); importedKeyBuilder.setTableName( logicalIndex.getKey().getTableName() ); importedKeyBuilder.setUnique( logicalIndex.isUnique() ); @@ -255,34 +247,24 @@ private static Index getIndexMeta( LogicalIndex logicalIndex ) { } - public static List getDocumentEntities( long namespaceId, String entityPattern ) { + static List getDocumentEntities( long namespaceId, String entityPattern ) { return null; } - public static List getGraphEntities( long namespaceId, String entityPattern ) { + static List getGraphEntities( long namespaceId, String entityPattern ) { return null; } - private static List getLogicalIndexesOf( long entityId, boolean unique ) { - return Catalog.getInstance().getSnapshot().rel().getIndexes( entityId, unique ); - } - - - public static synchronized DatabasesResponse getDatabases() { - Database database = Database.newBuilder() - .setDatabaseName( Catalog.DATABASE_NAME ) - .setOwnerName( "system" ) - .setDefaultNamespaceName( Catalog.DEFAULT_NAMESPACE_NAME ) - .build(); - return DatabasesResponse.newBuilder() - .addDatabases( database ) + static synchronized DefaultNamespaceResponse getDefaultNamespace() { + return DefaultNamespaceResponse.newBuilder() + .setDefaultNamespace( Catalog.DEFAULT_NAMESPACE_NAME ) .build(); } - public static synchronized TableTypesResponse getTableTypes() { + static synchronized TableTypesResponse getTableTypes() { List tableTypes = Arrays.stream( EntityType.values() ).map( EntityType::name ).toList(); TableTypesResponse.Builder responseBuilder = TableTypesResponse.newBuilder(); tableTypes.forEach( tableType -> responseBuilder.addTableTypes( getTableTypeMeta( tableType ) ) ); @@ -295,7 +277,7 @@ private static TableType getTableTypeMeta( String tableType ) { } - public static synchronized TypesResponse getTypes() { + static synchronized TypesResponse getTypes() { TypesResponse.Builder responseBuilder = TypesResponse.newBuilder(); Arrays.stream( PolyType.values() ).forEach( polyType -> responseBuilder.addTypes( getTypeMeta( polyType ) ) ); return responseBuilder.build(); @@ -319,19 +301,13 @@ private static Type getTypeMeta( PolyType polyType ) { } - public static String getSqlKeywords() { - // TODO: get data after functionality is implemented - return ""; - } - - - public static ProceduresResponse getProcedures( String languageName, String procedureNamePattern ) { + static ProceduresResponse getProcedures( String languageName, String procedureNamePattern ) { // TODO: get data after functionality is implemented return ProceduresResponse.newBuilder().build(); } - public static ClientInfoPropertyMetaResponse getClientInfoProperties() { + static ClientInfoPropertyMetaResponse getClientInfoProperties() { List propertyInfoMetas = PIClientInfoProperties.DEFAULTS.stream() .map( DbMetaRetriever::getClientInfoPropertyMeta ).collect( Collectors.toList() ); return ClientInfoPropertyMetaResponse.newBuilder() @@ -342,15 +318,15 @@ public static ClientInfoPropertyMetaResponse getClientInfoProperties() { private static ClientInfoPropertyMeta getClientInfoPropertyMeta( PIClientInfoProperties.ClientInfoPropertiesDefault clientInfoPropertiesDefault ) { return ClientInfoPropertyMeta.newBuilder() - .setKey( clientInfoPropertiesDefault.key ) - .setDefaultValue( clientInfoPropertiesDefault.default_value ) - .setMaxlength( clientInfoPropertiesDefault.maxlength ) - .setDescription( clientInfoPropertiesDefault.description ) + .setKey( clientInfoPropertiesDefault.key() ) + .setDefaultValue( clientInfoPropertiesDefault.default_value() ) + .setMaxlength( clientInfoPropertiesDefault.maxLength() ) + .setDescription( clientInfoPropertiesDefault.description() ) .build(); } - public static FunctionsResponse getFunctions( QueryLanguage language, FunctionCategory functionCategory ) { + static FunctionsResponse getFunctions( QueryLanguage language, FunctionCategory functionCategory ) { List functions = OperatorRegistry.getMatchingOperators( language ).values().stream() .filter( o -> o instanceof org.polypheny.db.nodes.Function ) .map( org.polypheny.db.nodes.Function.class::cast ) @@ -371,7 +347,7 @@ private static Function getFunctionMeta( org.polypheny.db.nodes.Function functio } - public static DbmsVersionResponse getDbmsVersion() { + static DbmsVersionResponse getDbmsVersion() { try { String versionName = PolyphenyDb.class.getPackage().getImplementationVersion(); if ( versionName == null ) { @@ -406,4 +382,4 @@ public static DbmsVersionResponse getDbmsVersion() { } } -} \ No newline at end of file +} diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/MonitoringPage.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/MonitoringPage.java new file mode 100644 index 0000000000..2dcba107bb --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/MonitoringPage.java @@ -0,0 +1,125 @@ +/* + * 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 java.util.Arrays; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.Setter; +import org.polypheny.db.information.InformationGroup; +import org.polypheny.db.information.InformationManager; +import org.polypheny.db.information.InformationPage; +import org.polypheny.db.information.InformationTable; +import org.polypheny.db.prisminterface.statements.StatementManager; + +class MonitoringPage { + + @Setter + private ClientManager clientManager; + + private final Set statementManagers = new HashSet<>(); + private final InformationPage informationPage; + private final InformationGroup interfaceGroup; + private final InformationTable statementsTable; + private final InformationTable connectionsTable; + + private final InformationGroup connectionsGroup; + private final InformationTable connectionListTable; + + + MonitoringPage( String uniqueName, String description ) { + InformationManager informationManager = InformationManager.getInstance(); + + informationPage = new InformationPage( uniqueName, description ) + .fullWidth() + .setLabel( "Interfaces" ); + interfaceGroup = new InformationGroup( informationPage, "Interface Statistics" ); + + informationManager.addPage( informationPage ); + informationManager.addGroup( interfaceGroup ); + + statementsTable = new InformationTable( interfaceGroup, Arrays.asList( "Attribute", "Value" ) ); + statementsTable.setOrder( 1 ); + informationManager.registerInformation( statementsTable ); + + connectionsTable = new InformationTable( interfaceGroup, Arrays.asList( "Attribute", "Value" ) ); + connectionsTable.setOrder( 2 ); + informationManager.registerInformation( connectionsTable ); + + connectionsGroup = new InformationGroup( informationPage, "Connections" ); + informationManager.addGroup( connectionsGroup ); + connectionListTable = new InformationTable( connectionsGroup, + Arrays.asList( "UUID", "TX", "Auto Commit", "Default Namespace" ) ); + connectionListTable.setOrder( 3 ); + informationManager.registerInformation( connectionListTable ); + + informationPage.setRefreshFunction( this::update ); + } + + + private void update() { + connectionsTable.reset(); + connectionsTable.addRow( "Open Connections", clientManager.getClientCount() ); + + statementsTable.reset(); + AtomicInteger statementCount = new AtomicInteger(); + statementManagers.forEach( m -> statementCount.addAndGet( m.openStatementCount() ) ); + statementsTable.addRow( "Open Statements", statementCount.get() ); + + connectionListTable.reset(); + clientManager.getClients().forEach( this::addClientEntry ); + } + + + private void addClientEntry( Entry clientEntry ) { + String uuid = clientEntry.getKey(); + PIClient client = clientEntry.getValue(); + String txId = "-"; + if ( !client.hasNoTransaction() ) { + txId = String.valueOf( client.getOrCreateNewTransaction().getId() ); + } + connectionListTable.addRow( + uuid, + txId, + client.isAutoCommit(), + client.getNamespace() + ); + } + + + void addStatementManager( StatementManager statementManager ) { + statementManagers.add( statementManager ); + } + + + void removeStatementManager( StatementManager statementManager ) { + statementManagers.remove( statementManager ); + } + + + public void remove() { + InformationManager im = InformationManager.getInstance(); + im.removeInformation( connectionsTable ); + im.removeInformation( statementsTable ); + im.removeInformation( connectionListTable ); + im.removeGroup( interfaceGroup ); + im.removePage( informationPage ); + } + +} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/NamedValueProcessor.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/NamedValueProcessor.java similarity index 89% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/NamedValueProcessor.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/NamedValueProcessor.java index e9616fb851..8efe9f5c15 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/NamedValueProcessor.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/NamedValueProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.polypheny.db.protointerface; +package org.polypheny.db.prisminterface; import java.util.ArrayList; import java.util.List; @@ -22,6 +22,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.Getter; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; import org.polypheny.db.type.entity.PolyValue; public class NamedValueProcessor { @@ -60,7 +61,7 @@ public List transformValueMap( Map values ) { for ( String placeholder : replacements ) { PolyValue value = values.get( placeholder ); if ( value == null ) { - throw new RuntimeException( "Missing named parameter: " + placeholder ); + throw new GenericRuntimeException( "Missing named parameter: " + placeholder ); } image.add( value ); } diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIClient.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIClient.java similarity index 59% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIClient.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIClient.java index 0c02bd61bb..a4953ceb47 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIClient.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,14 +14,14 @@ * limitations under the License. */ -package org.polypheny.db.protointerface; +package org.polypheny.db.prisminterface; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.polypheny.db.catalog.entity.LogicalUser; import org.polypheny.db.catalog.entity.logical.LogicalNamespace; -import org.polypheny.db.protointerface.statements.StatementManager; +import org.polypheny.db.prisminterface.statements.StatementManager; import org.polypheny.db.transaction.Transaction; import org.polypheny.db.transaction.TransactionException; import org.polypheny.db.transaction.TransactionManager; @@ -30,28 +30,30 @@ public class PIClient { @Getter - private String clientUUID; - private LogicalUser catalogUser; + private final String clientUUID; + private final LogicalUser catalogUser; private Transaction currentTransaction; - private TransactionManager transactionManager; + private final TransactionManager transactionManager; @Getter - private StatementManager statementManager; + private final StatementManager statementManager; @Getter - private PIClientInfoProperties PIClientInfoProperties; + private final PIClientInfoProperties PIClientInfoProperties; @Getter @Setter private boolean isAutoCommit; @Getter @Setter private LogicalNamespace namespace; - private boolean isActive; + @Getter + private final MonitoringPage monitoringPage; - public PIClient( + PIClient( String clientUUID, LogicalUser catalogUser, TransactionManager transactionManager, LogicalNamespace namespace, + MonitoringPage monitoringPage, boolean isAutoCommit ) { this.statementManager = new StatementManager( this ); this.PIClientInfoProperties = new PIClientInfoProperties(); @@ -60,18 +62,16 @@ public PIClient( this.catalogUser = catalogUser; this.transactionManager = transactionManager; this.isAutoCommit = isAutoCommit; - this.isActive = true; + this.monitoringPage = monitoringPage; + monitoringPage.addStatementManager( statementManager ); } - public Transaction getCurrentOrCreateNewTransaction() { - synchronized ( this ) { - if ( currentTransaction == null || !currentTransaction.isActive() ) { - //TODO TH: can a single transaction contain changes to different namespaces -> use null - currentTransaction = transactionManager.startTransaction( catalogUser.id, namespace.id, false, "ProtoInterface" ); - } - return currentTransaction; + public Transaction getOrCreateNewTransaction() { + if ( hasNoTransaction() ) { + currentTransaction = transactionManager.startTransaction( catalogUser.id, namespace.id, false, "PrismInterface" ); } + return currentTransaction; } @@ -84,9 +84,7 @@ public void commitCurrentTransactionIfAuto() { public void commitCurrentTransaction() throws PIServiceException { - synchronized ( this ) { - commitCurrentTransactionUnsynchronized(); - } + commitCurrentTransactionUnsynchronized(); } @@ -99,28 +97,27 @@ private void commitCurrentTransactionUnsynchronized() throws PIServiceException } catch ( TransactionException e ) { throw new PIServiceException( "Committing current transaction failed: " + e.getMessage() ); } finally { - endCurrentTransaction(); + clearCurrentTransaction(); } } public void rollbackCurrentTransaction() throws PIServiceException { - synchronized ( this ) { - if ( hasNoTransaction() ) { - return; - } - try { - currentTransaction.rollback(); - } catch ( TransactionException e ) { - throw new PIServiceException( "Rollback of current transaction failed: " + e.getLocalizedMessage() ); - } finally { - endCurrentTransaction(); - } + if ( hasNoTransaction() ) { + return; + } + try { + currentTransaction.getCancelFlag().set( true ); + currentTransaction.rollback(); + } catch ( TransactionException e ) { + throw new PIServiceException( "Rollback of current transaction failed: " + e.getLocalizedMessage() ); + } finally { + clearCurrentTransaction(); } } - private void endCurrentTransaction() { + private void clearCurrentTransaction() { currentTransaction = null; } @@ -130,23 +127,10 @@ public boolean hasNoTransaction() { } - public void prepareForDisposal() { + void prepareForDisposal() { statementManager.closeAll(); - if ( !hasNoTransaction() ) { - rollbackCurrentTransaction(); - } - } - - - public void setIsActive() { - isActive = true; - } - - - public boolean returnAndResetIsActive() { - boolean oldIsActive = isActive; - isActive = false; - return oldIsActive; + rollbackCurrentTransaction(); + monitoringPage.removeStatementManager( statementManager ); } } diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIClientInfoProperties.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIClientInfoProperties.java similarity index 71% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIClientInfoProperties.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIClientInfoProperties.java index 7bb2941ecd..caccd5b21a 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIClientInfoProperties.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIClientInfoProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,57 +14,50 @@ * limitations under the License. */ -package org.polypheny.db.protointerface; +package org.polypheny.db.prisminterface; import java.util.Arrays; import java.util.List; import java.util.Properties; -import lombok.AllArgsConstructor; -public class PIClientInfoProperties extends Properties { +class PIClientInfoProperties extends Properties { private static final int MAX_STRING_LENGTH = 2147483647; - public static final List DEFAULTS = Arrays.asList( + static final List DEFAULTS = Arrays.asList( new ClientInfoPropertiesDefault( "ApplicationName", "", MAX_STRING_LENGTH, - "Name of the application which interacts with the proto-interface by this client." + "Name of the application which interacts with the prism-interface by this client." ), new ClientInfoPropertiesDefault( "ApplicationVersionString", "", MAX_STRING_LENGTH, - "Version description of the application that interacts with the proto-interface through this user." + "Version description of the application that interacts with the prism-interface through this user." ), new ClientInfoPropertiesDefault( "ClientHostname", "", MAX_STRING_LENGTH, - "Hostname of the computer on which the application is running that interacts with the proto-interface via this user." + "Hostname of the computer on which the application is running that interacts with the prism-interface via this user." ), new ClientInfoPropertiesDefault( "ClientUser", "", MAX_STRING_LENGTH, - "User name of the user under which the application interacting with the proto-interface via this user is running." + "User name of the user under which the application interacting with the prism-interface via this user is running." ) ); - public PIClientInfoProperties() { + PIClientInfoProperties() { super(); DEFAULTS.forEach( p -> setProperty( p.key, p.default_value ) ); } - @AllArgsConstructor - public static class ClientInfoPropertiesDefault { - - String key; - String default_value; - int maxlength; - String description; + record ClientInfoPropertiesDefault( String key, String default_value, int maxLength, String description ) { } diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIPlugin.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIPlugin.java new file mode 100644 index 0000000000..2e450949ad --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIPlugin.java @@ -0,0 +1,155 @@ +/* + * 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 com.google.common.collect.ImmutableList; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.Extension; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; +import org.polypheny.db.iface.Authenticator; +import org.polypheny.db.iface.QueryInterface; +import org.polypheny.db.iface.QueryInterfaceManager; +import org.polypheny.db.plugins.PluginContext; +import org.polypheny.db.plugins.PolyPlugin; +import org.polypheny.db.prisminterface.PIPlugin.PrismInterface.Transport; +import org.polypheny.db.transaction.TransactionManager; + +public class PIPlugin extends PolyPlugin { + + public PIPlugin( PluginContext context ) { + super( context ); + } + + + @Override + public void afterCatalogInit() { + QueryInterfaceManager.addInterfaceTemplate( PrismInterface.INTERFACE_NAME + " (Plain transport)", + PrismInterface.INTERFACE_DESCRIPTION, PrismInterface.AVAILABLE_PLAIN_SETTINGS, ( a, b, c, d ) -> new PrismInterface( a, b, c, Transport.PLAIN, d ) ); + QueryInterfaceManager.addInterfaceTemplate( PrismInterface.INTERFACE_NAME + " (Unix transport)", + PrismInterface.INTERFACE_DESCRIPTION, PrismInterface.AVAILABLE_UNIX_SETTINGS, ( a, b, c, d ) -> new PrismInterface( a, b, c, Transport.UNIX, d ) ); + } + + + public void stop() { + QueryInterfaceManager.removeInterfaceType( PrismInterface.INTERFACE_NAME ); + } + + + @Slf4j + @Extension + public static class PrismInterface extends QueryInterface implements PropertyChangeListener { + + public static final String INTERFACE_NAME = "Prism Interface"; + public static final String INTERFACE_DESCRIPTION = "Query interface supporting multiple query languages and data models."; + public static final List AVAILABLE_PLAIN_SETTINGS = ImmutableList.of( + new QueryInterfaceSettingInteger( "port", false, true, false, 20590 ) + ); + public static final List AVAILABLE_UNIX_SETTINGS = ImmutableList.of( + new QueryInterfaceSettingString( "path", false, true, false, "polypheny-prism.sock" ) + ); + + @Getter + private final TransactionManager transactionManager; + @Getter + private final Authenticator authenticator; + @Getter + private ClientManager clientManager; + private PIServer prismInterfaceServer; + @Getter + private MonitoringPage monitoringPage; + + + enum Transport { + PLAIN, + UNIX, + } + + + private final Transport transport; + + + private PrismInterface( TransactionManager transactionManager, Authenticator authenticator, String uniqueName, Transport transport, Map settings ) { + super( transactionManager, authenticator, uniqueName, settings, true, true ); + this.authenticator = authenticator; + this.transactionManager = transactionManager; + this.transport = transport; + this.monitoringPage = new MonitoringPage( uniqueName, INTERFACE_NAME ); + } + + + @Override + public List getAvailableSettings() { + return switch ( transport ) { + case PLAIN -> AVAILABLE_PLAIN_SETTINGS; + case UNIX -> AVAILABLE_UNIX_SETTINGS; + }; + } + + + @Override + public void shutdown() { + try { + prismInterfaceServer.shutdown(); + } catch ( IOException | InterruptedException e ) { + throw new GenericRuntimeException( e ); + } + } + + + @Override + public String getInterfaceType() { + return INTERFACE_NAME; + } + + + @Override + protected void reloadSettings( List updatedSettings ) { + } + + + @Override + public void propertyChange( PropertyChangeEvent evt ) { + + } + + + @Override + public void languageChange() { + + } + + + @Override + public void run() { + clientManager = new ClientManager( this ); + try { + prismInterfaceServer = PIServer.startServer( clientManager, transport, settings ); + } catch ( IOException e ) { + log.error( "Prism interface server could not be started: {}", e.getMessage() ); + throw new GenericRuntimeException( e ); + } + } + + } + +} diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIServer.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIServer.java new file mode 100644 index 0000000000..1e9860eb3a --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIServer.java @@ -0,0 +1,199 @@ +/* + * 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 java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.nio.channels.AsynchronousCloseException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; +import javax.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; +import org.polypheny.db.prisminterface.PIPlugin.PrismInterface; +import org.polypheny.db.prisminterface.transport.PlainTransport; +import org.polypheny.db.prisminterface.transport.Transport; +import org.polypheny.db.prisminterface.transport.UnixTransport; +import org.polypheny.db.util.PolyphenyHomeDirManager; +import org.polypheny.db.util.Util; + +@Slf4j +class PIServer { + + private final ServerSocketChannel server; + private final static AtomicLong ID_COUNTER = new AtomicLong(); + private final ServerAndLock fileLock; // Needed for unix servers to keep a lock on the socket + private AtomicBoolean shutdown = new AtomicBoolean( false ); + + + private PIServer( ServerSocketChannel server, ClientManager clientManager, String name, Function createTransport, @Nullable ServerAndLock fileLock ) throws IOException { + this.server = server; + this.fileLock = fileLock; + log.info( "Prism Interface started and is listening for {} connections on {}", name.toLowerCase(), server.getLocalAddress() ); + Thread acceptor = new Thread( () -> acceptLoop( server, clientManager, name, createTransport ), "PrismInterface" + name + "Server" ); + acceptor.start(); + Runtime.getRuntime().addShutdownHook( new Thread( this::shutdownHook ) ); + } + + + private PIServer( ServerSocketChannel server, ClientManager clientManager, String name, Function createTransport ) throws IOException { + this( server, clientManager, name, createTransport, null ); + } + + + static PIServer startServer( ClientManager clientManager, PrismInterface.Transport transport, Map settings ) throws IOException { + return switch ( transport ) { + case PLAIN -> new PIServer( createInetServer( Integer.parseInt( settings.get( "port" ) ) ), clientManager, "Plain", PlainTransport::accept ); + case UNIX -> { + ServerAndLock sl = createUnixServer( settings.get( "path" ) ); + yield new PIServer( sl.server, clientManager, "Unix", UnixTransport::accept, sl ); + } + }; + } + + + private static ServerSocketChannel createInetServer( int port ) throws IOException { + return ServerSocketChannel.open( StandardProtocolFamily.INET ) + .bind( new InetSocketAddress( Inet4Address.getLoopbackAddress(), port ) ); + } + + + private static ServerAndLock createUnixServer( String path ) throws IOException { + File socket; + if ( !path.endsWith( ".sock" ) ) { + throw new IOException( "Socket paths must end with .sock" ); + } + Path p = Paths.get( path ); + Path lockPath; + if ( p.isAbsolute() ) { + socket = p.toFile(); + lockPath = Paths.get( path + ".lock" ); + } else { + if ( p.getNameCount() != 1 ) { + throw new IOException( "Relative socket paths may not contain directory separators" ); + } + PolyphenyHomeDirManager phm = PolyphenyHomeDirManager.getInstance(); + socket = phm.registerNewFile( path ); + lockPath = phm.registerNewFile( path + ".lock" ).toPath(); + } + FileChannel fileChannel = FileChannel.open( lockPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE ); + Optional fileLock = Optional.ofNullable( fileChannel.tryLock() ); + if ( fileLock.isPresent() ) { + socket.delete(); + ServerSocketChannel s = ServerSocketChannel.open( StandardProtocolFamily.UNIX ) + .bind( UnixDomainSocketAddress.of( socket.getAbsolutePath() ) ); + socket.setWritable( true, false ); + return new ServerAndLock( s, fileLock.get(), socket, lockPath ); + } else { + throw new IOException( "There is already a Polypheny instance listening at " + socket.getPath() ); + } + } + + + private String getRemoteAddressOrNone( SocketChannel s ) { + try { + return s.getRemoteAddress().toString(); + } catch ( IOException ignore ) { + return "unknown"; + } + } + + + private void acceptConnection( SocketChannel s, String name, long connectionId, Function createTransport, ClientManager clientManager ) { + try { + log.info( "accept {} connection with id {} from {}", name.toLowerCase(), connectionId, getRemoteAddressOrNone( s ) ); + PIService.acceptConnection( createTransport.apply( s ), connectionId, clientManager ); + } catch ( GenericRuntimeException e ) { + if ( e.getCause() instanceof EOFException ) { + return; + } + if ( e.getCause() instanceof IOException ) { + log.error( "accept {} connection: {}", name, e.getCause().getMessage() ); + return; + } + throw e; + } + } + + + private void acceptLoop( ServerSocketChannel server, ClientManager clientManager, String name, Function createTransport ) { + while ( true ) { + try { + SocketChannel s = server.accept(); + long connectionId = ID_COUNTER.getAndIncrement(); + Thread t = new Thread( () -> acceptConnection( s, name, connectionId, createTransport, clientManager ), String.format( "PrismInterface" + name + "ClientConnection%d", connectionId ) ); + t.start(); + } catch ( IOException e ) { + if (e instanceof AsynchronousCloseException && shutdown.get() ) { + return; + } + log.error( "acceptLoop", e ); + break; + } catch ( Throwable t ) { + // For debug purposes + log.error( "Unhandled exception", t ); + break; + } + } + } + + + void shutdown() throws InterruptedException, IOException { + if ( shutdown.getAndSet( true ) ) { + return; + } + if ( log.isTraceEnabled() ) { + log.trace( "prism-interface server shutdown requested" ); + } + if ( fileLock != null ) { + fileLock.socket.delete(); + fileLock.lockFile.toFile().delete(); + fileLock.lock.release(); + } + Util.closeNoThrow( server ); + } + + + private void shutdownHook() { + try { + shutdown(); + } catch ( IOException | InterruptedException e ) { + log.info( "Shutdown hook failed: ", e ); + } + } + + + private record ServerAndLock( ServerSocketChannel server, FileLock lock, File socket, Path lockFile ) { + + } + +} diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIService.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIService.java new file mode 100644 index 0000000000..080a329229 --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIService.java @@ -0,0 +1,581 @@ +/* + * 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 java.io.EOFException; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.NotImplementedException; +import org.polypheny.db.algebra.constant.FunctionCategory; +import org.polypheny.db.catalog.Catalog; +import org.polypheny.db.catalog.entity.logical.LogicalNamespace; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; +import org.polypheny.db.iface.AuthenticationException; +import org.polypheny.db.languages.QueryLanguage; +import org.polypheny.db.prisminterface.statementProcessing.StatementProcessor; +import org.polypheny.db.prisminterface.statements.PIPreparedIndexedStatement; +import org.polypheny.db.prisminterface.statements.PIPreparedNamedStatement; +import org.polypheny.db.prisminterface.statements.PIStatement; +import org.polypheny.db.prisminterface.statements.PIUnparameterizedStatement; +import org.polypheny.db.prisminterface.statements.PIUnparameterizedStatementBatch; +import org.polypheny.db.prisminterface.transport.Transport; +import org.polypheny.db.prisminterface.utils.PrismUtils; +import org.polypheny.db.prisminterface.utils.PrismValueDeserializer; +import org.polypheny.db.prisminterface.utils.PropertyUtils; +import org.polypheny.db.prisminterface.utils.VersionUtils; +import org.polypheny.db.sql.language.SqlJdbcFunctionCall; +import org.polypheny.db.transaction.TransactionException; +import org.polypheny.db.type.entity.PolyValue; +import org.polypheny.db.util.Util; +import org.polypheny.prism.ClientInfoProperties; +import org.polypheny.prism.ClientInfoPropertiesRequest; +import org.polypheny.prism.ClientInfoPropertiesResponse; +import org.polypheny.prism.CloseResultRequest; +import org.polypheny.prism.CloseResultResponse; +import org.polypheny.prism.CloseStatementRequest; +import org.polypheny.prism.CloseStatementResponse; +import org.polypheny.prism.CommitRequest; +import org.polypheny.prism.CommitResponse; +import org.polypheny.prism.ConnectionCheckRequest; +import org.polypheny.prism.ConnectionCheckResponse; +import org.polypheny.prism.ConnectionProperties; +import org.polypheny.prism.ConnectionPropertiesUpdateRequest; +import org.polypheny.prism.ConnectionPropertiesUpdateResponse; +import org.polypheny.prism.ConnectionRequest; +import org.polypheny.prism.ConnectionResponse; +import org.polypheny.prism.ConnectionResponse.Builder; +import org.polypheny.prism.DbmsVersionRequest; +import org.polypheny.prism.DbmsVersionResponse; +import org.polypheny.prism.DefaultNamespaceRequest; +import org.polypheny.prism.DefaultNamespaceResponse; +import org.polypheny.prism.DisconnectRequest; +import org.polypheny.prism.DisconnectResponse; +import org.polypheny.prism.EntitiesRequest; +import org.polypheny.prism.EntitiesResponse; +import org.polypheny.prism.ErrorResponse; +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; +import org.polypheny.prism.Frame; +import org.polypheny.prism.FunctionsRequest; +import org.polypheny.prism.FunctionsResponse; +import org.polypheny.prism.MetaStringResponse; +import org.polypheny.prism.NamespacesRequest; +import org.polypheny.prism.NamespacesResponse; +import org.polypheny.prism.PrepareStatementRequest; +import org.polypheny.prism.PreparedStatementSignature; +import org.polypheny.prism.ProceduresRequest; +import org.polypheny.prism.ProceduresResponse; +import org.polypheny.prism.Request; +import org.polypheny.prism.Request.TypeCase; +import org.polypheny.prism.Response; +import org.polypheny.prism.RollbackRequest; +import org.polypheny.prism.RollbackResponse; +import org.polypheny.prism.SqlKeywordsRequest; +import org.polypheny.prism.SqlNumericFunctionsRequest; +import org.polypheny.prism.SqlStringFunctionsRequest; +import org.polypheny.prism.SqlSystemFunctionsRequest; +import org.polypheny.prism.SqlTimeDateFunctionsRequest; +import org.polypheny.prism.StatementBatchResponse; +import org.polypheny.prism.StatementResponse; +import org.polypheny.prism.StatementResult; +import org.polypheny.prism.TableTypesRequest; +import org.polypheny.prism.TableTypesResponse; +import org.polypheny.prism.TypesRequest; +import org.polypheny.prism.TypesResponse; + +@Slf4j +class PIService { + + private final long connectionId; + private final ClientManager clientManager; + private final Transport con; + private String uuid = null; + + + private PIService( Transport con, long connectionId, ClientManager clientManager ) { + this.con = con; + this.connectionId = connectionId; + this.clientManager = clientManager; + } + + + public static void acceptConnection( Transport con, long connectionId, ClientManager clientManager ) { + PIService service = new PIService( con, connectionId, clientManager ); + service.acceptLoop(); + } + + + private Response createErrorResponse( long id, String message ) { + return Response.newBuilder() + .setId( id ) + .setLast( true ) + .setErrorResponse( ErrorResponse.newBuilder().setMessage( message ) ) + .build(); + } + + + private boolean handleFirstMessage() throws IOException { + boolean success = false; + Request firstReq = readOneMessage(); + + if ( firstReq.getTypeCase() != TypeCase.CONNECTION_REQUEST ) { + sendOneMessage( createErrorResponse( firstReq.getId(), "First message must be a connection request" ) ); + return false; + } + + Response r; + try { + r = connect( firstReq.getConnectionRequest(), new ResponseMaker<>( firstReq, "connection_response" ) ); + success = true; + } catch ( TransactionException | AuthenticationException e ) { + r = createErrorResponse( firstReq.getId(), e.getMessage() ); + } + sendOneMessage( r ); + return success; + } + + + private CompletableFuture waitForRequest() { + return CompletableFuture.supplyAsync( () -> { + try { + return readOneMessage(); + } catch ( IOException e ) { + throw new PIServiceException( e ); + } + } ); + } + + + private void handleRequest( Request req, CompletableFuture f ) { + Response r; + try { + r = handleMessage( req ); + } catch ( Throwable t ) { + r = createErrorResponse( req.getId(), t.getMessage() ); + } + try { + sendOneMessage( r ); + f.complete( r ); + } catch ( IOException e ) { + throw new GenericRuntimeException( e ); + } + } + + + private void handleMessages() throws IOException, ExecutionException, InterruptedException { + if ( !handleFirstMessage() ) { + return; + } + + Queue waiting = new LinkedList<>(); + CompletableFuture request = waitForRequest(); + CompletableFuture response = null; + Thread handle = null; + try { + while ( true ) { + if ( response == null ) { + Request req = waiting.poll(); + if ( req != null ) { + response = new CompletableFuture<>(); + CompletableFuture finalResponse = response; + handle = new Thread( () -> handleRequest( req, finalResponse ), String.format( "PrismConnection%dRequest%dHandler", connectionId, req.getId() ) ); + handle.setUncaughtExceptionHandler( ( t, e ) -> finalResponse.completeExceptionally( e ) ); + handle.start(); + } + } + + // Wait for next event + if ( response == null ) { + request.get(); + } else { + CompletableFuture.anyOf( request, response ).get(); + } + + if ( request.isDone() ) { + waiting.add( request.get() ); + request = waitForRequest(); + } else if ( response != null && response.isDone() ) { + handle.join(); + handle = null; + Response r = response.get(); + if ( r.getTypeCase() == Response.TypeCase.DISCONNECT_RESPONSE ) { + break; + } + response = null; + } + } + } catch ( ExecutionException e ) { + if ( e.getCause() instanceof PIServiceException p && p.getCause() instanceof EOFException eof ) { + throw eof; + } + throw e; + } finally { + if ( handle != null ) { + handle.interrupt(); + } + } + } + + + private void acceptLoop() { + try { + handleMessages(); + } catch ( Throwable e ) { + if ( !(e instanceof EOFException) ) { + throw new GenericRuntimeException( e ); + } + } finally { + if ( uuid != null ) { + clientManager.unregisterConnection( clientManager.getClient( uuid ) ); + } + Util.closeNoThrow( con ); + } + } + + + private void sendOneMessage( Response r ) throws IOException { + con.sendMessage( r.toByteArray() ); + } + + + private Request readOneMessage() throws IOException { + return Request.parseFrom( con.receiveMessage() ); + } + + + private Response handleMessage( Request req ) throws IOException { + if ( uuid == null || clientManager.getClient( uuid ) == null ) { + throw new IllegalStateException( "Clients must be authenticated before sending any messages" ); + } + return switch ( req.getTypeCase() ) { + case DBMS_VERSION_REQUEST -> getDbmsVersion( req.getDbmsVersionRequest(), new ResponseMaker<>( req, "dbms_version_response" ) ); + case DEFAULT_NAMESPACE_REQUEST -> getDefaultNamespace( req.getDefaultNamespaceRequest(), new ResponseMaker<>( req, "default_namespace_response" ) ); + case TABLE_TYPES_REQUEST -> getTableTypes( req.getTableTypesRequest(), new ResponseMaker<>( req, "table_types_response" ) ); + case TYPES_REQUEST -> getTypes( req.getTypesRequest(), new ResponseMaker<>( req, "types_response" ) ); + case PROCEDURES_REQUEST -> searchProcedures( req.getProceduresRequest(), new ResponseMaker<>( req, "procedures_response" ) ); + case FUNCTIONS_REQUEST -> searchFunctions( req.getFunctionsRequest(), new ResponseMaker<>( req, "functions_response" ) ); + case NAMESPACES_REQUEST -> searchNamespaces( req.getNamespacesRequest(), new ResponseMaker<>( req, "namespaces_response" ) ); + case ENTITIES_REQUEST -> searchEntities( req.getEntitiesRequest(), new ResponseMaker<>( req, "entities_response" ) ); + case SQL_STRING_FUNCTIONS_REQUEST -> getSqlStringFunctions( req.getSqlStringFunctionsRequest(), new ResponseMaker<>( req, "sql_string_functions_response" ) ); + case SQL_SYSTEM_FUNCTIONS_REQUEST -> getSqlSystemFunctions( req.getSqlSystemFunctionsRequest(), new ResponseMaker<>( req, "sql_system_functions_response" ) ); + case SQL_TIME_DATE_FUNCTIONS_REQUEST -> getSqlTimeDateFunctions( req.getSqlTimeDateFunctionsRequest(), new ResponseMaker<>( req, "sql_time_date_functions_response" ) ); + case SQL_NUMERIC_FUNCTIONS_REQUEST -> getSqlNumericFunctions( req.getSqlNumericFunctionsRequest(), new ResponseMaker<>( req, "sql_numeric_functions_response" ) ); + case SQL_KEYWORDS_REQUEST -> getSqlKeywords( req.getSqlKeywordsRequest(), new ResponseMaker<>( req, "sql_keywords_response" ) ); + case CONNECTION_REQUEST -> throw new GenericRuntimeException( "ConnectionRequest only allowed as first message" ); + case CONNECTION_CHECK_REQUEST -> checkConnection( req.getConnectionCheckRequest(), new ResponseMaker<>( req, "connection_check_response" ) ); + case DISCONNECT_REQUEST -> disconnect( req.getDisconnectRequest(), new ResponseMaker<>( req, "disconnect_response" ) ); + case CLIENT_INFO_PROPERTIES_REQUEST -> getClientInfoProperties( req.getClientInfoPropertiesRequest(), new ResponseMaker<>( req, "client_info_properties_response" ) ); + case SET_CLIENT_INFO_PROPERTIES_REQUEST -> setClientInfoProperties( req.getSetClientInfoPropertiesRequest(), new ResponseMaker<>( req, "set_client_info_properties_response" ) ); + case EXECUTE_UNPARAMETERIZED_STATEMENT_REQUEST -> executeUnparameterizedStatement( req.getExecuteUnparameterizedStatementRequest(), new ResponseMaker<>( req, "statement_response" ) ); + case EXECUTE_UNPARAMETERIZED_STATEMENT_BATCH_REQUEST -> executeUnparameterizedStatementBatch( req.getExecuteUnparameterizedStatementBatchRequest(), new ResponseMaker<>( req, "statement_batch_response" ) ); + case PREPARE_INDEXED_STATEMENT_REQUEST -> prepareIndexedStatement( req.getPrepareIndexedStatementRequest(), new ResponseMaker<>( req, "prepared_statement_signature" ) ); + case EXECUTE_INDEXED_STATEMENT_REQUEST -> executeIndexedStatement( req.getExecuteIndexedStatementRequest(), new ResponseMaker<>( req, "statement_result" ) ); + case EXECUTE_INDEXED_STATEMENT_BATCH_REQUEST -> executeIndexedStatementBatch( req.getExecuteIndexedStatementBatchRequest(), new ResponseMaker<>( req, "statement_batch_response" ) ); + case PREPARE_NAMED_STATEMENT_REQUEST -> prepareNamedStatement( req.getPrepareNamedStatementRequest(), new ResponseMaker<>( req, "prepared_statement_signature" ) ); + case EXECUTE_NAMED_STATEMENT_REQUEST -> executeNamedStatement( req.getExecuteNamedStatementRequest(), new ResponseMaker<>( req, "statement_result" ) ); + case FETCH_REQUEST -> fetchResult( req.getFetchRequest(), new ResponseMaker<>( req, "frame" ) ); + case CLOSE_STATEMENT_REQUEST -> closeStatement( req.getCloseStatementRequest(), new ResponseMaker<>( req, "close_statement_response" ) ); + case COMMIT_REQUEST -> commitTransaction( req.getCommitRequest(), new ResponseMaker<>( req, "commit_response" ) ); + case ROLLBACK_REQUEST -> rollbackTransaction( req.getRollbackRequest(), new ResponseMaker<>( req, "rollback_response" ) ); + case CONNECTION_PROPERTIES_UPDATE_REQUEST -> updateConnectionProperties( req.getConnectionPropertiesUpdateRequest(), new ResponseMaker<>( req, "connection_properties_update_response" ) ); + case CLOSE_RESULT_REQUEST -> closeResult( req.getCloseResultRequest(), new ResponseMaker<>( req, "close_result_response" ) ); + case TYPE_NOT_SET -> throw new NotImplementedException( "Unsupported call " + req.getTypeCase() ); + }; + } + + + private Response connect( ConnectionRequest request, ResponseMaker responseObserver ) throws TransactionException, AuthenticationException { + if ( uuid != null ) { + throw new PIServiceException( "Can only connect once per session" ); + } + Builder responseBuilder = ConnectionResponse.newBuilder() + .setMajorApiVersion( VersionUtils.getMAJOR_API_VERSION() ) + .setMinorApiVersion( VersionUtils.getMINOR_API_VERSION() ); + boolean isCompatible = checkApiVersion( request ); + responseBuilder.setIsCompatible( isCompatible ); + ConnectionResponse ConnectionResponse = responseBuilder.build(); + // reject incompatible client + if ( !isCompatible ) { + log.info( "Incompatible client and server version" ); + return responseObserver.makeResponse( ConnectionResponse ); + } + + uuid = clientManager.registerConnection( request, con ); + return responseObserver.makeResponse( ConnectionResponse ); + } + + + private Response disconnect( DisconnectRequest request, ResponseMaker responseObserver ) { + PIClient client = getClient(); + clientManager.unregisterConnection( client ); + uuid = null; + return responseObserver.makeResponse( DisconnectResponse.newBuilder().build() ); + } + + + private Response checkConnection( ConnectionCheckRequest request, ResponseMaker responseObserver ) { + return responseObserver.makeResponse( ConnectionCheckResponse.newBuilder().build() ); + } + + + private Response getDbmsVersion( DbmsVersionRequest request, ResponseMaker responseObserver ) { + return responseObserver.makeResponse( DbMetaRetriever.getDbmsVersion() ); + } + + + private MetaStringResponse buildMetaStringResponse( String string ) { + return MetaStringResponse.newBuilder() + .setString( string ) + .build(); + } + + + private Response getDefaultNamespace( DefaultNamespaceRequest request, ResponseMaker responseObserver ) { + return responseObserver.makeResponse( DbMetaRetriever.getDefaultNamespace() ); + } + + + private Response getTableTypes( TableTypesRequest request, ResponseMaker responseObserver ) { + return responseObserver.makeResponse( DbMetaRetriever.getTableTypes() ); + } + + + private Response getTypes( TypesRequest request, ResponseMaker responseStreamObserver ) { + return responseStreamObserver.makeResponse( DbMetaRetriever.getTypes() ); + } + + + private Response searchNamespaces( NamespacesRequest request, ResponseMaker responseObserver ) { + String namespacePattern = request.hasNamespacePattern() ? request.getNamespacePattern() : null; + String namespaceType = request.hasNamespaceType() ? request.getNamespaceType() : null; + return responseObserver.makeResponse( DbMetaRetriever.searchNamespaces( namespacePattern, namespaceType ) ); + } + + + private Response searchEntities( EntitiesRequest request, ResponseMaker responseObserver ) { + String entityPattern = request.hasEntityPattern() ? request.getEntityPattern() : null; + return responseObserver.makeResponse( DbMetaRetriever.searchEntities( request.getNamespaceName(), entityPattern ) ); + } + + + private Response getSqlStringFunctions( SqlStringFunctionsRequest request, ResponseMaker responseObserver ) { + return responseObserver.makeResponse( buildMetaStringResponse( SqlJdbcFunctionCall.getStringFunctions() ) ); + } + + + private Response getSqlSystemFunctions( SqlSystemFunctionsRequest request, ResponseMaker responseObserver ) { + return responseObserver.makeResponse( buildMetaStringResponse( SqlJdbcFunctionCall.getSystemFunctions() ) ); + } + + + private Response getSqlTimeDateFunctions( SqlTimeDateFunctionsRequest request, ResponseMaker responseObserver ) { + return responseObserver.makeResponse( buildMetaStringResponse( SqlJdbcFunctionCall.getTimeDateFunctions() ) ); + } + + + private Response getSqlNumericFunctions( SqlNumericFunctionsRequest request, ResponseMaker responseObserver ) { + return responseObserver.makeResponse( buildMetaStringResponse( SqlJdbcFunctionCall.getNumericFunctions() ) ); + } + + + private Response getSqlKeywords( SqlKeywordsRequest request, ResponseMaker responseObserver ) { + // TODO actually return keywords + return responseObserver.makeResponse( buildMetaStringResponse( "" ) ); + } + + + private Response searchProcedures( ProceduresRequest request, ResponseMaker responseObserver ) { + String procedurePattern = request.hasProcedureNamePattern() ? request.getProcedureNamePattern() : null; + return responseObserver.makeResponse( DbMetaRetriever.getProcedures( request.getLanguage(), procedurePattern ) ); + } + + + private Response searchFunctions( FunctionsRequest request, ResponseMaker responseObserver ) { + QueryLanguage queryLanguage = QueryLanguage.from( request.getQueryLanguage() ); + FunctionCategory functionCategory = FunctionCategory.valueOf( request.getFunctionCategory() ); + return responseObserver.makeResponse( DbMetaRetriever.getFunctions( queryLanguage, functionCategory ) ); + } + + + private Response executeUnparameterizedStatement( ExecuteUnparameterizedStatementRequest request, ResponseMaker responseObserver ) throws IOException { + PIClient client = getClient(); + PIUnparameterizedStatement statement = client.getStatementManager().createUnparameterizedStatement( request ); + Response mid = responseObserver.makeResponse( PrismUtils.createResult( statement ), false ); + sendOneMessage( mid ); + StatementResult result = statement.execute( + request.hasFetchSize() + ? request.getFetchSize() + : PropertyUtils.DEFAULT_FETCH_SIZE + ); + return responseObserver.makeResponse( PrismUtils.createResult( statement, result ) ); + } + + + private Response executeUnparameterizedStatementBatch( ExecuteUnparameterizedStatementBatchRequest request, ResponseMaker responseObserver ) throws IOException { + PIClient client = getClient(); + PIUnparameterizedStatementBatch batch = client.getStatementManager().createUnparameterizedStatementBatch( request.getStatementsList() ); + Response mid = responseObserver.makeResponse( PrismUtils.createStatementBatchStatus( batch.getBatchId() ), false ); + sendOneMessage( mid ); + List updateCounts = batch.executeBatch(); + return responseObserver.makeResponse( PrismUtils.createStatementBatchStatus( batch.getBatchId(), updateCounts ) ); + } + + + private Response prepareIndexedStatement( PrepareStatementRequest request, ResponseMaker responseObserver ) { + PIClient client = getClient(); + PIPreparedIndexedStatement statement = client.getStatementManager().createIndexedPreparedInterfaceStatement( request ); + return responseObserver.makeResponse( PrismUtils.createPreparedStatementSignature( statement ) ); + } + + + private Response executeIndexedStatement( ExecuteIndexedStatementRequest request, ResponseMaker responseObserver ) { + PIClient client = getClient(); + PIPreparedIndexedStatement statement = client.getStatementManager().getIndexedPreparedStatement( request.getStatementId() ); + int fetchSize = request.hasFetchSize() + ? request.getFetchSize() + : PropertyUtils.DEFAULT_FETCH_SIZE; + return responseObserver.makeResponse( statement.execute( PrismValueDeserializer.deserializeParameterList( request.getParameters().getParametersList() ), statement.getParameterMetas(), fetchSize ) ); + } + + + private Response executeIndexedStatementBatch( ExecuteIndexedStatementBatchRequest request, ResponseMaker resultObserver ) { + PIClient client = getClient(); + PIPreparedIndexedStatement statement = client.getStatementManager().getIndexedPreparedStatement( request.getStatementId() ); + List> valuesList = PrismValueDeserializer.deserializeParameterLists( request.getParametersList() ); + List updateCounts = statement.executeBatch( valuesList ); + return resultObserver.makeResponse( PrismUtils.createStatementBatchStatus( statement.getId(), updateCounts ) ); + } + + + private Response prepareNamedStatement( PrepareStatementRequest request, ResponseMaker responseObserver ) { + PIClient client = getClient(); + PIPreparedNamedStatement statement = client.getStatementManager().createNamedPreparedInterfaceStatement( request ); + return responseObserver.makeResponse( PrismUtils.createPreparedStatementSignature( statement ) ); + } + + + private Response executeNamedStatement( ExecuteNamedStatementRequest request, ResponseMaker responseObserver ) { + PIClient client = getClient(); + PIPreparedNamedStatement statement = client.getStatementManager().getNamedPreparedStatement( request.getStatementId() ); + int fetchSize = request.hasFetchSize() + ? request.getFetchSize() + : PropertyUtils.DEFAULT_FETCH_SIZE; + try { + return responseObserver.makeResponse( statement.execute( PrismValueDeserializer.deserilaizeParameterMap( request.getParameters().getParametersMap() ), fetchSize ) ); + } catch ( Exception e ) { + throw new GenericRuntimeException( e ); + } + } + + + private Response fetchResult( FetchRequest request, ResponseMaker responseObserver ) { + PIClient client = getClient(); + PIStatement statement = client.getStatementManager().getStatement( request.getStatementId() ); + int fetchSize = request.hasFetchSize() + ? request.getFetchSize() + : PropertyUtils.DEFAULT_FETCH_SIZE; + Frame frame = StatementProcessor.fetch( statement, fetchSize ); + return responseObserver.makeResponse( frame ); + } + + + private Response commitTransaction( CommitRequest request, ResponseMaker responseStreamObserver ) { + PIClient client = getClient(); + client.commitCurrentTransaction(); + return responseStreamObserver.makeResponse( CommitResponse.newBuilder().build() ); + } + + + private Response rollbackTransaction( RollbackRequest request, ResponseMaker responseStreamObserver ) { + PIClient client = getClient(); + client.rollbackCurrentTransaction(); + return responseStreamObserver.makeResponse( RollbackResponse.newBuilder().build() ); + } + + + private Response closeStatement( CloseStatementRequest request, ResponseMaker responseObserver ) { + PIClient client = getClient(); + client.getStatementManager().closeStatementOrBatch( request.getStatementId() ); + return responseObserver.makeResponse( CloseStatementResponse.newBuilder().build() ); + } + + + private Response closeResult( CloseResultRequest request, ResponseMaker responseObserver ) { + PIClient client = getClient(); + PIStatement statement = client.getStatementManager().getStatement( request.getStatementId() ); + statement.closeResults(); + return responseObserver.makeResponse( CloseResultResponse.newBuilder().build() ); + } + + + private Response updateConnectionProperties( ConnectionPropertiesUpdateRequest request, ResponseMaker responseObserver ) { + PIClient client = getClient(); + ConnectionProperties properties = request.getConnectionProperties(); + if ( properties.hasIsAutoCommit() ) { + client.setAutoCommit( properties.getIsAutoCommit() ); + } + if ( properties.hasNamespaceName() ) { + String namespaceName = properties.getNamespaceName(); + Optional optionalNamespace = Catalog.getInstance().getSnapshot().getNamespace( namespaceName ); + if ( optionalNamespace.isEmpty() ) { + throw new PIServiceException( "Getting namespace " + namespaceName + " failed." ); + } + client.setNamespace( optionalNamespace.get() ); + } + return responseObserver.makeResponse( ConnectionPropertiesUpdateResponse.newBuilder().build() ); + } + + + private Response getClientInfoProperties( ClientInfoPropertiesRequest request, ResponseMaker responseObserver ) { + PIClient client = getClient(); + ClientInfoProperties.Builder responseBuilder = ClientInfoProperties.newBuilder(); + PIClientInfoProperties PIClientInfoProperties = client.getPIClientInfoProperties(); + PIClientInfoProperties.stringPropertyNames().forEach( s -> responseBuilder.putProperties( s, PIClientInfoProperties.getProperty( s ) ) ); + return responseObserver.makeResponse( responseBuilder.build() ); + } + + + private Response setClientInfoProperties( ClientInfoProperties properties, ResponseMaker reponseObserver ) { + PIClient client = getClient(); + client.getPIClientInfoProperties().putAll( properties.getPropertiesMap() ); + return reponseObserver.makeResponse( ClientInfoPropertiesResponse.newBuilder().build() ); + } + + + private PIClient getClient() throws PIServiceException { + if ( uuid == null ) { + throw new PIServiceException( "Must authenticate first" ); + } + return clientManager.getClient( uuid ); + } + + + private static boolean checkApiVersion( ConnectionRequest connectionRequest ) { + return connectionRequest.getMajorApiVersion() == VersionUtils.getMAJOR_API_VERSION(); + } + +} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIServiceException.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIServiceException.java similarity index 89% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIServiceException.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIServiceException.java index 60d085cd01..91d5744760 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIServiceException.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/PIServiceException.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,14 +14,15 @@ * limitations under the License. */ -package org.polypheny.db.protointerface; +package org.polypheny.db.prisminterface; import java.sql.SQLException; import java.util.Optional; import lombok.Getter; -import org.polypheny.db.protointerface.proto.ErrorDetails; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; +import org.polypheny.prism.ErrorDetails; -public class PIServiceException extends RuntimeException { +public class PIServiceException extends GenericRuntimeException { @Getter private String state; @@ -54,11 +55,6 @@ public PIServiceException( String reason ) { } - public PIServiceException() { - super(); - } - - public PIServiceException( Throwable cause ) { super( cause ); } @@ -89,7 +85,7 @@ public PIServiceException( ErrorDetails errorDetails ) { } - public ErrorDetails getProtoErrorDetails() { + public ErrorDetails getPrismErrorDetails() { ErrorDetails.Builder errorDetailsBuilder = ErrorDetails.newBuilder(); errorDetailsBuilder.setErrorCode( getErrorCode() ); Optional.ofNullable( getState() ).ifPresent( errorDetailsBuilder::setState ); diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/ResponseMaker.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/ResponseMaker.java new file mode 100644 index 0000000000..029ad2964c --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/ResponseMaker.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.db.prisminterface; + +import org.polypheny.prism.Request; +import org.polypheny.prism.Response; + +public class ResponseMaker { + + private final Request req; + private final String field; + + + ResponseMaker( Request req, String field ) { + this.req = req; + this.field = field; + } + + + Response makeResponse( T msg ) { + return makeResponse( msg, true ); + } + + + Response makeResponse( T msg, boolean isLast ) { + return Response.newBuilder() + .setId( req.getId() ) + .setLast( isLast ) + .setField( Response.getDescriptor().findFieldByName( field ), msg ) + .build(); + } + +} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/relational/RelationalMetaRetriever.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/relational/RelationalMetaRetriever.java similarity index 92% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/relational/RelationalMetaRetriever.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/relational/RelationalMetaRetriever.java index 2ec4252580..1698652b4d 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/relational/RelationalMetaRetriever.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/relational/RelationalMetaRetriever.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.relational; +package org.polypheny.db.prisminterface.relational; import java.util.ArrayList; import java.util.List; @@ -29,14 +29,14 @@ import org.polypheny.db.algebra.type.AlgDataTypeField; import org.polypheny.db.plan.AlgOptUtil; import org.polypheny.db.processing.QueryProcessorHelpers; -import org.polypheny.db.protointerface.proto.ArrayMeta; -import org.polypheny.db.protointerface.proto.ColumnMeta; -import org.polypheny.db.protointerface.proto.FieldMeta; -import org.polypheny.db.protointerface.proto.ParameterMeta; -import org.polypheny.db.protointerface.proto.ProtoPolyType; -import org.polypheny.db.protointerface.proto.StructMeta; -import org.polypheny.db.protointerface.proto.TypeMeta; import org.polypheny.db.type.PolyType; +import org.polypheny.prism.ArrayMeta; +import org.polypheny.prism.ColumnMeta; +import org.polypheny.prism.FieldMeta; +import org.polypheny.prism.ParameterMeta; +import org.polypheny.prism.ProtoPolyType; +import org.polypheny.prism.StructMeta; +import org.polypheny.prism.TypeMeta; public class RelationalMetaRetriever { @@ -140,6 +140,7 @@ private static TypeMeta retrieveTypeMeta( AlgDataType algDataType ) { .setElementType( elementTypeMeta ) .build(); return TypeMeta.newBuilder() + .setProtoValueType( ProtoPolyType.ARRAY ) .setArrayMeta( arrayMeta ) .build(); } else { @@ -156,8 +157,9 @@ private static TypeMeta retrieveTypeMeta( AlgDataType algDataType ) { //TODO TH: handle structured type meta in a useful way .build(); } + ProtoPolyType type = getFromPolyType( polyType ); return TypeMeta.newBuilder() - .setProtoValueType( getFromPolyType( polyType ) ) + .setProtoValueType( type ) .build(); } } @@ -174,7 +176,7 @@ private static AlgDataType retrieveAlgDataType( PolyImplementation polyImplement JavaTypeFactory typeFactory = polyImplementation.getStatement().getTransaction().getTypeFactory(); return AlgOptUtil.createDmlRowType( kind, typeFactory ); default: - return polyImplementation.getRowType(); + return polyImplementation.tupleType; } } diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/DocumentExecutor.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/DocumentExecutor.java similarity index 82% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/DocumentExecutor.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/DocumentExecutor.java index aa307f3642..2c430eb562 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/DocumentExecutor.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/DocumentExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.statementProcessing; +package org.polypheny.db.prisminterface.statementProcessing; import java.util.List; import java.util.stream.Collectors; @@ -22,18 +22,18 @@ import org.polypheny.db.PolyImplementation; import org.polypheny.db.ResultIterator; import org.polypheny.db.catalog.logistic.DataModel; -import org.polypheny.db.protointerface.PIClient; -import org.polypheny.db.protointerface.PIServiceException; -import org.polypheny.db.protointerface.proto.Frame; -import org.polypheny.db.protointerface.proto.StatementResult; -import org.polypheny.db.protointerface.statements.PIStatement; -import org.polypheny.db.protointerface.utils.ProtoUtils; +import org.polypheny.db.prisminterface.PIClient; +import org.polypheny.db.prisminterface.PIServiceException; +import org.polypheny.db.prisminterface.statements.PIStatement; +import org.polypheny.db.prisminterface.utils.PrismUtils; import org.polypheny.db.transaction.Statement; import org.polypheny.db.type.entity.PolyValue; +import org.polypheny.prism.Frame; +import org.polypheny.prism.StatementResult; public class DocumentExecutor extends Executor { - private static DataModel namespaceType = DataModel.DOCUMENT; + private static final DataModel namespaceType = DataModel.DOCUMENT; @Override @@ -46,7 +46,7 @@ DataModel getDataModel() { StatementResult executeAndGetResult( PIStatement piStatement ) { if ( hasInvalidNamespaceType( piStatement ) ) { throw new PIServiceException( "The results of type " - + piStatement.getLanguage().getDataModel() + + piStatement.getLanguage().dataModel() + "returned by this statement can't be retrieved by a document retriever.", "I9000", 9000 @@ -73,10 +73,10 @@ StatementResult executeAndGetResult( PIStatement piStatement ) { @Override - StatementResult executeAndGetResult( PIStatement piStatement, int fetchSize ) throws Exception { + StatementResult executeAndGetResult( PIStatement piStatement, int fetchSize ) { if ( hasInvalidNamespaceType( piStatement ) ) { throw new PIServiceException( "The results of type " - + piStatement.getLanguage().getDataModel() + + piStatement.getLanguage().dataModel() + "returned by this statement can't be retrieved by a document retriever.", "I9000", 9000 @@ -110,7 +110,7 @@ StatementResult executeAndGetResult( PIStatement piStatement, int fetchSize ) th Frame fetch( PIStatement piStatement, int fetchSize ) { if ( hasInvalidNamespaceType( piStatement ) ) { throw new PIServiceException( "The results of type " - + piStatement.getLanguage().getDataModel() + + piStatement.getLanguage().dataModel() + "returned by this statement can't be retrieved by a document retriever.", "I9000", 9000 @@ -126,22 +126,22 @@ Frame fetch( PIStatement piStatement, int fetchSize ) { } PolyImplementation implementation = piStatement.getImplementation(); if ( implementation == null ) { - throw new PIServiceException( "Can't fetch form an unexecuted statement.", + throw new PIServiceException( "Can't fetch from an unexecuted statement.", "I9002", 9002 ); } ResultIterator iterator = piStatement.getIterator(); if ( iterator == null ) { - throw new PIServiceException( "Can't fetch form an unexecuted statement.", + throw new PIServiceException( "Can't fetch from an unexecuted statement.", "I9002", 9002 ); } startOrResumeStopwatch( executionStopWatch ); - List data = iterator.getNextBatch().stream().map( p -> p.get( 0 ) ).collect( Collectors.toList() ); + List data = iterator.getNextBatch( fetchSize ).stream().map( p -> p.get( 0 ) ).collect( Collectors.toList() ); executionStopWatch.stop(); - return ProtoUtils.buildDocumentFrame( !iterator.hasMoreRows(), data ); + return PrismUtils.buildDocumentFrame( !iterator.hasMoreRows(), data ); } } diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/Executor.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/Executor.java similarity index 77% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/Executor.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/Executor.java index fe9c1d18f2..2030a84be3 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/Executor.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/Executor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.statementProcessing; +package org.polypheny.db.prisminterface.statementProcessing; import org.apache.commons.lang3.time.StopWatch; import org.polypheny.db.catalog.logistic.DataModel; -import org.polypheny.db.protointerface.proto.Frame; -import org.polypheny.db.protointerface.proto.StatementResult; -import org.polypheny.db.protointerface.statements.PIStatement; +import org.polypheny.db.prisminterface.statements.PIStatement; +import org.polypheny.prism.Frame; +import org.polypheny.prism.StatementResult; public abstract class Executor { @@ -30,19 +30,20 @@ protected void startOrResumeStopwatch( StopWatch stopWatch ) { return; } if ( stopWatch.isStopped() ) { + stopWatch.reset(); stopWatch.start(); } } protected boolean hasInvalidNamespaceType( PIStatement piStatement ) { - return piStatement.getLanguage().getDataModel() != getDataModel(); + return piStatement.getLanguage().dataModel() != getDataModel(); } abstract DataModel getDataModel(); - abstract StatementResult executeAndGetResult( PIStatement piStatement ) throws Exception; + abstract StatementResult executeAndGetResult( PIStatement piStatement ); abstract StatementResult executeAndGetResult( PIStatement piStatement, int fetchSize ) throws Exception; diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/RelationalExecutor.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/RelationalExecutor.java similarity index 75% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/RelationalExecutor.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/RelationalExecutor.java index 50762b6b98..f73afa9446 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/RelationalExecutor.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/RelationalExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,29 +14,29 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.statementProcessing; +package org.polypheny.db.prisminterface.statementProcessing; import java.util.List; -import java.util.Objects; import org.apache.commons.lang3.time.StopWatch; import org.polypheny.db.PolyImplementation; import org.polypheny.db.ResultIterator; import org.polypheny.db.algebra.constant.Kind; import org.polypheny.db.catalog.logistic.DataModel; -import org.polypheny.db.protointerface.PIClient; -import org.polypheny.db.protointerface.PIServiceException; -import org.polypheny.db.protointerface.proto.ColumnMeta; -import org.polypheny.db.protointerface.proto.Frame; -import org.polypheny.db.protointerface.proto.StatementResult; -import org.polypheny.db.protointerface.relational.RelationalMetaRetriever; -import org.polypheny.db.protointerface.statements.PIStatement; -import org.polypheny.db.protointerface.utils.ProtoUtils; +import org.polypheny.db.monitoring.events.MonitoringType; +import org.polypheny.db.prisminterface.PIClient; +import org.polypheny.db.prisminterface.PIServiceException; +import org.polypheny.db.prisminterface.relational.RelationalMetaRetriever; +import org.polypheny.db.prisminterface.statements.PIStatement; +import org.polypheny.db.prisminterface.utils.PrismUtils; import org.polypheny.db.transaction.Statement; import org.polypheny.db.type.entity.PolyValue; +import org.polypheny.prism.ColumnMeta; +import org.polypheny.prism.Frame; +import org.polypheny.prism.StatementResult; public class RelationalExecutor extends Executor { - private static DataModel namespaceType = DataModel.RELATIONAL; + private static final DataModel namespaceType = DataModel.RELATIONAL; @Override @@ -46,10 +46,10 @@ DataModel getDataModel() { @Override - StatementResult executeAndGetResult( PIStatement piStatement ) throws Exception { + StatementResult executeAndGetResult( PIStatement piStatement ) { if ( hasInvalidNamespaceType( piStatement ) ) { throw new PIServiceException( "The results of type " - + piStatement.getLanguage().getDataModel() + + piStatement.getLanguage().dataModel() + "returned by this statement can't be retrieved by a relational retriever.", "I9000", 9000 @@ -64,7 +64,7 @@ StatementResult executeAndGetResult( PIStatement piStatement ) throws Exception } PolyImplementation implementation = piStatement.getImplementation(); if ( implementation == null ) { - throw new PIServiceException( "Can't retrieve results form an unexecuted statement.", + throw new PIServiceException( "Can't retrieve results from an unexecuted statement.", "I9002", 9002 ); @@ -72,7 +72,9 @@ StatementResult executeAndGetResult( PIStatement piStatement ) throws Exception PIClient client = piStatement.getClient(); StatementResult.Builder resultBuilder = StatementResult.newBuilder(); if ( implementation.isDDL() || Kind.DML.contains( implementation.getKind() ) ) { - resultBuilder.setScalar( implementation.getRowsChanged( statement ) ); + try ( ResultIterator iterator = implementation.execute( statement, -1 ) ) { + resultBuilder.setScalar( PolyImplementation.getRowsChanged( statement, iterator.getIterator(), MonitoringType.from( implementation.getKind() ) ) ); + } client.commitCurrentTransactionIfAuto(); return resultBuilder.build(); } @@ -83,10 +85,10 @@ StatementResult executeAndGetResult( PIStatement piStatement ) throws Exception } - public StatementResult executeAndGetResult( PIStatement piStatement, int fetchSize ) throws Exception { + public StatementResult executeAndGetResult( PIStatement piStatement, int fetchSize ) { if ( hasInvalidNamespaceType( piStatement ) ) { throw new PIServiceException( "The results of type " - + piStatement.getLanguage().getDataModel() + + piStatement.getLanguage().dataModel() + "returned by this statement can't be retrieved by a relational retriever.", "I9000", 9000 @@ -101,7 +103,7 @@ public StatementResult executeAndGetResult( PIStatement piStatement, int fetchSi } PolyImplementation implementation = piStatement.getImplementation(); if ( implementation == null ) { - throw new PIServiceException( "Can't retrieve results form an unprepared statement.", + throw new PIServiceException( "Can't retrieve results from an unprepared statement.", "I9002", 9002 ); @@ -113,7 +115,9 @@ public StatementResult executeAndGetResult( PIStatement piStatement, int fetchSi return resultBuilder.build(); } if ( Kind.DML.contains( implementation.getKind() ) ) { - resultBuilder.setScalar( implementation.getRowsChanged( statement ) ); + try ( ResultIterator iterator = implementation.execute( statement, -1 ) ) { + resultBuilder.setScalar( PolyImplementation.getRowsChanged( statement, iterator.getIterator(), MonitoringType.from( implementation.getKind() ) ) ); + } client.commitCurrentTransactionIfAuto(); return resultBuilder.build(); } @@ -132,7 +136,7 @@ public StatementResult executeAndGetResult( PIStatement piStatement, int fetchSi public Frame fetch( PIStatement piStatement, int fetchSize ) { if ( hasInvalidNamespaceType( piStatement ) ) { throw new PIServiceException( "The results of type " - + piStatement.getLanguage().getDataModel() + + piStatement.getLanguage().dataModel() + "returned by this statement can't be retrieved by a relational retriever.", "I9000", 9000 @@ -141,28 +145,28 @@ public Frame fetch( PIStatement piStatement, int fetchSize ) { StopWatch executionStopWatch = piStatement.getExecutionStopWatch(); PolyImplementation implementation = piStatement.getImplementation(); if ( implementation == null ) { - throw new PIServiceException( "Can't fetch form an unprepared statement.", + throw new PIServiceException( "Can't fetch from an unprepared statement.", "I9002", 9002 ); } ResultIterator iterator = piStatement.getIterator(); if ( iterator == null ) { - throw new PIServiceException( "Can't fetch form an unexecuted statement.", + throw new PIServiceException( "Can't fetch from an unexecuted statement.", "I9002", 9002 ); } startOrResumeStopwatch( executionStopWatch ); - List> rows = iterator.getNextBatch(); + List> rows = iterator.getNextBatch( fetchSize ); executionStopWatch.suspend(); - boolean isLast = fetchSize == 0 || Objects.requireNonNull( rows ).size() < fetchSize; + boolean isLast = !iterator.hasMoreRows(); if ( isLast ) { executionStopWatch.stop(); implementation.getExecutionTimeMonitor().setExecutionTime( executionStopWatch.getNanoTime() ); } List columnMetas = RelationalMetaRetriever.retrieveColumnMetas( implementation ); - return ProtoUtils.buildRelationalFrame( isLast, rows, columnMetas ); + return PrismUtils.buildRelationalFrame( isLast, rows, columnMetas ); } } diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/StatementProcessor.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/StatementProcessor.java similarity index 55% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/StatementProcessor.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/StatementProcessor.java index 8b52bc88de..fe48b6f7cc 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/StatementProcessor.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statementProcessing/StatementProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,31 +14,34 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.statementProcessing; +package org.polypheny.db.prisminterface.statementProcessing; import com.google.common.collect.ImmutableMap; +import java.util.List; import java.util.Map; import org.polypheny.db.algebra.type.AlgDataType; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; import org.polypheny.db.catalog.logistic.DataModel; +import org.polypheny.db.languages.LanguageManager; import org.polypheny.db.languages.QueryLanguage; import org.polypheny.db.nodes.Node; +import org.polypheny.db.prisminterface.PIServiceException; +import org.polypheny.db.prisminterface.relational.RelationalMetaRetriever; +import org.polypheny.db.prisminterface.statements.PIPreparedStatement; +import org.polypheny.db.prisminterface.statements.PIStatement; +import org.polypheny.db.processing.ImplementationContext; import org.polypheny.db.processing.Processor; -import org.polypheny.db.protointerface.PIServiceException; -import org.polypheny.db.protointerface.proto.Frame; -import org.polypheny.db.protointerface.proto.StatementResult; -import org.polypheny.db.protointerface.relational.RelationalMetaRetriever; -import org.polypheny.db.protointerface.statements.PIPreparedStatement; -import org.polypheny.db.protointerface.statements.PIStatement; +import org.polypheny.db.processing.QueryContext; +import org.polypheny.db.transaction.Statement; import org.polypheny.db.transaction.Transaction; import org.polypheny.db.util.Pair; +import org.polypheny.prism.Frame; +import org.polypheny.prism.StatementResult; public class StatementProcessor { - private static final Map EXECUTORS = - ImmutableMap.builder() - .put( QueryLanguage.from( "sql" ), new SqlImplementer() ) - .put( QueryLanguage.from( "mongo" ), new MongoImplementer() ) - .build(); + private static final String ORIGIN = "prism-interface"; + private static final Map RESULT_RETRIEVERS = ImmutableMap.builder() .put( DataModel.RELATIONAL, new RelationalExecutor() ) @@ -47,22 +50,37 @@ public class StatementProcessor { public static void implement( PIStatement piStatement ) { - StatementImplementer statementImplementer = EXECUTORS.get( piStatement.getLanguage() ); - if ( statementImplementer == null ) { - throw new PIServiceException( "No executor registered for language " + piStatement.getLanguage(), - "I9005", - 9005 + Statement statement = piStatement.getStatement(); + if ( statement == null ) { + throw new PIServiceException( "Statement is not linked to a PolyphenyStatement", + "I9003", + 9003 ); } - statementImplementer.implement( piStatement ); + QueryContext context = QueryContext.builder() + .query( piStatement.getQuery() ) + .language( piStatement.getLanguage() ) + .namespaceId( piStatement.getNamespace().id ) + .transactionManager( piStatement.getTransaction().getTransactionManager() ) + .origin( ORIGIN ) + .build(); + List implementations = LanguageManager.getINSTANCE().anyPrepareQuery( context, statement ); + if ( implementations.stream().anyMatch( i -> i.getQuery().isAutoGenerated() ) ) { + throw new GenericRuntimeException( "This query relies on an auto-generated subquery, which is not supported by this interface." ); + } + + if ( implementations.get( 0 ).getImplementation() == null ) { + throw new GenericRuntimeException( implementations.get( 0 ).getException().orElseThrow() ); + } + piStatement.setImplementation( implementations.get( 0 ).getImplementation() ); } - public static StatementResult executeAndGetResult( PIStatement piStatement ) throws Exception { - Executor executor = RESULT_RETRIEVERS.get( piStatement.getLanguage().getDataModel() ); + public static StatementResult executeAndGetResult( PIStatement piStatement ) { + Executor executor = RESULT_RETRIEVERS.get( piStatement.getLanguage().dataModel() ); if ( executor == null ) { throw new PIServiceException( "No result retriever registered for namespace type " - + piStatement.getLanguage().getDataModel(), + + piStatement.getLanguage().dataModel(), "I9004", 9004 ); @@ -71,24 +89,28 @@ public static StatementResult executeAndGetResult( PIStatement piStatement ) thr } - public static StatementResult executeAndGetResult( PIStatement piStatement, int fetchSize ) throws Exception { - Executor executor = RESULT_RETRIEVERS.get( piStatement.getLanguage().getDataModel() ); + public static StatementResult executeAndGetResult( PIStatement piStatement, int fetchSize ) { + Executor executor = RESULT_RETRIEVERS.get( piStatement.getLanguage().dataModel() ); if ( executor == null ) { throw new PIServiceException( "No result retriever registered for namespace type " - + piStatement.getLanguage().getDataModel(), + + piStatement.getLanguage().dataModel(), "I9004", 9004 ); } - return executor.executeAndGetResult( piStatement, fetchSize ); + try { + return executor.executeAndGetResult( piStatement, fetchSize ); + } catch ( Exception e ) { + throw new GenericRuntimeException( e ); // TODO: check if this is still needed, or if executeAndGetResult throws another exception by now + } } public static Frame fetch( PIStatement piStatement, int fetchSize ) { - Executor executor = RESULT_RETRIEVERS.get( piStatement.getLanguage().getDataModel() ); + Executor executor = RESULT_RETRIEVERS.get( piStatement.getLanguage().dataModel() ); if ( executor == null ) { throw new PIServiceException( "No result retriever registered for namespace type " - + piStatement.getLanguage().getDataModel(), + + piStatement.getLanguage().dataModel(), "I9004", 9004 ); @@ -98,7 +120,7 @@ public static Frame fetch( PIStatement piStatement, int fetchSize ) { public static void prepare( PIPreparedStatement piStatement ) { - Transaction transaction = piStatement.getClient().getCurrentOrCreateNewTransaction(); + Transaction transaction = piStatement.getClient().getOrCreateNewTransaction(); String query = piStatement.getQuery(); QueryLanguage queryLanguage = piStatement.getLanguage(); diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIPreparedIndexedStatement.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIPreparedIndexedStatement.java new file mode 100644 index 0000000000..449133b425 --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIPreparedIndexedStatement.java @@ -0,0 +1,177 @@ +/* + * 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.statements; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import lombok.Getter; +import lombok.Setter; +import org.polypheny.db.PolyImplementation; +import org.polypheny.db.adapter.java.JavaTypeFactory; +import org.polypheny.db.algebra.type.AlgDataType; +import org.polypheny.db.catalog.entity.logical.LogicalNamespace; +import org.polypheny.db.languages.QueryLanguage; +import org.polypheny.db.prisminterface.PIClient; +import org.polypheny.db.prisminterface.statementProcessing.StatementProcessor; +import org.polypheny.db.transaction.Statement; +import org.polypheny.db.transaction.Transaction; +import org.polypheny.db.type.PolyType; +import org.polypheny.db.type.entity.PolyValue; +import org.polypheny.prism.ParameterMeta; +import org.polypheny.prism.StatementResult; + +@Getter +public class PIPreparedIndexedStatement extends PIPreparedStatement { + + protected String query; + protected Statement statement; + @Setter + protected PolyImplementation implementation; + + + public PIPreparedIndexedStatement( + int id, + PIClient client, + QueryLanguage language, + LogicalNamespace namespace, + String query ) { + super( + id, client, language, namespace + ); + this.query = query; + } + + + public List executeBatch( List> valuesBatch ) { + List updateCounts = new ArrayList<>(); + if ( statement == null || client.hasNoTransaction() ) { + statement = client.getOrCreateNewTransaction().createStatement(); + } else { + statement.getDataContext().resetParameterValues(); + } + List types = IntStream.range( 0, valuesBatch.size() ).mapToObj( i -> deriveType( statement.getTransaction().getTypeFactory(), parameterMetas.get( i ) ) ).toList(); + int i = 0; + for ( List column : valuesBatch ) { + statement.getDataContext().addParameterValues( i, types.get( i++ ), column ); + } + StatementProcessor.implement( this ); + updateCounts.add( StatementProcessor.executeAndGetResult( this ).getScalar() ); + //} + return updateCounts; + } + + + @SuppressWarnings("Duplicates") + public StatementResult execute( List values, List parameterMetas, int fetchSize ) { + if ( statement == null || client.hasNoTransaction() ) { + statement = client.getOrCreateNewTransaction().createStatement(); + } else { + statement.getDataContext().resetParameterValues(); + } + long index = 0; + for ( PolyValue value : values ) { + if ( value != null ) { + AlgDataType algDataType = parameterMetas.size() > index + ? deriveType( statement.getTransaction().getTypeFactory(), parameterMetas.get( (int) index ) ) + : statement.getTransaction().getTypeFactory().createPolyType( value.type ); + statement.getDataContext().addParameterValues( index++, algDataType, List.of( value ) ); + } + } + StatementProcessor.implement( this ); + return StatementProcessor.executeAndGetResult( this, fetchSize ); + } + + + private AlgDataType deriveType( JavaTypeFactory typeFactory, ParameterMeta parameterMeta ) { + return switch ( parameterMeta.getTypeName().toUpperCase() ) { + case "DECIMAL" -> { + if ( parameterMeta.getPrecision() >= 0 && parameterMeta.getScale() >= 0 ) { + yield typeFactory.createPolyType( PolyType.DECIMAL, parameterMeta.getPrecision(), parameterMeta.getScale() ); + } else if ( parameterMeta.getPrecision() >= 0 ) { + yield typeFactory.createPolyType( PolyType.DECIMAL, parameterMeta.getPrecision() ); + } + yield typeFactory.createPolyType( PolyType.DECIMAL ); + } + case "DOUBLE" -> typeFactory.createPolyType( PolyType.DOUBLE ); + case "FLOAT" -> typeFactory.createPolyType( PolyType.FLOAT ); + case "INT", "INTEGER" -> typeFactory.createPolyType( PolyType.INTEGER ); + case "VARCHAR" -> { + if ( parameterMeta.getPrecision() > 0 ) { + yield typeFactory.createPolyType( PolyType.VARCHAR, parameterMeta.getPrecision() ); + } + yield typeFactory.createPolyType( PolyType.VARCHAR ); + } + case "CHAR" -> { + if ( parameterMeta.getPrecision() > 0 ) { + yield typeFactory.createPolyType( PolyType.CHAR, parameterMeta.getPrecision() ); + } + yield typeFactory.createPolyType( PolyType.CHAR ); + } + case "TEXT" -> typeFactory.createPolyType( PolyType.TEXT ); + case "JSON" -> typeFactory.createPolyType( PolyType.JSON ); + case "BOOLEAN" -> typeFactory.createPolyType( PolyType.BOOLEAN ); + case "TINYINT" -> typeFactory.createPolyType( PolyType.TINYINT ); + case "SMALLINT" -> typeFactory.createPolyType( PolyType.SMALLINT ); + case "BIGINT" -> typeFactory.createPolyType( PolyType.BIGINT ); + case "DATE" -> typeFactory.createPolyType( PolyType.DATE ); + case "TIME" -> { + if ( parameterMeta.getPrecision() >= 0 ) { + yield typeFactory.createPolyType( PolyType.TIME, parameterMeta.getPrecision() ); + } + yield typeFactory.createPolyType( PolyType.TIME ); + } + case "TIMESTAMP" -> { + if ( parameterMeta.getPrecision() >= 0 ) { + yield typeFactory.createPolyType( PolyType.TIMESTAMP, parameterMeta.getPrecision() ); + } + yield typeFactory.createPolyType( PolyType.TIMESTAMP ); + } + case "BINARY" -> { + if ( parameterMeta.getPrecision() > 0 ) { + yield typeFactory.createPolyType( PolyType.BINARY, parameterMeta.getPrecision() ); + } + yield typeFactory.createPolyType( PolyType.BINARY ); + } + case "VARBINARY" -> { + if ( parameterMeta.getPrecision() > 0 ) { + yield typeFactory.createPolyType( PolyType.VARBINARY, parameterMeta.getPrecision() ); + } + yield typeFactory.createPolyType( PolyType.VARBINARY ); + } + case "FILE" -> typeFactory.createPolyType( PolyType.FILE ); + case "IMAGE" -> typeFactory.createPolyType( PolyType.IMAGE ); + case "VIDEO" -> typeFactory.createPolyType( PolyType.VIDEO ); + case "AUDIO" -> typeFactory.createPolyType( PolyType.AUDIO ); + default -> typeFactory.createPolyType( PolyType.valueOf( parameterMeta.getTypeName() ) ); + }; + } + + + @Override + public void close() { + statement.close(); + closeResults(); + } + + + @Override + public Transaction getTransaction() { + return statement.getTransaction(); + } + +} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIPreparedNamedStatement.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIPreparedNamedStatement.java similarity index 63% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIPreparedNamedStatement.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIPreparedNamedStatement.java index 174353641a..87ed675b49 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIPreparedNamedStatement.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIPreparedNamedStatement.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.statements; +package org.polypheny.db.prisminterface.statements; import java.util.List; import java.util.Map; @@ -23,13 +23,13 @@ import org.polypheny.db.PolyImplementation; import org.polypheny.db.catalog.entity.logical.LogicalNamespace; import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.protointerface.NamedValueProcessor; -import org.polypheny.db.protointerface.PIClient; -import org.polypheny.db.protointerface.proto.StatementResult; -import org.polypheny.db.protointerface.statementProcessing.StatementProcessor; +import org.polypheny.db.prisminterface.NamedValueProcessor; +import org.polypheny.db.prisminterface.PIClient; +import org.polypheny.db.prisminterface.statementProcessing.StatementProcessor; import org.polypheny.db.transaction.Statement; import org.polypheny.db.transaction.Transaction; import org.polypheny.db.type.entity.PolyValue; +import org.polypheny.prism.StatementResult; public class PIPreparedNamedStatement extends PIPreparedStatement { @@ -59,17 +59,24 @@ public PIPreparedNamedStatement( @SuppressWarnings("Duplicates") public StatementResult execute( Map values, int fetchSize ) throws Exception { - synchronized ( client ) { - if ( statement == null ) { - statement = client.getCurrentOrCreateNewTransaction().createStatement(); - } - List valueList = namedValueProcessor.transformValueMap( values ); - for ( int i = 0; i < valueList.size(); i++ ) { - statement.getDataContext().addParameterValues( i, null, List.of( valueList.get( i ) ) ); - } - StatementProcessor.implement( this ); - return StatementProcessor.executeAndGetResult( this, fetchSize ); + if ( statement == null || client.hasNoTransaction() ) { + statement = client.getOrCreateNewTransaction().createStatement(); + } else { + statement.getDataContext().resetParameterValues(); } + List valueList = namedValueProcessor.transformValueMap( values ); + for ( int i = 0; i < valueList.size(); i++ ) { + statement.getDataContext().addParameterValues( i, PolyValue.deriveType( valueList.get( i ), this.statement.getDataContext().getTypeFactory() ), List.of( valueList.get( i ) ) ); + } + StatementProcessor.implement( this ); + return StatementProcessor.executeAndGetResult( this, fetchSize ); + } + + + @Override + public void close() { + statement.close(); + closeResults(); } diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIPreparedStatement.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIPreparedStatement.java similarity index 84% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIPreparedStatement.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIPreparedStatement.java index 8c2b8b4f59..5ad99896c5 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIPreparedStatement.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIPreparedStatement.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,15 +14,15 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.statements; +package org.polypheny.db.prisminterface.statements; import java.util.List; import org.jetbrains.annotations.NotNull; import org.polypheny.db.catalog.entity.logical.LogicalNamespace; import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.protointerface.PIClient; -import org.polypheny.db.protointerface.proto.ParameterMeta; -import org.polypheny.db.protointerface.statementProcessing.StatementProcessor; +import org.polypheny.db.prisminterface.PIClient; +import org.polypheny.db.prisminterface.statementProcessing.StatementProcessor; +import org.polypheny.prism.ParameterMeta; public abstract class PIPreparedStatement extends PIStatement implements Signaturizable { diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIStatement.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIStatement.java similarity index 91% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIStatement.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIStatement.java index fcd81485ca..d31ca34a07 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIStatement.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIStatement.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.statements; +package org.polypheny.db.prisminterface.statements; import lombok.Getter; import lombok.Setter; @@ -26,7 +26,7 @@ import org.polypheny.db.catalog.entity.logical.LogicalNamespace; import org.polypheny.db.catalog.exceptions.GenericRuntimeException; import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.protointerface.PIClient; +import org.polypheny.db.prisminterface.PIClient; import org.polypheny.db.transaction.Statement; import org.polypheny.db.transaction.Transaction; @@ -61,12 +61,15 @@ public void closeResults() { } try { iterator.close(); + iterator = null; } catch ( Exception e ) { throw new GenericRuntimeException( "Closing of open result iterator failed" ); } } + public abstract void close(); + public abstract PolyImplementation getImplementation(); public abstract void setImplementation( PolyImplementation implementation ); diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIUnparameterizedStatement.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIUnparameterizedStatement.java similarity index 71% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIUnparameterizedStatement.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIUnparameterizedStatement.java index 42b747a092..9976c5aa1b 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIUnparameterizedStatement.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIUnparameterizedStatement.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.statements; +package org.polypheny.db.prisminterface.statements; import lombok.Getter; import lombok.Setter; @@ -22,11 +22,11 @@ import org.polypheny.db.PolyImplementation; import org.polypheny.db.catalog.entity.logical.LogicalNamespace; import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.protointerface.PIClient; -import org.polypheny.db.protointerface.proto.StatementResult; -import org.polypheny.db.protointerface.statementProcessing.StatementProcessor; +import org.polypheny.db.prisminterface.PIClient; +import org.polypheny.db.prisminterface.statementProcessing.StatementProcessor; import org.polypheny.db.transaction.Statement; import org.polypheny.db.transaction.Transaction; +import org.polypheny.prism.StatementResult; @Slf4j public class PIUnparameterizedStatement extends PIStatement { @@ -51,12 +51,18 @@ public PIUnparameterizedStatement( int id, PIClient client, QueryLanguage langua } - public StatementResult execute( int fetchSize ) throws Exception { - statement = client.getCurrentOrCreateNewTransaction().createStatement(); - synchronized ( client ) { - StatementProcessor.implement( this ); - return StatementProcessor.executeAndGetResult( this, fetchSize ); - } + public StatementResult execute( int fetchSize ) { + statement = client.getOrCreateNewTransaction().createStatement(); + + StatementProcessor.implement( this ); + return StatementProcessor.executeAndGetResult( this, fetchSize ); + } + + + @Override + public void close() { + statement.close(); + closeResults(); } diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIUnparameterizedStatementBatch.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIUnparameterizedStatementBatch.java similarity index 74% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIUnparameterizedStatementBatch.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIUnparameterizedStatementBatch.java index 7d19335275..29b7821892 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIUnparameterizedStatementBatch.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/PIUnparameterizedStatementBatch.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,31 +14,30 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.statements; - -import lombok.Getter; -import org.polypheny.db.protointerface.PIClient; +package org.polypheny.db.prisminterface.statements; import java.util.LinkedList; import java.util.List; -import org.polypheny.db.protointerface.utils.PropertyUtils; +import lombok.Getter; +import org.polypheny.db.prisminterface.PIClient; +import org.polypheny.db.prisminterface.utils.PropertyUtils; @Getter public class PIUnparameterizedStatementBatch { List statements; - PIClient protoInterfaceClient; + PIClient prismInterfaceClient; int batchId; - public PIUnparameterizedStatementBatch( int batchId, PIClient protoInterfaceClient, List statements ) { + public PIUnparameterizedStatementBatch( int batchId, PIClient prismInterfaceClient, List statements ) { this.statements = statements; - this.protoInterfaceClient = protoInterfaceClient; + this.prismInterfaceClient = prismInterfaceClient; this.batchId = batchId; } - public List executeBatch() throws Exception { + public List executeBatch() { int fetchSize = PropertyUtils.DEFAULT_FETCH_SIZE; List updateCounts = new LinkedList<>(); for ( PIUnparameterizedStatement statement : statements ) { diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/Signaturizable.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/Signaturizable.java new file mode 100644 index 0000000000..68450b1488 --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/Signaturizable.java @@ -0,0 +1,26 @@ +/* + * 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.statements; + +import java.util.List; +import org.polypheny.prism.ParameterMeta; + +public interface Signaturizable { + + List getParameterMetas(); + +} diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/StatementManager.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/StatementManager.java new file mode 100644 index 0000000000..73946541a8 --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/statements/StatementManager.java @@ -0,0 +1,228 @@ +/* + * 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.statements; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.polypheny.db.catalog.Catalog; +import org.polypheny.db.catalog.entity.logical.LogicalNamespace; +import org.polypheny.db.languages.LanguageManager; +import org.polypheny.db.languages.QueryLanguage; +import org.polypheny.db.prisminterface.PIClient; +import org.polypheny.db.prisminterface.PIServiceException; +import org.polypheny.prism.ExecuteUnparameterizedStatementRequest; +import org.polypheny.prism.PrepareStatementRequest; + +@Slf4j +public class StatementManager { + + private final AtomicInteger statementIdGenerator; + private final PIClient client; + private ConcurrentHashMap openStatements; + private ConcurrentHashMap openUnparameterizedBatches; + + + public StatementManager( PIClient client ) { + this.client = client; + statementIdGenerator = new AtomicInteger( 1 ); + openStatements = new ConcurrentHashMap<>(); + openUnparameterizedBatches = new ConcurrentHashMap<>(); + } + + + private LogicalNamespace getNamespace( String namespaceName ) { + Optional optionalNamespace = Catalog.getInstance().getSnapshot().getNamespace( namespaceName ); + + if ( optionalNamespace.isEmpty() ) { + throw new PIServiceException( "Getting namespace " + namespaceName + " failed." ); + } + return optionalNamespace.get(); + } + + + public PIUnparameterizedStatement createUnparameterizedStatement( ExecuteUnparameterizedStatementRequest request ) throws PIServiceException { + String languageName = request.getLanguageName(); + if ( !isSupportedLanguage( languageName ) ) { + throw new PIServiceException( "Language " + languageName + " not supported." ); + } + int statementId = statementIdGenerator.getAndIncrement(); + LogicalNamespace namespace = client.getNamespace(); + if ( request.hasNamespaceName() ) { + namespace = getNamespace( request.getNamespaceName() ); + } + assert namespace != null; + PIUnparameterizedStatement statement = new PIUnparameterizedStatement( + statementId, + client, + QueryLanguage.from( languageName ), + namespace, + request.getStatement() + ); + openStatements.put( statementId, statement ); + if ( log.isTraceEnabled() ) { + log.trace( "created request {}", statement ); + } + return statement; + } + + + public PIUnparameterizedStatementBatch createUnparameterizedStatementBatch( List statements ) { + List PIUnparameterizedStatements = statements.stream() + .map( this::createUnparameterizedStatement ) + .collect( Collectors.toList() ); + final int batchId = statementIdGenerator.getAndIncrement(); + final PIUnparameterizedStatementBatch batch = new PIUnparameterizedStatementBatch( batchId, client, PIUnparameterizedStatements ); + openUnparameterizedBatches.put( batchId, batch ); + if ( log.isTraceEnabled() ) { + log.trace( "created batch {}", batch ); + } + return batch; + } + + + public PIPreparedIndexedStatement createIndexedPreparedInterfaceStatement( PrepareStatementRequest request ) throws PIServiceException { + String languageName = request.getLanguageName(); + if ( !isSupportedLanguage( languageName ) ) { + throw new PIServiceException( "Language " + languageName + " not supported." ); + } + int statementId = statementIdGenerator.getAndIncrement(); + LogicalNamespace namespace = client.getNamespace(); + if ( request.hasNamespaceName() ) { + namespace = getNamespace( request.getNamespaceName() ); + } + assert namespace != null; + PIPreparedIndexedStatement statement = new PIPreparedIndexedStatement( + statementId, + client, + QueryLanguage.from( languageName ), + namespace, + request.getStatement() + ); + openStatements.put( statementId, statement ); + if ( log.isTraceEnabled() ) { + log.trace( "created named prepared statement {}", statement ); + } + return statement; + } + + + public PIPreparedNamedStatement createNamedPreparedInterfaceStatement( PrepareStatementRequest request ) throws PIServiceException { + String languageName = request.getLanguageName(); + if ( !isSupportedLanguage( languageName ) ) { + throw new PIServiceException( "Language " + languageName + " not supported." ); + } + final int statementId = statementIdGenerator.getAndIncrement(); + LogicalNamespace namespace = client.getNamespace(); + if ( request.hasNamespaceName() ) { + namespace = getNamespace( request.getNamespaceName() ); + } + assert namespace != null; + PIPreparedNamedStatement statement = new PIPreparedNamedStatement( + statementId, + client, + QueryLanguage.from( languageName ), + namespace, + request.getStatement() + ); + openStatements.put( statementId, statement ); + if ( log.isTraceEnabled() ) { + log.trace( "created named prepared statement {}", statement ); + } + return statement; + } + + + public void closeAll() { + openUnparameterizedBatches.values().forEach( this::closeBatch ); + openStatements.values().forEach( s -> closeStatement( s.getId() ) ); + } + + + public void closeBatch( PIUnparameterizedStatementBatch toClose ) { + toClose.getStatements().forEach( s -> closeStatementOrBatch( s.getId() ) ); + } + + + private void closeStatement( int statementId ) { + PIStatement statementToClose = openStatements.get( statementId ); + if ( statementToClose == null ) { + return; + } + + openStatements.remove( statementId ); + statementToClose.close(); + } + + + public void closeStatementOrBatch( int statementId ) { + PIUnparameterizedStatementBatch batchToClose = openUnparameterizedBatches.remove( statementId ); + if ( batchToClose != null ) { + closeBatch( batchToClose ); + return; + } + closeStatement( statementId ); + } + + + public PIStatement getStatement( int statementId ) { + if ( statementId == 0 ) { + throw new PIServiceException( "Invalid statement id: 0 (possibly uninitialized prism protocol field)" ); + } + PIStatement statement = openStatements.get( statementId ); + if ( statement == null ) { + throw new PIServiceException( "A statement with id " + statementId + " does not exist for that client" ); + } + return statement; + } + + + public PIPreparedNamedStatement getNamedPreparedStatement( int statementId ) throws PIServiceException { + PIStatement statement = getStatement( statementId ); + if ( !(statement instanceof PIPreparedNamedStatement) ) { + throw new PIServiceException( "A named prepared statement with id " + statementId + " does not exist for that client" ); + } + return (PIPreparedNamedStatement) statement; + } + + + public PIPreparedIndexedStatement getIndexedPreparedStatement( int statementId ) throws PIServiceException { + PIStatement statement = getStatement( statementId ); + if ( !(statement instanceof PIPreparedIndexedStatement) ) { + throw new PIServiceException( "A prepared indexed statement with id " + statementId + " does not exist for that client" ); + } + return (PIPreparedIndexedStatement) statement; + } + + + public boolean isSupportedLanguage( String statementLanguageName ) { + return LanguageManager.getLanguages() + .stream() + .map( QueryLanguage::serializedName ) + .collect( Collectors.toSet() ) + .contains( statementLanguageName ); + } + + + public int openStatementCount() { + return openStatements.size(); + } + +} diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/transport/PlainTransport.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/transport/PlainTransport.java new file mode 100644 index 0000000000..4e1d680135 --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/transport/PlainTransport.java @@ -0,0 +1,154 @@ +/* + * 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.transport; + +import java.io.EOFException; +import java.io.IOException; +import java.net.StandardSocketOptions; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.SocketChannel; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Optional; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; +import org.polypheny.db.util.Util; + +public class PlainTransport implements Transport { + + private final static String VERSION = "plain-v1@polypheny.com"; + + protected final SocketChannel con; + + + PlainTransport( SocketChannel con ) throws IOException { + this( con, VERSION ); + } + + + protected PlainTransport( SocketChannel con, String version ) throws IOException { + this.con = con; + exchangeVersion( version ); + } + + + @Override + public Optional getPeer() { + return Optional.empty(); + } + + + void exchangeVersion( String version ) throws IOException { + if ( !version.matches( "\\A[a-z0-9@.-]+\\z" ) ) { + throw new IOException( "Invalid version name" ); + } + byte len = (byte) (version.length() + 1); // Trailing '\n' + if ( len <= 0 ) { + throw new IOException( "Version too long" ); + } + ByteBuffer bb = ByteBuffer.allocate( 1 + len ); // Leading size + bb.put( len ); + bb.put( version.getBytes( StandardCharsets.US_ASCII ) ); + bb.put( (byte) '\n' ); + bb.rewind(); + writeEntireBuffer( bb ); + byte[] response = readVersionResponse( len ); + if ( !Arrays.equals( bb.array(), response ) ) { + throw new IOException( "Invalid client version" ); + } + } + + + private byte[] readVersionResponse( byte len ) throws IOException { + ByteBuffer bb = ByteBuffer.allocate( 1 + len ); // Leading size + ByteBuffer length = bb.slice( 0, 1 ); + bb.position( 1 ); + readEntireBuffer( length ); + if ( length.get() != len ) { + throw new IOException( "Invalid version response length" ); + } + readEntireBuffer( bb ); + return bb.array(); + } + + + private void writeEntireBuffer( ByteBuffer bb ) throws IOException { + synchronized ( con ) { + while ( bb.remaining() > 0 ) { + int i = con.write( bb ); + if ( i == -1 ) { + throw new EOFException(); + } + } + } + } + + + @Override + public void sendMessage( byte[] msg ) throws IOException { + ByteBuffer bb = ByteBuffer.allocate( 8 + msg.length ); + bb.order( ByteOrder.LITTLE_ENDIAN ); + bb.putLong( msg.length ); + bb.put( msg ); + bb.rewind(); + writeEntireBuffer( bb ); + } + + + private void readEntireBuffer( ByteBuffer bb ) throws IOException { + while ( bb.remaining() > 0 ) { + int i = con.read( bb ); + if ( i == -1 ) { + throw new EOFException(); + } + } + bb.rewind(); + } + + + @Override + public byte[] receiveMessage() throws IOException { + ByteBuffer bb = ByteBuffer.allocate( 8 ); + readEntireBuffer( bb ); + bb.order( ByteOrder.LITTLE_ENDIAN ); // TODO Big endian like other network protocols? + long length = bb.getLong(); + if ( length == 0 ) { + throw new IOException( "Invalid message length" ); + } + bb = ByteBuffer.allocate( (int) length ); + readEntireBuffer( bb ); + return bb.array(); + } + + + @Override + public void close() { + Util.closeNoThrow( con ); + } + + + public static Transport accept( SocketChannel con ) { + try { + con.setOption( StandardSocketOptions.TCP_NODELAY, true ); + return new PlainTransport( con ); + } catch ( IOException e ) { + Util.closeNoThrow( con ); + throw new GenericRuntimeException( e ); + } + } + +} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/StatementImplementer.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/transport/Transport.java similarity index 50% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/StatementImplementer.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/transport/Transport.java index 991f0ed1a3..80fc620835 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/StatementImplementer.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/transport/Transport.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,23 +14,20 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.statementProcessing; +package org.polypheny.db.prisminterface.transport; -import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.protointerface.statements.PIStatement; +import java.io.Closeable; +import java.io.IOException; +import java.util.Optional; -public abstract class StatementImplementer { +public interface Transport extends Closeable { - protected final String ORIGIN = "Proto-Interface"; + Optional getPeer(); + void sendMessage( byte[] msg ) throws IOException; - protected boolean hasInvalidLanguage( PIStatement piStatement ) { - return piStatement.getLanguage() != getLanguage(); - } + byte[] receiveMessage() throws IOException; - - abstract QueryLanguage getLanguage(); - - abstract void implement( PIStatement piStatement ); + void close(); } diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/transport/UnixTransport.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/transport/UnixTransport.java new file mode 100644 index 0000000000..75e12b3e50 --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/transport/UnixTransport.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.db.prisminterface.transport; + +import java.io.IOException; +import java.nio.channels.SocketChannel; +import java.util.Optional; +import jdk.net.ExtendedSocketOptions; +import jdk.net.UnixDomainPrincipal; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; + +public class UnixTransport extends PlainTransport { + + private final static String VERSION = "unix-v1@polypheny.com"; + + + UnixTransport( SocketChannel con ) throws IOException { + super( con, VERSION ); + } + + + public Optional getPeer() { + try { + UnixDomainPrincipal principal = con.getOption( ExtendedSocketOptions.SO_PEERCRED ); + return Optional.of( principal.user().toString() ); + } catch ( IOException e ) { + return Optional.empty(); + } + } + + + public static Transport accept( SocketChannel con ) { + try { + return new UnixTransport( con ); + } catch ( IOException e ) { + throw new GenericRuntimeException( e ); + } + } + +} diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PolyValueSerializer.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PolyValueSerializer.java new file mode 100644 index 0000000000..d3eb12788e --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PolyValueSerializer.java @@ -0,0 +1,303 @@ +/* + * 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.utils; + +import com.google.protobuf.ByteString; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.commons.lang3.NotImplementedException; +import org.polypheny.db.type.entity.PolyBinary; +import org.polypheny.db.type.entity.PolyBoolean; +import org.polypheny.db.type.entity.PolyInterval; +import org.polypheny.db.type.entity.PolyList; +import org.polypheny.db.type.entity.PolyString; +import org.polypheny.db.type.entity.PolyValue; +import org.polypheny.db.type.entity.category.PolyBlob; +import org.polypheny.db.type.entity.document.PolyDocument; +import org.polypheny.db.type.entity.numerical.PolyBigDecimal; +import org.polypheny.db.type.entity.numerical.PolyDouble; +import org.polypheny.db.type.entity.numerical.PolyFloat; +import org.polypheny.db.type.entity.numerical.PolyInteger; +import org.polypheny.db.type.entity.numerical.PolyLong; +import org.polypheny.db.type.entity.relational.PolyMap; +import org.polypheny.db.type.entity.temporal.PolyDate; +import org.polypheny.db.type.entity.temporal.PolyTime; +import org.polypheny.db.type.entity.temporal.PolyTimestamp; +import org.polypheny.prism.ProtoBigDecimal; +import org.polypheny.prism.ProtoBinary; +import org.polypheny.prism.ProtoBoolean; +import org.polypheny.prism.ProtoDate; +import org.polypheny.prism.ProtoDocument; +import org.polypheny.prism.ProtoDouble; +import org.polypheny.prism.ProtoEntry; +import org.polypheny.prism.ProtoFloat; +import org.polypheny.prism.ProtoInteger; +import org.polypheny.prism.ProtoInterval; +import org.polypheny.prism.ProtoList; +import org.polypheny.prism.ProtoLong; +import org.polypheny.prism.ProtoNull; +import org.polypheny.prism.ProtoString; +import org.polypheny.prism.ProtoTime; +import org.polypheny.prism.ProtoTimestamp; +import org.polypheny.prism.ProtoValue; + +public class PolyValueSerializer { + + + public static List serializeList( List valuesList ) { + return valuesList.stream().map( PolyValueSerializer::serialize ).collect( Collectors.toList() ); + } + + + public static List serializeToProtoEntryList( PolyMap polyMap ) { + return polyMap.entrySet().stream().map( PolyValueSerializer::serializeToProtoEntry ).collect( Collectors.toList() ); + } + + + public static ProtoEntry serializeToProtoEntry( Map.Entry polyMapEntry ) { + return ProtoEntry.newBuilder() + .setKey( serialize( polyMapEntry.getKey() ) ) + .setValue( serialize( polyMapEntry.getValue() ) ) + .build(); + } + + + public static ProtoValue serialize( PolyValue polyValue ) { + if ( polyValue == null || polyValue.isNull() ) { + return serializeAsProtoNull(); + } + return switch ( polyValue.getType() ) { + case BOOLEAN -> serializeAsProtoBoolean( polyValue.asBoolean() ); + case TINYINT, SMALLINT, INTEGER -> serializeAsProtoInteger( polyValue.asInteger() ); + case BIGINT -> serializeAsProtoLong( polyValue.asLong() ); + case DECIMAL -> serializeAsProtoBigDecimal( polyValue.asBigDecimal() ); + case REAL, FLOAT -> serializeAsProtoFloat( polyValue.asFloat() ); + case DOUBLE -> serializeAsProtoDouble( polyValue.asDouble() ); + case DATE -> serializeAsProtoDate( polyValue.asDate() ); + case TIME -> serializeAsProtoTime( polyValue.asTime() ); + case TIMESTAMP -> serializeAsProtoTimestamp( polyValue.asTimestamp() ); + case INTERVAL -> serializeAsProtoInterval( polyValue.asInterval() ); + case CHAR, VARCHAR -> serializeAsProtoString( polyValue.asString() ); + case BINARY, VARBINARY -> serializeAsProtoBinary( polyValue.asBinary() ); + case NULL -> serializeAsProtoNull(); + case ARRAY -> serializeAsProtoList( polyValue.asList() ); + case DOCUMENT -> serializeAsProtoDocument( polyValue.asDocument() ); + case IMAGE, VIDEO, AUDIO, FILE -> serializeAsProtoFile( polyValue.asBlob() ); // used + case MAP, GRAPH, NODE, EDGE, PATH, DISTINCT, STRUCTURED, ROW, OTHER, CURSOR, COLUMN_LIST, DYNAMIC_STAR, GEOMETRY, SYMBOL, JSON, MULTISET, USER_DEFINED_TYPE, ANY -> throw new NotImplementedException( "Serialization of " + polyValue.getType() + " to proto not implemented" ); + default -> throw new NotImplementedException(); + }; + } + + + public static ProtoDocument buildProtoDocument( PolyDocument polyDocument ) { + return ProtoDocument.newBuilder() + .addAllEntries( serializeToProtoEntryList( polyDocument.asMap() ) ) + .build(); + } + + + private static ProtoValue serializeAsProtoDocument( PolyDocument polyDocument ) { + return ProtoValue.newBuilder() + .setDocument( buildProtoDocument( polyDocument ) ) + .build(); + } + + + private static ProtoValue serializeAsProtoList( PolyList polyList ) { + return ProtoValue.newBuilder() + .setList( serializeToProtoList( polyList ) ) + .build(); + + } + + + private static ProtoList serializeToProtoList( PolyList polyList ) { + return ProtoList.newBuilder() + .addAllValues( serializeList( polyList.getValue() ) ) + .build(); + } + + + private static ProtoValue serializeAsProtoFile( PolyBlob polyBlob ) { + ProtoBinary protoBinary = ProtoBinary.newBuilder() + .setBinary( ByteString.copyFrom( polyBlob.getValue() ) ) + .build(); + return ProtoValue.newBuilder() + .setBinary( protoBinary ) + .build(); + } + + + private static ProtoValue serializeAsProtoInterval( PolyInterval polyInterval ) { + return ProtoValue.newBuilder() + .setInterval( ProtoInterval.newBuilder().setMonths( polyInterval.getMonths() ).setMilliseconds( polyInterval.getMillis() ).build() ) + .build(); + } + + + public static ProtoValue serializeAsProtoBoolean( PolyBoolean polyBoolean ) { + if ( polyBoolean.value == null ) { + return serializeAsProtoNull(); + } + ProtoBoolean protoBoolean = ProtoBoolean.newBuilder() + .setBoolean( polyBoolean.getValue() ) + .build(); + return ProtoValue.newBuilder() + .setBoolean( protoBoolean ) + .build(); + } + + + public static ProtoValue serializeAsProtoInteger( PolyInteger polyInteger ) { + if ( polyInteger.value == null ) { + return serializeAsProtoNull(); + } + ProtoInteger protoInteger = ProtoInteger.newBuilder() + .setInteger( polyInteger.getValue() ) + .build(); + return ProtoValue.newBuilder() + .setInteger( protoInteger ) + .build(); + } + + + public static ProtoValue serializeAsProtoLong( PolyLong polyLong ) { + if ( polyLong.value == null ) { + return serializeAsProtoNull(); + } + ProtoLong protoLong = ProtoLong.newBuilder() + .setLong( polyLong.value ) + .build(); + return ProtoValue.newBuilder() + .setLong( protoLong ) + .build(); + } + + + public static ProtoValue serializeAsProtoBinary( PolyBinary polyBinary ) { + if ( polyBinary.value == null ) { + return serializeAsProtoNull(); + } + ProtoBinary protoBinary = ProtoBinary.newBuilder() + .setBinary( ByteString.copyFrom( polyBinary.getValue() ) ) + .build(); + return ProtoValue.newBuilder() + .setBinary( protoBinary ) + .build(); + } + + + public static ProtoValue serializeAsProtoDate( PolyDate polyDate ) { + if ( polyDate.millisSinceEpoch == null ) { + return serializeAsProtoNull(); + } + ProtoDate protoDate = ProtoDate.newBuilder() + .setDate( polyDate.getDaysSinceEpoch() ) + .build(); + return ProtoValue.newBuilder() + .setDate( protoDate ) + .build(); + } + + + public static ProtoValue serializeAsProtoDouble( PolyDouble polyDouble ) { + if ( polyDouble.value == null ) { + return serializeAsProtoNull(); + } + ProtoDouble protoDouble = ProtoDouble.newBuilder() + .setDouble( polyDouble.doubleValue() ) + .build(); + return ProtoValue.newBuilder() + .setDouble( protoDouble ) + .build(); + } + + + public static ProtoValue serializeAsProtoFloat( PolyFloat polyFloat ) { + if ( polyFloat.value == null ) { + return serializeAsProtoNull(); + } + ProtoFloat protoFloat = ProtoFloat.newBuilder() + .setFloat( polyFloat.floatValue() ) + .build(); + return ProtoValue.newBuilder() + .setFloat( protoFloat ) + .build(); + } + + + public static ProtoValue serializeAsProtoString( PolyString polyString ) { + if ( polyString.value == null ) { + return serializeAsProtoNull(); + } + ProtoString protoString = ProtoString.newBuilder() + .setString( polyString.getValue() ) + .build(); + return ProtoValue.newBuilder() + .setString( protoString ) + .build(); + } + + + public static ProtoValue serializeAsProtoTime( PolyTime polyTime ) { + if ( polyTime.ofDay == null ) { + return serializeAsProtoNull(); + } + ProtoTime protoTime = ProtoTime.newBuilder() + .setTime( polyTime.getOfDay() ) + .build(); + return ProtoValue.newBuilder() + .setTime( protoTime ) + .build(); + } + + + public static ProtoValue serializeAsProtoTimestamp( PolyTimestamp polyTimestamp ) { + if ( polyTimestamp.millisSinceEpoch == null ) { + return serializeAsProtoNull(); + } + ProtoTimestamp protoTimestamp = ProtoTimestamp.newBuilder() + .setTimestamp( polyTimestamp.getMillisSinceEpoch() ) + .build(); + return ProtoValue.newBuilder() + .setTimestamp( protoTimestamp ) + .build(); + } + + + private static ProtoValue serializeAsProtoNull() { + return ProtoValue.newBuilder() + .setNull( ProtoNull.newBuilder().build() ) + .build(); + } + + + public static ProtoValue serializeAsProtoBigDecimal( PolyBigDecimal polyBigDecimal ) { + if ( polyBigDecimal.value == null ) { + return serializeAsProtoNull(); + } + ProtoBigDecimal protoBigDecimal = ProtoBigDecimal.newBuilder() + .setUnscaledValue( ByteString.copyFrom( polyBigDecimal.getValue().unscaledValue().toByteArray() ) ) + .setScale( polyBigDecimal.getValue().scale() ) + .build(); + return ProtoValue.newBuilder() + .setBigDecimal( protoBigDecimal ) + .build(); + } + +} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/utils/ProtoUtils.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PrismUtils.java similarity index 76% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/utils/ProtoUtils.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PrismUtils.java index a8a5e88332..bb0e66f4d4 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/utils/ProtoUtils.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PrismUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,25 +14,26 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.utils; +package org.polypheny.db.prisminterface.utils; import java.util.List; import java.util.stream.Collectors; -import org.polypheny.db.protointerface.proto.ColumnMeta; -import org.polypheny.db.protointerface.proto.DocumentFrame; -import org.polypheny.db.protointerface.proto.Frame; -import org.polypheny.db.protointerface.proto.PreparedStatementSignature; -import org.polypheny.db.protointerface.proto.ProtoDocument; -import org.polypheny.db.protointerface.proto.RelationalFrame; -import org.polypheny.db.protointerface.proto.Row; -import org.polypheny.db.protointerface.proto.StatementBatchResponse; -import org.polypheny.db.protointerface.proto.StatementResponse; -import org.polypheny.db.protointerface.proto.StatementResult; -import org.polypheny.db.protointerface.statements.PIPreparedStatement; -import org.polypheny.db.protointerface.statements.PIStatement; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; +import org.polypheny.db.prisminterface.statements.PIPreparedStatement; +import org.polypheny.db.prisminterface.statements.PIStatement; import org.polypheny.db.type.entity.PolyValue; +import org.polypheny.prism.ColumnMeta; +import org.polypheny.prism.DocumentFrame; +import org.polypheny.prism.Frame; +import org.polypheny.prism.PreparedStatementSignature; +import org.polypheny.prism.ProtoDocument; +import org.polypheny.prism.RelationalFrame; +import org.polypheny.prism.Row; +import org.polypheny.prism.StatementBatchResponse; +import org.polypheny.prism.StatementResponse; +import org.polypheny.prism.StatementResult; -public class ProtoUtils { +public class PrismUtils { public static StatementResponse createResult( PIStatement protoInterfaceStatement ) { @@ -81,7 +82,7 @@ public static Row serializeToRow( List row ) { public static List serializeToRows( List> rows ) { - return rows.stream().map( ProtoUtils::serializeToRow ).collect( Collectors.toList() ); + return rows.stream().map( PrismUtils::serializeToRow ).collect( Collectors.toList() ); } @@ -101,7 +102,7 @@ public static Frame buildDocumentFrame( boolean isLast, List data ) { List documents = data.stream() .map( PolyValue::asDocument ) .map( PolyValueSerializer::buildProtoDocument ) - .collect( Collectors.toList() ); + .toList(); DocumentFrame documentFrame = DocumentFrame.newBuilder() .addAllDocuments( documents ) .build(); @@ -113,7 +114,7 @@ public static Frame buildDocumentFrame( boolean isLast, List data ) { public static Frame buildGraphFrame() { - throw new RuntimeException( "Feature not implemented" ); + throw new GenericRuntimeException( "Feature not implemented" ); } } diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/utils/ProtoValueDeserializer.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PrismValueDeserializer.java similarity index 72% rename from plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/utils/ProtoValueDeserializer.java rename to plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PrismValueDeserializer.java index 28d7e8edaa..76e874d674 100644 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/utils/ProtoValueDeserializer.java +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PrismValueDeserializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,35 +14,38 @@ * limitations under the License. */ -package org.polypheny.db.protointerface.utils; +package org.polypheny.db.prisminterface.utils; import java.math.BigDecimal; import java.math.BigInteger; -import java.math.MathContext; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import org.polypheny.db.protointerface.proto.IndexedParameters; -import org.polypheny.db.protointerface.proto.ProtoBigDecimal; -import org.polypheny.db.protointerface.proto.ProtoValue; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; import org.polypheny.db.type.entity.PolyBinary; import org.polypheny.db.type.entity.PolyBoolean; import org.polypheny.db.type.entity.PolyList; -import org.polypheny.db.type.entity.PolyLong; import org.polypheny.db.type.entity.PolyNull; import org.polypheny.db.type.entity.PolyString; import org.polypheny.db.type.entity.PolyValue; +import org.polypheny.db.type.entity.category.PolyBlob; +import org.polypheny.db.type.entity.document.PolyDocument; import org.polypheny.db.type.entity.numerical.PolyBigDecimal; import org.polypheny.db.type.entity.numerical.PolyDouble; import org.polypheny.db.type.entity.numerical.PolyFloat; import org.polypheny.db.type.entity.numerical.PolyInteger; +import org.polypheny.db.type.entity.numerical.PolyLong; import org.polypheny.db.type.entity.temporal.PolyDate; import org.polypheny.db.type.entity.temporal.PolyTime; import org.polypheny.db.type.entity.temporal.PolyTimestamp; +import org.polypheny.prism.IndexedParameters; +import org.polypheny.prism.ProtoBigDecimal; +import org.polypheny.prism.ProtoValue; +import org.polypheny.prism.ProtoValue.ValueCase; -public class ProtoValueDeserializer { +public class PrismValueDeserializer { public static List> deserializeParameterLists( List parameterListsList ) { return transpose( parameterListsList.stream() @@ -66,7 +69,7 @@ private static List> transpose( List> values ) { public static List deserializeParameterList( List valuesList ) { - return valuesList.stream().map( ProtoValueDeserializer::deserializeProtoValue ).collect( Collectors.toList() ); + return valuesList.stream().map( PrismValueDeserializer::deserializeProtoValue ).collect( Collectors.toList() ); } @@ -82,24 +85,44 @@ public static PolyValue deserializeProtoValue( ProtoValue protoValue ) { case BOOLEAN -> deserializeToPolyBoolean( protoValue ); case INTEGER -> deserializeToPolyInteger( protoValue ); case LONG -> deserializeToPolyLong( protoValue ); - case BINARY -> deserializeToPolyBinary( protoValue ); - case DATE -> deserializeToPolyDate( protoValue ); - case DOUBLE -> deserializeToPolyDouble( protoValue ); + case BIG_DECIMAL -> deserializeToPolyBigDecimal( protoValue ); case FLOAT -> deserializeToPolyFloat( protoValue ); - case STRING -> deserializeToPolyString( protoValue ); + case DOUBLE -> deserializeToPolyDouble( protoValue ); + case DATE -> deserializeToPolyDate( protoValue ); case TIME -> deserializeToPolyTime( protoValue ); - case TIME_STAMP -> deserializeToPolyTimeStamp( protoValue ); - case NULL -> deserializeToPolyNull( protoValue ); - case BIG_DECIMAL -> deserializeToPolyBigDecimal( protoValue ); + case TIMESTAMP -> deserializeToPolyTimestamp( protoValue ); + case STRING -> deserializeToPolyString( protoValue ); + case BINARY -> deserializeToPolyBinary( protoValue ); + case NULL -> deserializeToPolyNull(); case LIST -> deserializeToPolyList( protoValue ); - default -> throw new RuntimeException( "Should never be thrown" ); + case FILE -> deserializeToPolyBlob( protoValue ); + case DOCUMENT -> deserializeToPolyDocument( protoValue ); + case VALUE_NOT_SET -> throw new GenericRuntimeException( "Invalid ProtoValue: no value is set" ); + default -> throw new GenericRuntimeException( "Deserialization of type " + protoValue.getValueCase() + " is not supported" ); }; } + private static PolyValue deserializeToPolyDocument( ProtoValue protoValue ) { + PolyDocument document = new PolyDocument(); + protoValue.getDocument().getEntriesList().stream() + .filter( e -> e.getKey().getValueCase() == ValueCase.STRING ) + .forEach( e -> document.put( + new PolyString( e.getKey().getString().getString() ), + deserializeProtoValue( e.getValue() ) + ) ); + return document; + } + + + private static PolyValue deserializeToPolyBlob( ProtoValue protoValue ) { + return PolyBlob.of( protoValue.getFile().getBinary().toByteArray() ); + } + + private static PolyValue deserializeToPolyList( ProtoValue protoValue ) { List values = protoValue.getList().getValuesList().stream() - .map( ProtoValueDeserializer::deserializeProtoValue ) + .map( PrismValueDeserializer::deserializeProtoValue ) .collect( Collectors.toList() ); return new PolyList<>( values ); } @@ -127,7 +150,7 @@ private static PolyBinary deserializeToPolyBinary( ProtoValue protoValue ) { private static PolyDate deserializeToPolyDate( ProtoValue protoValue ) { - return new PolyDate( protoValue.getDate().getDate() ); + return PolyDate.ofDays( (int) protoValue.getDate().getDate() ); } @@ -147,16 +170,16 @@ private static PolyString deserializeToPolyString( ProtoValue protoValue ) { private static PolyTime deserializeToPolyTime( ProtoValue protoValue ) { - return new PolyTime( protoValue.getTime().getValue() ); + return new PolyTime( protoValue.getTime().getTime() ); } - private static PolyTimestamp deserializeToPolyTimeStamp( ProtoValue protoValue ) { - return new PolyTimestamp( protoValue.getTimeStamp().getTimeStamp() ); + private static PolyTimestamp deserializeToPolyTimestamp( ProtoValue protoValue ) { + return new PolyTimestamp( protoValue.getTimestamp().getTimestamp() ); } - private static PolyNull deserializeToPolyNull( ProtoValue protoValue ) { + private static PolyNull deserializeToPolyNull() { return PolyNull.NULL; } @@ -168,10 +191,8 @@ private static PolyBigDecimal deserializeToPolyBigDecimal( ProtoValue protoValue private static BigDecimal deserializeToBigDecimal( ProtoValue protoValue ) { ProtoBigDecimal protoBigDecimal = protoValue.getBigDecimal(); - MathContext context = new MathContext( protoBigDecimal.getPrecision() ); byte[] unscaledValue = protoBigDecimal.getUnscaledValue().toByteArray(); - return new BigDecimal( new BigInteger( unscaledValue ), protoBigDecimal.getScale(), context ); + return new BigDecimal( new BigInteger( unscaledValue ), protoBigDecimal.getScale() ); } - } diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PropertyUtils.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PropertyUtils.java new file mode 100644 index 0000000000..df4c40e503 --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/PropertyUtils.java @@ -0,0 +1,27 @@ +/* + * 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.utils; + +import org.polypheny.db.catalog.Catalog; + +public class PropertyUtils { + + public static final boolean AUTOCOMMIT_DEFAULT = true; + public static final String DEFAULT_NAMESPACE_NAME = Catalog.DEFAULT_NAMESPACE_NAME; + public static final int DEFAULT_FETCH_SIZE = 100; + +} diff --git a/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/VersionUtils.java b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/VersionUtils.java new file mode 100644 index 0000000000..cf6f4131ab --- /dev/null +++ b/plugins/prism-interface/src/main/java/org/polypheny/db/prisminterface/utils/VersionUtils.java @@ -0,0 +1,52 @@ +/* + * 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.utils; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import lombok.Getter; + +public class VersionUtils { + + private static final String API_VERSION_PROPERTIES = "prism-api-version.properties"; + + @Getter + private static final int MAJOR_API_VERSION; + @Getter + private static final int MINOR_API_VERSION; + @Getter + private static final String API_VERSION_STRING; + + + static { + Properties properties = new Properties(); + try ( InputStream inputStream = VersionUtils.class.getClassLoader().getResourceAsStream( API_VERSION_PROPERTIES ) ) { + if ( inputStream != null ) { + properties.load( inputStream ); + API_VERSION_STRING = properties.getProperty( "version" ); + MAJOR_API_VERSION = Integer.parseInt( properties.getProperty( "majorVersion" ) ); + MINOR_API_VERSION = Integer.parseInt( properties.getProperty( "minorVersion" ) ); + } else { + throw new FileNotFoundException( "The prism api version properties could not be found." ); + } + } catch ( IOException e ) { + throw new RuntimeException( "Error loading API version properties", e ); + } + } +} diff --git a/plugins/prism-interface/src/test/java/org/polypheny/db/prisminterface/NamedValueProcessorTest.java b/plugins/prism-interface/src/test/java/org/polypheny/db/prisminterface/NamedValueProcessorTest.java new file mode 100644 index 0000000000..57a49f0ff9 --- /dev/null +++ b/plugins/prism-interface/src/test/java/org/polypheny/db/prisminterface/NamedValueProcessorTest.java @@ -0,0 +1,71 @@ +/* + * 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.assertThrows; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.polypheny.db.TestHelper; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; +import org.polypheny.db.type.entity.PolyString; +import org.polypheny.db.type.entity.PolyValue; + + +public class NamedValueProcessorTest { + + @BeforeAll + public static void init() { + // needed to launch polypheny + TestHelper.getInstance(); + } + + + @Test + public void replacePlaceholders__missingValue() { + final String statement = "select * from people where (first_name = :first_name or last_name= :last_name) and project = :project);"; + + final Map values = new HashMap<>(); + values.put( "first_name", PolyString.of( "tobias" ) ); + values.put( "last_name", PolyString.of( "hafner" ) ); + + NamedValueProcessor namedValueProcessor = new NamedValueProcessor( statement ); + assertThrows( GenericRuntimeException.class, () -> namedValueProcessor.transformValueMap( values ) ); + } + + + @Test + public void replacePlaceholders__checkValue() { + final String statement = "select * from people where (first_name = :first_name or last_name= :last_name) and project = :project);"; + + final Map values = new HashMap<>(); + values.put( "first_name", PolyString.of( "tobias" ) ); + values.put( "last_name", PolyString.of( "hafner" ) ); + values.put( "project", PolyString.of( "polypheny" ) ); + + NamedValueProcessor namedValueProcessor = new NamedValueProcessor( statement ); + List parameters = namedValueProcessor.transformValueMap( values ); + assertEquals( "tobias", parameters.get( 0 ).asString().getValue() ); + assertEquals( "hafner", parameters.get( 1 ).asString().getValue() ); + assertEquals( "polypheny", parameters.get( 2 ).asString().getValue() ); + } + +} diff --git a/plugins/proto-interface/src/test/java/org/polypheny/db/protointerface/PolyValueSerializationTest.java b/plugins/prism-interface/src/test/java/org/polypheny/db/prisminterface/PrismValueTest.java similarity index 54% rename from plugins/proto-interface/src/test/java/org/polypheny/db/protointerface/PolyValueSerializationTest.java rename to plugins/prism-interface/src/test/java/org/polypheny/db/prisminterface/PrismValueTest.java index a618165bf8..0851c11cf7 100644 --- a/plugins/proto-interface/src/test/java/org/polypheny/db/protointerface/PolyValueSerializationTest.java +++ b/plugins/prism-interface/src/test/java/org/polypheny/db/prisminterface/PrismValueTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 The Polypheny Project + * 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. @@ -14,119 +14,74 @@ * limitations under the License. */ -package org.polypheny.db.protointerface; +package org.polypheny.db.prisminterface; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.ByteArrayInputStream; import java.math.BigDecimal; import java.math.BigInteger; -import java.math.MathContext; import java.sql.Time; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.polypheny.db.TestHelper; -import org.polypheny.db.protointerface.proto.ProtoValue; -import org.polypheny.db.protointerface.proto.ProtoValue.ProtoValueType; -import org.polypheny.db.protointerface.proto.ProtoValue.ValueCase; -import org.polypheny.db.protointerface.utils.PolyValueSerializer; +import org.polypheny.db.prisminterface.utils.PolyValueSerializer; +import org.polypheny.db.prisminterface.utils.PrismValueDeserializer; import org.polypheny.db.type.PolyType; -import org.polypheny.db.type.entity.PolyBigDecimal; import org.polypheny.db.type.entity.PolyBinary; import org.polypheny.db.type.entity.PolyBoolean; -import org.polypheny.db.type.entity.PolyDate; -import org.polypheny.db.type.entity.PolyDouble; -import org.polypheny.db.type.entity.PolyFile; -import org.polypheny.db.type.entity.PolyFloat; -import org.polypheny.db.type.entity.PolyInteger; import org.polypheny.db.type.entity.PolyList; -import org.polypheny.db.type.entity.PolyLong; import org.polypheny.db.type.entity.PolyNull; -import org.polypheny.db.type.entity.PolyStream; import org.polypheny.db.type.entity.PolyString; -import org.polypheny.db.type.entity.PolySymbol; -import org.polypheny.db.type.entity.PolyTime; -import org.polypheny.db.type.entity.PolyTimeStamp; -import org.polypheny.db.type.entity.PolyUserDefinedValue; import org.polypheny.db.type.entity.PolyValue; - -public class PolyValueSerializationTest { - - @BeforeClass +import org.polypheny.db.type.entity.document.PolyDocument; +import org.polypheny.db.type.entity.numerical.PolyBigDecimal; +import org.polypheny.db.type.entity.numerical.PolyDouble; +import org.polypheny.db.type.entity.numerical.PolyFloat; +import org.polypheny.db.type.entity.numerical.PolyInteger; +import org.polypheny.db.type.entity.numerical.PolyLong; +import org.polypheny.db.type.entity.temporal.PolyDate; +import org.polypheny.db.type.entity.temporal.PolyTime; +import org.polypheny.db.type.entity.temporal.PolyTimestamp; +import org.polypheny.prism.ProtoValue; +import org.polypheny.prism.ProtoValue.ValueCase; + +public class PrismValueTest { + + @BeforeAll public static void init() { // needed to launch polypheny TestHelper.getInstance(); } - private enum TestEnum { - UNTESTED - } - - - public static PolyUserDefinedValue buildTestUdt() { - Map template = new HashMap<>(); - template.put( "binaryField", PolyType.BINARY ); - template.put( "booleanField", PolyType.BOOLEAN ); - template.put( "dateField", PolyType.DATE ); - template.put( "doubleField", PolyType.DOUBLE ); - template.put( "fileField", PolyType.FILE ); - template.put( "floatField", PolyType.FLOAT ); - template.put( "integerField", PolyType.INTEGER ); - template.put( "arrayField", PolyType.ARRAY ); - template.put( "bigIntField", PolyType.BIGINT ); - template.put( "nullField", PolyType.NULL ); - template.put( "stringField", PolyType.VARCHAR ); - template.put( "symbolField", PolyType.SYMBOL ); - template.put( "timeField", PolyType.TIME ); - template.put( "timestampField", PolyType.TIMESTAMP ); - Map value = new HashMap<>(); - value.put( "binaryField", PolyBinary.of( new byte[]{ 0x01, 0x02, 0x03 } ) ); - value.put( "booleanField", new PolyBoolean( true ) ); - value.put( "dateField", new PolyDate( 119581 ) ); - value.put( "doubleField", new PolyDouble( 123.456 ) ); - value.put( "fileField", new PolyFile( new byte[]{ 0x0A, 0x1B, 0x2C, 0x3D, 0x4E } ) ); - value.put( "floatField", new PolyFloat( 45.67f ) ); - value.put( "integerField", new PolyInteger( 1234 ) ); - value.put( "arrayField", new PolyList( - new PolyInteger( 1 ), - new PolyInteger( 2 ), - new PolyInteger( 3 ), - new PolyInteger( 4 ) - ) - ); - value.put( "bigIntField", new PolyLong( 1234567890L ) ); - value.put( "nullField", new PolyNull() ); - value.put( "stringField", new PolyString( "sample string" ) ); - value.put( "symbolField", new PolySymbol( TestEnum.UNTESTED ) ); - value.put( "timeField", PolyTime.of( new Time( 1691879380700L ) ) ); - value.put( "timestampField", new PolyTimeStamp( 1691879380700L ) ); - return new PolyUserDefinedValue( template, value ); - } - @Test - public void polyBigDecimalSeriaizationTest() { + public void polyBigDecimalSerializationTest() { BigDecimal expectedValue = new BigDecimal( 1691879380700L ); PolyBigDecimal expected = new PolyBigDecimal( expectedValue ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); - MathContext context = new MathContext( protoValue.getBigDecimal().getPrecision() ); int scale = protoValue.getBigDecimal().getScale(); BigInteger value = new BigInteger( protoValue.getBigDecimal().getUnscaledValue().toByteArray() ); - BigDecimal result = new BigDecimal( value, scale, context ); + BigDecimal result = new BigDecimal( value, scale ); assertEquals( expectedValue, result ); } @Test public void polyBinarySerializationTest() { - PolyBinary expected = PolyBinary.of( new byte[]{ 0x01, 0x02, 0x03 } ); + byte[] data = new byte[]{ 0x01, 0x02, 0x03 }; + PolyBinary expected = PolyBinary.of( data ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); - assertArrayEquals( new byte[]{ 0x01, 0x02, 0x03 }, protoValue.getBinary().toByteArray() ); + + assertArrayEquals( data, protoValue.getBinary().getBinary().toByteArray() ); + assertEquals( ValueCase.BINARY, protoValue.getValueCase() ); + + PolyValue deserialized = PrismValueDeserializer.deserializeProtoValue( protoValue ); + + assertEquals( PolyType.BINARY, deserialized.type ); + assertArrayEquals( data, deserialized.asBinary().getValue() ); } @@ -140,8 +95,8 @@ public void polyBooleanSerializationTest() { @Test public void polyDateSerializationTest() { - long daysSinceEpoch = 119581; - PolyDate expected = new PolyDate( daysSinceEpoch ); + int daysSinceEpoch = 119581; + PolyDate expected = PolyDate.ofDays( daysSinceEpoch ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( daysSinceEpoch, protoValue.getDate().getDate() ); } @@ -155,14 +110,6 @@ public void polyDoubleSerializationTest() { } - @Test - public void polyFileSerializationTest() { - PolyFile expected = new PolyFile( new byte[]{ 0x0A, 0x1B, 0x2C, 0x3D, 0x4E } ); - ProtoValue protoValue = PolyValueSerializer.serialize( expected ); - assertEquals( new byte[]{ 0x0A, 0x1B, 0x2C, 0x3D, 0x4E }, protoValue.getFile().getBytes().toByteArray() ); - } - - @Test public void polyFloatSerializationTest() { PolyFloat expected = new PolyFloat( 45.67f ); @@ -178,9 +125,10 @@ public void polyIntegerSerializationTest() { assertEquals( 1234, protoValue.getInteger().getInteger() ); } + @Test public void polyListSerializationTest() { - PolyList expected = new PolyList( + PolyList expected = new PolyList<>( new PolyInteger( 1 ), new PolyInteger( 2 ), new PolyInteger( 3 ), @@ -209,27 +157,64 @@ public void polyStringSerializationTest() { assertEquals( "sample string", protoValue.getString().getString() ); } + @Test public void polyTimeSerializationTest() { - PolyTime expected = PolyTime.of( new Time( 1691879380700L ) ); + long time = (3600 * 9 + 60 * 42 + 11) * 1000; + PolyTime expected = PolyTime.of( time ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); - assertEquals( 1691879380700L, protoValue.getTime().getValue() ); + assertEquals( time, protoValue.getTime().getTime() ); } @Test public void polyTimeStampSerializationTest() { - PolyTimeStamp expected = new PolyTimeStamp( 1691879380700L ); // Assuming ISO-8601 format + PolyTimestamp expected = new PolyTimestamp( 1691879380700L ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); - assertEquals( 1691879380700L, protoValue.getTimeStamp().getTimeStamp() ); + assertEquals( 1691879380700L, protoValue.getTimestamp().getTimestamp() ); + } + + + @Test + public void polyDocumentSerializationTest() { + PolyDocument document = new PolyDocument(); + document.put( new PolyString( "1st" ), new PolyBoolean( false ) ); + document.put( new PolyString( "2nd" ), PolyBinary.of( new byte[]{ 0, 1, 2, 3, 4 } ) ); + document.put( new PolyString( "3rd" ), new PolyDate( 47952743435L ) ); + PolyValueSerializer.serialize( document ); + + } + + + @Test + public void polyFileSerializationTest() { + + } + + + @Test + public void polyAudioSerializationTest() { + + } + + + @Test + public void polyImageSerializationTest() { + } + + @Test + public void polyVideoSerializationTest() { + + } + + @Test public void polyBigDecimalSerializationCorrectTypeTest() { PolyBigDecimal expected = new PolyBigDecimal( new BigDecimal( 1691879380700L ) ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.BIG_DECIMAL, protoValue.getValueCase() ); - assertEquals( ProtoValueType.DECIMAL, protoValue.getType() ); } @@ -238,7 +223,6 @@ public void polyBinarySerializationCorrectTypeTest() { PolyBinary expected = PolyBinary.of( new byte[]{ 0x01, 0x02, 0x03 } ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.BINARY, protoValue.getValueCase() ); - assertEquals( ProtoValueType.BINARY, protoValue.getType() ); } @@ -247,7 +231,6 @@ public void polyBooleanSerializationCorrectTypeTest() { PolyBoolean expected = new PolyBoolean( true ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.BOOLEAN, protoValue.getValueCase() ); - assertEquals( ProtoValueType.BOOLEAN, protoValue.getType() ); } @@ -257,7 +240,6 @@ public void polyDateSerializationCorrectTypeTest() { PolyDate expected = new PolyDate( daysSinceEpoch ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.DATE, protoValue.getValueCase() ); - assertEquals( ProtoValueType.DATE, protoValue.getType() ); } @@ -266,16 +248,6 @@ public void polyDoubleSerializationCorrectTypeTest() { PolyDouble expected = new PolyDouble( 123.456 ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.DOUBLE, protoValue.getValueCase() ); - assertEquals( ProtoValueType.DOUBLE, protoValue.getType() ); - } - - - @Test - public void polyFileSerializationCorrectTypeTest() { - PolyFile expected = new PolyFile( new byte[]{ 0x0A, 0x1B, 0x2C, 0x3D, 0x4E } ); - ProtoValue protoValue = PolyValueSerializer.serialize( expected ); - assertEquals( ValueCase.FILE, protoValue.getValueCase() ); - assertEquals( ProtoValueType.FILE, protoValue.getType() ); } @@ -284,7 +256,6 @@ public void polyFloatSerializationCorrectTypeTest() { PolyFloat expected = new PolyFloat( 45.67f ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.FLOAT, protoValue.getValueCase() ); - assertEquals( ProtoValueType.FLOAT, protoValue.getType() ); } @@ -293,12 +264,6 @@ public void polyIntegerSerializationCorrectTypeTest() { PolyInteger expected = new PolyInteger( 1234 ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.INTEGER, protoValue.getValueCase() ); - assertEquals( ProtoValueType.INTEGER, protoValue.getType() ); - } - - - @Test - public void polyIntervalSerializationCorrectTypeTest() { } @@ -312,7 +277,6 @@ public void polyListSerializationCorrectTypeTest() { ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.LIST, protoValue.getValueCase() ); - assertEquals( ProtoValueType.ARRAY, protoValue.getType() ); } @@ -321,7 +285,6 @@ public void polyLongSerializationCorrectTypeTest() { PolyLong expected = new PolyLong( 1234567890L ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.LONG, protoValue.getValueCase() ); - assertEquals( ProtoValueType.BIGINT, protoValue.getType() ); } @@ -330,17 +293,6 @@ public void polyNullSerializationCorrectTypeTest() { PolyNull expected = new PolyNull(); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.NULL, protoValue.getValueCase() ); - assertEquals( ProtoValueType.NULL, protoValue.getType() ); - } - - - @Test - public void polyStreamSerializationCorrectTypeTest() { - ByteArrayInputStream stream = new ByteArrayInputStream( "test data".getBytes() ); - PolyStream expected = new PolyStream( stream ); - ProtoValue protoValue = PolyValueSerializer.serialize( expected ); - assertEquals( ProtoValueType.FILE, protoValue.getType() ); - } @@ -349,15 +301,6 @@ public void polyStringSerializationCorrectTypeTest() { PolyString expected = new PolyString( "sample string" ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.STRING, protoValue.getValueCase() ); - assertEquals( ProtoValueType.VARCHAR, protoValue.getType() ); - } - - - @Test - public void polySymbolSerializationCorrectTypeTest() { - PolySymbol expected = new PolySymbol( TestEnum.UNTESTED ); - ProtoValue protoValue = PolyValueSerializer.serialize( expected ); - assertEquals( ProtoValueType.SYMBOL, protoValue.getType() ); } @@ -366,25 +309,15 @@ public void polyTimeSerializationCorrectTypeTest() { PolyTime expected = PolyTime.of( new Time( 1691879380700L ) ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); assertEquals( ValueCase.TIME, protoValue.getValueCase() ); - assertEquals( ProtoValueType.TIME, protoValue.getType() ); } @Test public void polyTimeStampSerializationCorrectTypeTest() { - PolyTimeStamp expected = new PolyTimeStamp( 1691879380700L ); + PolyTimestamp expected = new PolyTimestamp( 1691879380700L ); ProtoValue protoValue = PolyValueSerializer.serialize( expected ); - assertEquals( ValueCase.TIME_STAMP, protoValue.getValueCase() ); - assertEquals( ProtoValueType.TIMESTAMP, protoValue.getType() ); + assertEquals( ValueCase.TIMESTAMP, protoValue.getValueCase() ); } - @Test - public void polyUserDefinedValueSerializationCorrectTypeTest() { - PolyUserDefinedValue expected = buildTestUdt(); - ProtoValue protoValue = PolyValueSerializer.serialize( expected ); - assertEquals( ValueCase.USER_DEFINED_TYPE, protoValue.getValueCase() ); - assertEquals( ProtoValueType.USER_DEFINED_TYPE, protoValue.getType() ); - } - } diff --git a/plugins/proto-interface/build.gradle b/plugins/proto-interface/build.gradle deleted file mode 100644 index 6cd70b1441..0000000000 --- a/plugins/proto-interface/build.gradle +++ /dev/null @@ -1,137 +0,0 @@ -plugins { - id 'java' - id "com.google.protobuf" version "0.9.3" -} - -apply plugin: "com.google.protobuf" - -group "org.polypheny" - -configurations { - tests { - extendsFrom testRuntimeOnly - } -} - -repositories { - mavenCentral() -} - -dependencies { - - compileOnly project(path: ':core') - compileOnly project(path: ':dbms') - compileOnly project(path: ':plugins:sql-language') - compileOnly project(path: ':plugins:mql-language') - testImplementation project(path: ':plugins:mql-language') - testImplementation project(path: ":dbms", configuration: "test") - testImplementation project(path: ':dbms') - testImplementation project(path: ":core", configuration: "tests") - testImplementation project(path: ':core') - - compileOnly project(":monitoring") - // https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java - implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.21.12' - - // https://mvnrepository.com/artifact/io.grpc/grpc-all - implementation group: 'io.grpc', name: 'grpc-all', version: '1.55.1' - - implementation 'javax.annotation:javax.annotation-api:1.3.2' -} - -def generatedDirName = "${project.buildDir}/generated" - -// compile protos on build -compileJava.dependsOn(generateProto) - - -protobuf { - protoc { - artifact = "com.google.protobuf:protoc:3.21.12" - } - plugins { - - grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.55.1' - } - } - generateProtoTasks { - all()*.plugins { - grpc {} - } - } - generatedFilesBaseDir = "${generatedDirName}/proto" -} - -sourceSets { - main { - proto { - srcDir "${protobuf.generatedFilesBaseDir}/proto/main/grpc" - srcDir "${protobuf.generatedFilesBaseDir}/proto/main/java" - } - java { - srcDirs = ["src/main/java", "build/generated-sources", 'build/generated/source/proto/main/grpc', 'build/generated/source/proto/main/java'] - } - resources { - srcDirs = ["src/main/resources"] - } - output.resourcesDir = file(project.buildDir.absolutePath + "/classes") - } - test { - java { - srcDirs = ["src/test/java"] - } - resources { - srcDirs = ["src/test/resources", "src/main/resources"] - } - output.resourcesDir = file(project.buildDir.absolutePath + "/test-classes") - } -} - -delombok { - dependsOn(":monitoring:processResources") -} - -compileJava { - dependsOn(":core:processResources") - dependsOn(":monitoring:processResources") -} - -extractIncludeProto { - dependsOn(":core:compileJava") - dependsOn(":monitoring:compileJava") - dependsOn(":plugins:mql-language:compileJava") - dependsOn(":webui:compileJava") - dependsOn(":plugins:sql-language:compileJava") -} - -/** - * Tests - */ -test { - maxHeapSize = "2g" // Increase heap size (default is 512MB) -} - -/** - * JARs - */ -jar { - manifest { - attributes "Manifest-Version": "1.0" - attributes "Copyright": "The Polypheny Project (polypheny.org)" - attributes "Version": "$project.version" - } -} - -java { - withJavadocJar() - withSourcesJar() -} - - -/** - * LICENSEE - */ -licensee { - allow('Apache-2.0') -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/ClientManager.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/ClientManager.java deleted file mode 100644 index e4f5753d06..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/ClientManager.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface; - -import java.util.List; -import java.util.Optional; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.polypheny.db.catalog.Catalog; -import org.polypheny.db.catalog.entity.LogicalUser; -import org.polypheny.db.catalog.entity.logical.LogicalNamespace; -import org.polypheny.db.iface.AuthenticationException; -import org.polypheny.db.iface.Authenticator; -import org.polypheny.db.protointerface.proto.ConnectionRequest; -import org.polypheny.db.protointerface.utils.PropertyUtils; -import org.polypheny.db.transaction.Transaction; -import org.polypheny.db.transaction.TransactionException; -import org.polypheny.db.transaction.TransactionManager; - -@Slf4j -public class ClientManager { - - private static final long HEARTBEAT_TOLERANCE = 2000; - @Getter - private long heartbeatInterval; - - private ConcurrentHashMap openConnections; - private final Authenticator authenticator; - private final TransactionManager transactionManager; - private Timer cleanupTimer; - - - public ClientManager( PIPlugin.ProtoInterface protoInterface ) { - this.openConnections = new ConcurrentHashMap<>(); - this.authenticator = protoInterface.getAuthenticator(); - this.transactionManager = protoInterface.getTransactionManager(); - if ( protoInterface.isRequiresHeartbeat() ) { - this.heartbeatInterval = protoInterface.getHeartbeatInterval(); - this.cleanupTimer = new Timer(); - cleanupTimer.schedule( createNewCleanupTask(), 0, heartbeatInterval + HEARTBEAT_TOLERANCE ); - } - this.heartbeatInterval = 0; - } - - - public void unregisterConnection( PIClient client ) { - synchronized ( client ) { - client.prepareForDisposal(); - openConnections.remove( client.getClientUUID() ); - } - } - - - public void registerConnection( ConnectionRequest connectionRequest ) throws AuthenticationException, TransactionException, PIServiceException { - if ( log.isTraceEnabled() ) { - log.trace( "User {} tries to establish connection via proto interface.", connectionRequest.getClientUuid() ); - } - // reject already connected user - if ( isConnected( connectionRequest.getClientUuid() ) ) { - throw new PIServiceException( "A user with uid " + connectionRequest.getClientUuid() + " is already connected." ); - } - String username = connectionRequest.hasUsername() ? connectionRequest.getUsername() : Catalog.USER_NAME; - String password = connectionRequest.hasPassword() ? connectionRequest.getPassword() : null; - LogicalUser user = authenticator.authenticate( username, password ); - Transaction transaction = transactionManager.startTransaction( user.id, false, "proto-interface" ); - transaction.commit(); - LogicalNamespace namespace = getNamespaceOrDefault( connectionRequest ); - boolean isAutocommit = getAutocommitOrDefault( connectionRequest ); - PIClient client = new PIClient( - connectionRequest.getClientUuid(), - user, - transactionManager, - namespace, - isAutocommit - ); - openConnections.put( connectionRequest.getClientUuid(), client ); - if ( log.isTraceEnabled() ) { - log.trace( "proto-interface established connection to user {}.", connectionRequest.getClientUuid() ); - } - } - - - private LogicalNamespace getNamespaceOrDefault( ConnectionRequest connectionRequest ) { - String namespaceName = PropertyUtils.DEFAULT_NAMESPACE_NAME; - if ( connectionRequest.hasConnectionProperties() && connectionRequest.getConnectionProperties().hasNamespaceName() ) { - namespaceName = connectionRequest.getConnectionProperties().getNamespaceName(); - } - Optional optionalNamespace = Catalog.getInstance().getSnapshot().getNamespace( namespaceName ); - if ( optionalNamespace.isEmpty() ) { - throw new PIServiceException( "Getting namespace " + namespaceName + " failed." ); - } - - return optionalNamespace.get(); - } - - - private boolean getAutocommitOrDefault( ConnectionRequest connectionRequest ) { - if ( connectionRequest.hasConnectionProperties() && connectionRequest.getConnectionProperties().hasIsAutoCommit() ) { - return connectionRequest.getConnectionProperties().getIsAutoCommit(); - } - return PropertyUtils.AUTOCOMMIT_DEFAULT; - } - - - public PIClient getClient( String clientUUID ) throws PIServiceException { - if ( !openConnections.containsKey( clientUUID ) ) { - throw new PIServiceException( "Client not registered! Has the server been restarted in the meantime?" ); - } - return openConnections.get( clientUUID ); - } - - - private TimerTask createNewCleanupTask() { - Runnable runnable = this::unregisterInactiveClients; - return new TimerTask() { - @Override - public void run() { - runnable.run(); - } - }; - } - - - private void unregisterInactiveClients() { - List inactiveClients = openConnections.values().stream() - .filter( c -> !c.returnAndResetIsActive() ).collect( Collectors.toList() ); - inactiveClients.forEach( this::unregisterConnection ); - } - - - private boolean isConnected( String clientUUID ) { - return openConnections.containsKey( clientUUID ); - } - -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/ClientMetaInterceptor.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/ClientMetaInterceptor.java deleted file mode 100644 index c0ff2ba4e9..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/ClientMetaInterceptor.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface; - -import io.grpc.*; - -public class ClientMetaInterceptor implements ServerInterceptor { - - public static final Context.Key CLIENT = Context.key( "protoInterfaceClient" ); - - - @Override - public ServerCall.Listener interceptCall( ServerCall call, Metadata headers, ServerCallHandler next ) { - final String clientUUID = headers.get( Metadata.Key.of( "clientUUID", Metadata.ASCII_STRING_MARSHALLER ) ); - Context context = Context.current().withValue( CLIENT, clientUUID ); - return Contexts.interceptCall( context, call, headers, next ); - } - -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/ExceptionHandler.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/ExceptionHandler.java deleted file mode 100644 index 8b72a4e1f2..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/ExceptionHandler.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface; - -import io.grpc.ForwardingServerCallListener; -import io.grpc.Metadata; -import io.grpc.ServerCall; -import io.grpc.ServerCallHandler; -import io.grpc.ServerInterceptor; -import io.grpc.Status; -import io.grpc.protobuf.ProtoUtils; -import java.util.Arrays; -import java.util.Optional; -import lombok.extern.slf4j.Slf4j; -import org.apache.calcite.avatica.remote.AvaticaRuntimeException; -import org.polypheny.db.protointerface.proto.ErrorDetails; - -@Slf4j -public class ExceptionHandler implements ServerInterceptor { - - private static final Metadata.Key ERROR_DETAILS_KEY = ProtoUtils.keyForProto( ErrorDetails.getDefaultInstance() ); - - - @Override - public ServerCall.Listener interceptCall( - ServerCall call, Metadata metadata, - ServerCallHandler nextHandler ) { - ServerCall.Listener listener = nextHandler.startCall( call, metadata ); - return new ExceptionHandlingServerCallListener<>( listener, call, metadata ); - } - - - private class ExceptionHandlingServerCallListener extends ForwardingServerCallListener.SimpleForwardingServerCallListener { - - private final ServerCall serverCall; - private final Metadata metadata; - - - ExceptionHandlingServerCallListener( ServerCall.Listener listener, ServerCall serverCall, Metadata metadata ) { - super( listener ); - this.serverCall = serverCall; - this.metadata = metadata; - } - - - @Override - public void onHalfClose() { - try { - super.onHalfClose(); - } catch ( PIServiceException e ) { - handleProtoInterfaceServiceException( e, serverCall, metadata ); - throw e; - } catch ( AvaticaRuntimeException e ) { - handleAvaticaRuntimeException( e, serverCall, metadata ); - throw e; - } catch ( Exception e ) { - handleGenericExceptions( e, serverCall, metadata ); - throw e; - } - } - - - private void handleGenericExceptions( Exception exception, ServerCall serverCall, Metadata metadata ) { - //serverCall.close(Status.fromThrowable(exception), metadata); - ErrorDetails.Builder errorDetailsBuilder = ErrorDetails.newBuilder(); - if ( exception.getMessage() == null ) { - errorDetailsBuilder.setMessage( "No information provided. Returning stacktrace instead: " + Arrays.toString( exception.getStackTrace() ) ); - } else { - errorDetailsBuilder.setMessage( exception.getMessage() ); - } - metadata.put( ERROR_DETAILS_KEY, errorDetailsBuilder.build() ); - serverCall.close( Status.INTERNAL.withDescription( exception.getMessage() ), metadata ); - } - - - private void handleProtoInterfaceServiceException( PIServiceException exception, ServerCall serverCall, Metadata metadata ) { - ErrorDetails errorDetails = exception.getProtoErrorDetails(); - metadata.put( ERROR_DETAILS_KEY, errorDetails ); - serverCall.close( Status.INTERNAL.withDescription( exception.getMessage() ), metadata ); - } - - - private void handleAvaticaRuntimeException( AvaticaRuntimeException avaticaRuntimeException, ServerCall serverCall, Metadata metadata ) { - //serverCall.close(Status.fromThrowable(avaticaRuntimeException), metadata); - ErrorDetails.Builder errorDetailsBuilder = ErrorDetails.newBuilder(); - errorDetailsBuilder.setErrorCode( avaticaRuntimeException.getErrorCode() ); - errorDetailsBuilder.setState( avaticaRuntimeException.getSqlState() ); - Optional.ofNullable( avaticaRuntimeException.getErrorMessage() ).ifPresent( errorDetailsBuilder::setMessage ); - metadata.put( ERROR_DETAILS_KEY, errorDetailsBuilder.build() ); - serverCall.close( Status.INTERNAL.withDescription( avaticaRuntimeException.getErrorMessage() ), metadata ); - } - - } - -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIPlugin.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIPlugin.java deleted file mode 100644 index 16f48d0d93..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIPlugin.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface; - -import com.google.common.collect.ImmutableList; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.pf4j.Extension; -import org.polypheny.db.iface.Authenticator; -import org.polypheny.db.iface.QueryInterface; -import org.polypheny.db.iface.QueryInterfaceManager; -import org.polypheny.db.plugins.PluginContext; -import org.polypheny.db.plugins.PolyPlugin; -import org.polypheny.db.transaction.TransactionManager; -import org.polypheny.db.util.Util; - -public class PIPlugin extends PolyPlugin { - - public PIPlugin( PluginContext context ) { - super( context ); - } - - - @Override - public void afterCatalogInit() { - Map settings = new HashMap<>(); - settings.put( "port", "20590" ); - settings.put( "requires heartbeat", "false" ); - settings.put( "heartbeat interval", "0" ); - QueryInterfaceManager.addInterfaceType( "proto-interface", ProtoInterface.class, settings ); - } - - - public void stop() { - QueryInterfaceManager.removeInterfaceType( ProtoInterface.class ); - } - - - @Slf4j - @Extension - public static class ProtoInterface extends QueryInterface implements PropertyChangeListener { - - public static final String INTERFACE_NAME = "Proto Interface"; - public static final String INTERFACE_DESCRIPTION = "proto-interface query interface supporting the PolySQL dialect."; - public static final List AVAILABLE_SETTINGS = ImmutableList.of( - new QueryInterfaceSettingInteger( "port", false, true, false, 20590 ), - new QueryInterfaceSettingBoolean( "requires heartbeat", false, true, false, false ), - new QueryInterfaceSettingLong( "heartbeat interval", false, true, false, 300000L ) - ); - @Getter - private final int port; - @Getter - private final boolean requiresHeartbeat; - @Getter - private final long heartbeatInterval; - @Getter - private TransactionManager transactionManager; - @Getter - private Authenticator authenticator; - @Getter - private ClientManager clientManager; - private PIServer protoInterfaceServer; - - - public ProtoInterface( TransactionManager transactionManager, Authenticator authenticator, long queryInterfaceId, String uniqueName, Map settings ) { - super( transactionManager, authenticator, queryInterfaceId, uniqueName, settings, true, true ); - this.authenticator = authenticator; - this.transactionManager = transactionManager; - this.port = Integer.parseInt( settings.get( "port" ) ); - if ( !Util.checkIfPortIsAvailable( port ) ) { - // Port is already in use - throw new RuntimeException( "Unable to start " + INTERFACE_NAME + " on port " + port + "! The port is already in use." ); - } - this.requiresHeartbeat = Boolean.getBoolean( settings.get( "requires heartbeat" ) ); - this.heartbeatInterval = Long.parseLong( settings.get( "heartbeat interval" ) ); - } - - - @Override - public List getAvailableSettings() { - return AVAILABLE_SETTINGS; - } - - - @Override - public void shutdown() { - try { - protoInterfaceServer.shutdown(); - } catch ( InterruptedException e ) { - throw new RuntimeException( e ); - } - } - - - @Override - public String getInterfaceType() { - return INTERFACE_NAME; - } - - - @Override - protected void reloadSettings( List updatedSettings ) { - } - - - @Override - public void propertyChange( PropertyChangeEvent evt ) { - - } - - - @Override - public void languageChange() { - - } - - - @Override - public void run() { - clientManager = new ClientManager( this ); - protoInterfaceServer = new PIServer( this ); - // protoInterfaceServer = new PIServer( port, protoInterfaceService, clientManager ); - try { - protoInterfaceServer.start(); - } catch ( IOException e ) { - log.error( "Proto interface server could not be started: {}", e.getMessage() ); - } - } - - } - -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIServer.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIServer.java deleted file mode 100644 index 294bf415b9..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIServer.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface; - -import io.grpc.Grpc; -import io.grpc.InsecureServerCredentials; -import io.grpc.Server; -import io.grpc.ServerBuilder; -import lombok.extern.slf4j.Slf4j; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -@Slf4j -public class PIServer { - - private final Server server; - private final int port; - - - public PIServer( PIPlugin.ProtoInterface protoInterface ) { - this.port = protoInterface.getPort(); - ServerBuilder serverBuilder = Grpc.newServerBuilderForPort( port, InsecureServerCredentials.create() ); - server = serverBuilder - .addService( new PIService( protoInterface.getClientManager() ) ) - .intercept( new ClientMetaInterceptor() ) - .intercept( new ExceptionHandler() ) - .build(); - } - - - public void start() throws IOException { - server.start(); - if ( log.isTraceEnabled() ) { - log.trace( "proto-interface server started on port {}", port ); - } - // used to handle unexpected shutdown of the JVM - Runtime.getRuntime().addShutdownHook( getShutdownHook() ); - } - - - public void shutdown() throws InterruptedException { - if ( server == null ) { - return; - } - if ( log.isTraceEnabled() ) { - log.trace( "proto-interface server shutdown requested" ); - } - server.shutdown().awaitTermination( 60, TimeUnit.SECONDS ); - } - - - private Thread getShutdownHook() { - return new Thread( () -> { - // Use stderr here since the logger may have been reset by its JVM shutdown hook. - System.err.println( "shutting down gRPC server since JVM is shutting down" ); - try { - PIServer.this.shutdown(); - } catch ( InterruptedException e ) { - e.printStackTrace( System.err ); - } - System.err.println( "server shut down" ); - } ); - } - -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIService.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIService.java deleted file mode 100644 index c8cada7203..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/PIService.java +++ /dev/null @@ -1,477 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface; - -import io.grpc.stub.StreamObserver; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import lombok.SneakyThrows; -import org.polypheny.db.algebra.constant.FunctionCategory; -import org.polypheny.db.catalog.Catalog; -import org.polypheny.db.catalog.entity.logical.LogicalNamespace; -import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.protointerface.proto.ClientInfoProperties; -import org.polypheny.db.protointerface.proto.ClientInfoPropertiesRequest; -import org.polypheny.db.protointerface.proto.ClientInfoPropertiesResponse; -import org.polypheny.db.protointerface.proto.CloseStatementRequest; -import org.polypheny.db.protointerface.proto.CloseStatementResponse; -import org.polypheny.db.protointerface.proto.CommitRequest; -import org.polypheny.db.protointerface.proto.CommitResponse; -import org.polypheny.db.protointerface.proto.ConnectionCheckRequest; -import org.polypheny.db.protointerface.proto.ConnectionCheckResponse; -import org.polypheny.db.protointerface.proto.ConnectionProperties; -import org.polypheny.db.protointerface.proto.ConnectionPropertiesUpdateRequest; -import org.polypheny.db.protointerface.proto.ConnectionPropertiesUpdateResponse; -import org.polypheny.db.protointerface.proto.ConnectionRequest; -import org.polypheny.db.protointerface.proto.ConnectionResponse; -import org.polypheny.db.protointerface.proto.DatabasesRequest; -import org.polypheny.db.protointerface.proto.DatabasesResponse; -import org.polypheny.db.protointerface.proto.DbmsVersionRequest; -import org.polypheny.db.protointerface.proto.DbmsVersionResponse; -import org.polypheny.db.protointerface.proto.DisconnectRequest; -import org.polypheny.db.protointerface.proto.DisconnectionResponse; -import org.polypheny.db.protointerface.proto.EntitiesRequest; -import org.polypheny.db.protointerface.proto.EntitiesResponse; -import org.polypheny.db.protointerface.proto.ExecuteIndexedStatementBatchRequest; -import org.polypheny.db.protointerface.proto.ExecuteIndexedStatementRequest; -import org.polypheny.db.protointerface.proto.ExecuteNamedStatementRequest; -import org.polypheny.db.protointerface.proto.ExecuteUnparameterizedStatementBatchRequest; -import org.polypheny.db.protointerface.proto.ExecuteUnparameterizedStatementRequest; -import org.polypheny.db.protointerface.proto.FetchRequest; -import org.polypheny.db.protointerface.proto.Frame; -import org.polypheny.db.protointerface.proto.FunctionsRequest; -import org.polypheny.db.protointerface.proto.FunctionsResponse; -import org.polypheny.db.protointerface.proto.LanguageRequest; -import org.polypheny.db.protointerface.proto.LanguageResponse; -import org.polypheny.db.protointerface.proto.MetaStringResponse; -import org.polypheny.db.protointerface.proto.Namespace; -import org.polypheny.db.protointerface.proto.NamespaceRequest; -import org.polypheny.db.protointerface.proto.NamespacesRequest; -import org.polypheny.db.protointerface.proto.NamespacesResponse; -import org.polypheny.db.protointerface.proto.PrepareStatementRequest; -import org.polypheny.db.protointerface.proto.PreparedStatementSignature; -import org.polypheny.db.protointerface.proto.ProceduresRequest; -import org.polypheny.db.protointerface.proto.ProceduresResponse; -import org.polypheny.db.protointerface.proto.ProtoInterfaceGrpc; -import org.polypheny.db.protointerface.proto.RollbackRequest; -import org.polypheny.db.protointerface.proto.RollbackResponse; -import org.polypheny.db.protointerface.proto.SqlKeywordsRequest; -import org.polypheny.db.protointerface.proto.SqlNumericFunctionsRequest; -import org.polypheny.db.protointerface.proto.SqlStringFunctionsRequest; -import org.polypheny.db.protointerface.proto.SqlSystemFunctionsRequest; -import org.polypheny.db.protointerface.proto.SqlTimeDateFunctionsRequest; -import org.polypheny.db.protointerface.proto.StatementBatchResponse; -import org.polypheny.db.protointerface.proto.StatementResponse; -import org.polypheny.db.protointerface.proto.StatementResult; -import org.polypheny.db.protointerface.proto.TableTypesRequest; -import org.polypheny.db.protointerface.proto.TableTypesResponse; -import org.polypheny.db.protointerface.proto.TypesRequest; -import org.polypheny.db.protointerface.proto.TypesResponse; -import org.polypheny.db.protointerface.statementProcessing.StatementProcessor; -import org.polypheny.db.protointerface.statements.PIPreparedIndexedStatement; -import org.polypheny.db.protointerface.statements.PIPreparedNamedStatement; -import org.polypheny.db.protointerface.statements.PIStatement; -import org.polypheny.db.protointerface.statements.PIUnparameterizedStatement; -import org.polypheny.db.protointerface.statements.PIUnparameterizedStatementBatch; -import org.polypheny.db.protointerface.utils.PropertyUtils; -import org.polypheny.db.protointerface.utils.ProtoUtils; -import org.polypheny.db.protointerface.utils.ProtoValueDeserializer; -import org.polypheny.db.sql.language.SqlJdbcFunctionCall; -import org.polypheny.db.type.entity.PolyValue; - -public class PIService extends ProtoInterfaceGrpc.ProtoInterfaceImplBase { - - private static final int majorApiVersion = 2; - private static final int minorApiVersion = 0; - private ClientManager clientManager; - - - public PIService( ClientManager clientManager ) { - this.clientManager = clientManager; - } - - - @SneakyThrows - @Override - public void connect( ConnectionRequest request, StreamObserver responseObserver ) { - ConnectionResponse.Builder responseBuilder = ConnectionResponse.newBuilder() - .setMajorApiVersion( majorApiVersion ) - .setMinorApiVersion( minorApiVersion ); - if ( clientManager.getHeartbeatInterval() > 0 ) { - responseBuilder.setHeartbeatInterval( clientManager.getHeartbeatInterval() ); - } - boolean isCompatible = checkApiVersion( request ); - responseBuilder.setIsCompatible( isCompatible ); - ConnectionResponse ConnectionResponse = responseBuilder.build(); - // reject incompatible client - if ( !isCompatible ) { - responseObserver.onNext( ConnectionResponse ); - responseObserver.onCompleted(); - return; - } - clientManager.registerConnection( request ); - responseObserver.onNext( ConnectionResponse ); - responseObserver.onCompleted(); - } - - - @SneakyThrows - @Override - public void disconnect( DisconnectRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - clientManager.unregisterConnection( client ); - responseObserver.onNext( DisconnectionResponse.newBuilder().build() ); - responseObserver.onCompleted(); - } - - - @Override - public void checkConnection( ConnectionCheckRequest request, StreamObserver responseObserver ) { - getClient().setIsActive(); - responseObserver.onNext( ConnectionCheckResponse.newBuilder().build() ); - responseObserver.onCompleted(); - } - - - @Override - public void getDbmsVersion( DbmsVersionRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - responseObserver.onNext( DbMetaRetriever.getDbmsVersion() ); - responseObserver.onCompleted(); - } - - - @Override - public void getSupportedLanguages( LanguageRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - LanguageResponse supportedLanguages = LanguageResponse.newBuilder() - .addAllLanguageNames( new LinkedList<>() ) - .build(); - responseObserver.onNext( supportedLanguages ); - responseObserver.onCompleted(); - } - - - private MetaStringResponse buildMetaStringResponse( String string ) { - return MetaStringResponse.newBuilder() - .setString( string ) - .build(); - } - - - @Override - public void getDatabases( DatabasesRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - responseObserver.onNext( DbMetaRetriever.getDatabases() ); - responseObserver.onCompleted(); - } - - - @Override - public void getTableTypes( TableTypesRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - responseObserver.onNext( DbMetaRetriever.getTableTypes() ); - responseObserver.onCompleted(); - } - - - @Override - public void getTypes( TypesRequest request, StreamObserver responseStreamObserver ) { - /* called as client auth check */ - getClient(); - responseStreamObserver.onNext( DbMetaRetriever.getTypes() ); - responseStreamObserver.onCompleted(); - } - - - @Override - public void searchNamespaces( NamespacesRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - String namespacePattern = request.hasNamespacePattern() ? request.getNamespacePattern() : null; - String namespaceType = request.hasNamespaceType() ? request.getNamespaceType() : null; - responseObserver.onNext( DbMetaRetriever.searchNamespaces( namespacePattern, namespaceType ) ); - responseObserver.onCompleted(); - } - - - @Override - public void getNamespace( NamespaceRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - responseObserver.onNext( DbMetaRetriever.getNamespace( request.getNamespaceName() ) ); - responseObserver.onCompleted(); - } - - - @Override - public void searchEntities( EntitiesRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - String entityPattern = request.hasEntityPattern() ? request.getEntityPattern() : null; - responseObserver.onNext( DbMetaRetriever.searchEntities( request.getNamespaceName(), entityPattern ) ); - responseObserver.onCompleted(); - } - - - @Override - public void getSqlStringFunctions( SqlStringFunctionsRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - responseObserver.onNext( buildMetaStringResponse( SqlJdbcFunctionCall.getStringFunctions() ) ); - responseObserver.onCompleted(); - } - - - @Override - public void getSqlSystemFunctions( SqlSystemFunctionsRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - responseObserver.onNext( buildMetaStringResponse( SqlJdbcFunctionCall.getSystemFunctions() ) ); - responseObserver.onCompleted(); - } - - - @Override - public void getSqlTimeDateFunctions( SqlTimeDateFunctionsRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - responseObserver.onNext( buildMetaStringResponse( SqlJdbcFunctionCall.getTimeDateFunctions() ) ); - responseObserver.onCompleted(); - } - - - @Override - public void getSqlNumericFunctions( SqlNumericFunctionsRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - responseObserver.onNext( buildMetaStringResponse( SqlJdbcFunctionCall.getNumericFunctions() ) ); - responseObserver.onCompleted(); - } - - - @Override - public void getSqlKeywords( SqlKeywordsRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - // TODO actually return keywords - responseObserver.onNext( buildMetaStringResponse( "" ) ); - responseObserver.onCompleted(); - } - - - @Override - public void searchProcedures( ProceduresRequest request, StreamObserver responeObserver ) { - /* called as client auth check */ - getClient(); - String procedurePattern = request.hasProcedureNamePattern() ? request.getProcedureNamePattern() : null; - responeObserver.onNext( DbMetaRetriever.getProcedures( request.getLanguage(), procedurePattern ) ); - responeObserver.onCompleted(); - } - - - @Override - public void searchFunctions( FunctionsRequest request, StreamObserver responseObserver ) { - /* called as client auth check */ - getClient(); - QueryLanguage queryLanguage = QueryLanguage.from( request.getQueryLanguage() ); - FunctionCategory functionCategory = FunctionCategory.valueOf( request.getFunctionCategory() ); - responseObserver.onNext( DbMetaRetriever.getFunctions( queryLanguage, functionCategory ) ); - responseObserver.onCompleted(); - } - - - @SneakyThrows - @Override - public void executeUnparameterizedStatement( ExecuteUnparameterizedStatementRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - PIUnparameterizedStatement statement = client.getStatementManager().createUnparameterizedStatement( request ); - responseObserver.onNext( ProtoUtils.createResult( statement ) ); - StatementResult result = request.hasFetchSize() - ? statement.execute( request.getFetchSize() ) - : statement.execute( PropertyUtils.DEFAULT_FETCH_SIZE ); - responseObserver.onNext( ProtoUtils.createResult( statement, result ) ); - responseObserver.onCompleted(); - } - - - @SneakyThrows - @Override - public void executeUnparameterizedStatementBatch( ExecuteUnparameterizedStatementBatchRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - PIUnparameterizedStatementBatch batch = client.getStatementManager().createUnparameterizedStatementBatch( request.getStatementsList() ); - responseObserver.onNext( ProtoUtils.createStatementBatchStatus( batch.getBatchId() ) ); - List updateCounts = batch.executeBatch(); - responseObserver.onNext( ProtoUtils.createStatementBatchStatus( batch.getBatchId(), updateCounts ) ); - responseObserver.onCompleted(); - } - - - @SneakyThrows - @Override - public void prepareIndexedStatement( PrepareStatementRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - PIPreparedIndexedStatement statement = client.getStatementManager().createIndexedPreparedInterfaceStatement( request ); - responseObserver.onNext( ProtoUtils.createPreparedStatementSignature( statement ) ); - responseObserver.onCompleted(); - } - - - @SneakyThrows - @Override - public void executeIndexedStatement( ExecuteIndexedStatementRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - PIPreparedIndexedStatement statement = client.getStatementManager().getIndexedPreparedStatement( request.getStatementId() ); - int fetchSize = request.hasFetchSize() - ? request.getFetchSize() - : PropertyUtils.DEFAULT_FETCH_SIZE; - responseObserver.onNext( statement.execute( ProtoValueDeserializer.deserializeParameterList( request.getParameters().getParametersList() ), fetchSize ) ); - responseObserver.onCompleted(); - } - - - @SneakyThrows - @Override - public void executeIndexedStatementBatch( ExecuteIndexedStatementBatchRequest request, StreamObserver resultObserver ) { - PIClient client = getClient(); - PIPreparedIndexedStatement statement = client.getStatementManager().getIndexedPreparedStatement( request.getStatementId() ); - List> valuesList = ProtoValueDeserializer.deserializeParameterLists( request.getParametersList() ); - List updateCounts = statement.executeBatch( valuesList ); - resultObserver.onNext( ProtoUtils.createStatementBatchStatus( statement.getId(), updateCounts ) ); - resultObserver.onCompleted(); - } - - - @SneakyThrows - @Override - public void prepareNamedStatement( PrepareStatementRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - PIPreparedNamedStatement statement = client.getStatementManager().createNamedPreparedInterfaceStatement( request ); - responseObserver.onNext( ProtoUtils.createPreparedStatementSignature( statement ) ); - responseObserver.onCompleted(); - } - - - @SneakyThrows - @Override - public void executeNamedStatement( ExecuteNamedStatementRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - PIPreparedNamedStatement statement = client.getStatementManager().getNamedPreparedStatement( request.getStatementId() ); - int fetchSize = request.hasFetchSize() - ? request.getFetchSize() - : PropertyUtils.DEFAULT_FETCH_SIZE; - responseObserver.onNext( statement.execute( ProtoValueDeserializer.deserilaizeParameterMap( request.getParameters().getParametersMap() ), fetchSize ) ); - responseObserver.onCompleted(); - } - - - @SneakyThrows - @Override - public void fetchResult( FetchRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - PIStatement statement = client.getStatementManager().getStatement( request.getStatementId() ); - int fetchSize = request.hasFetchSize() - ? request.getFetchSize() - : PropertyUtils.DEFAULT_FETCH_SIZE; - Frame frame = StatementProcessor.fetch( statement, fetchSize ); - responseObserver.onNext( frame ); - responseObserver.onCompleted(); - } - - - @Override - public void commitTransaction( CommitRequest request, StreamObserver responseStreamObserver ) { - PIClient client = getClient(); - client.commitCurrentTransaction(); - responseStreamObserver.onNext( CommitResponse.newBuilder().build() ); - responseStreamObserver.onCompleted(); - } - - - @Override - public void rollbackTransaction( RollbackRequest request, StreamObserver responseStreamObserver ) { - PIClient client = getClient(); - client.rollbackCurrentTransaction(); - responseStreamObserver.onNext( RollbackResponse.newBuilder().build() ); - responseStreamObserver.onCompleted(); - } - - - @Override - public void closeStatement( CloseStatementRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - client.getStatementManager().closeStatementOrBatch( request.getStatementId() ); - responseObserver.onNext( CloseStatementResponse.newBuilder().build() ); - responseObserver.onCompleted(); - } - - - @Override - public void updateConnectionProperties( ConnectionPropertiesUpdateRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - ConnectionProperties properties = request.getConnectionProperties(); - if ( properties.hasIsAutoCommit() ) { - client.setAutoCommit( properties.getIsAutoCommit() ); - } - if ( properties.hasNamespaceName() ) { - String namespaceName = properties.getNamespaceName(); - Optional optionalNamespace = Catalog.getInstance().getSnapshot().getNamespace( namespaceName ); - if ( optionalNamespace.isEmpty() ) { - throw new PIServiceException( "Getting namespace " + namespaceName + " failed." ); - } - client.setNamespace( optionalNamespace.get() ); - } - responseObserver.onNext( ConnectionPropertiesUpdateResponse.newBuilder().build() ); - responseObserver.onCompleted(); - } - - - @Override - public void getClientInfoProperties( ClientInfoPropertiesRequest request, StreamObserver responseObserver ) { - PIClient client = getClient(); - org.polypheny.db.protointerface.proto.ClientInfoProperties.Builder responseBuilder = ClientInfoProperties.newBuilder(); - PIClientInfoProperties PIClientInfoProperties = client.getPIClientInfoProperties(); - PIClientInfoProperties.stringPropertyNames().forEach( s -> responseBuilder.putProperties( s, PIClientInfoProperties.getProperty( s ) ) ); - responseObserver.onNext( responseBuilder.build() ); - responseObserver.onCompleted(); - } - - - @Override - public void setClientInfoProperties( ClientInfoProperties properties, StreamObserver reponseObserver ) { - PIClient client = getClient(); - client.getPIClientInfoProperties().putAll( properties.getPropertiesMap() ); - reponseObserver.onNext( ClientInfoPropertiesResponse.newBuilder().build() ); - reponseObserver.onCompleted(); - } - - - private PIClient getClient() throws PIServiceException { - return clientManager.getClient( ClientMetaInterceptor.CLIENT.get() ); - } - - - private boolean checkApiVersion( ConnectionRequest connectionRequest ) { - return connectionRequest.getMajorApiVersion() == majorApiVersion; - } - -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/MongoImplementer.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/MongoImplementer.java deleted file mode 100644 index f4b7c2e8e4..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/MongoImplementer.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface.statementProcessing; - -import java.util.List; -import org.polypheny.db.languages.LanguageManager; -import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.processing.ImplementationContext; -import org.polypheny.db.processing.QueryContext; -import org.polypheny.db.protointerface.PIServiceException; -import org.polypheny.db.protointerface.statements.PIStatement; -import org.polypheny.db.transaction.Statement; - -public class MongoImplementer extends StatementImplementer { - - private static QueryLanguage language = QueryLanguage.from( "mongo" ); - - - @Override - public QueryLanguage getLanguage() { - return language; - } - - - @Override - public void implement( PIStatement piStatement ) throws PIServiceException { - if ( hasInvalidLanguage( piStatement ) ) { - throw new PIServiceException( "The statement in the language " - + piStatement.getLanguage() - + "can't be executed using a mql executor.", - "I9003", - 9003 - ); - } - Statement statement = piStatement.getStatement(); - if ( statement == null ) { - throw new PIServiceException( "Statement is not linked to a polypheny statement", - "I9001", - 9001 - ); - } - QueryContext context = QueryContext.builder() - .query( piStatement.getQuery() ) - .language( QueryLanguage.from( "mql" ) ) - .namespaceId( piStatement.getNamespace().id ) - .origin( ORIGIN ) - .build(); - List implementations = LanguageManager.getINSTANCE().anyPrepareQuery( context, statement ); - - piStatement.setImplementation( implementations.get( 0 ).getImplementation() ); - } - - -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/SqlImplementer.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/SqlImplementer.java deleted file mode 100644 index 86eaccf215..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statementProcessing/SqlImplementer.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface.statementProcessing; - -import java.util.List; -import org.polypheny.db.languages.LanguageManager; -import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.processing.ImplementationContext; -import org.polypheny.db.processing.QueryContext; -import org.polypheny.db.protointerface.PIServiceException; -import org.polypheny.db.protointerface.statements.PIStatement; -import org.polypheny.db.transaction.Statement; - -public class SqlImplementer extends StatementImplementer { - - private static QueryLanguage language = QueryLanguage.from( "sql" ); - - - @Override - public QueryLanguage getLanguage() { - return language; - } - - - @Override - public void implement( PIStatement piStatement ) throws PIServiceException { - if ( hasInvalidLanguage( piStatement ) ) { - throw new PIServiceException( "The statement in the language " - + piStatement.getLanguage() - + "can't be executed using a sql executor.", - "I9003", - 9003 - ); - } - Statement statement = piStatement.getStatement(); - if ( statement == null ) { - throw new PIServiceException( "Statement is not linked to a polypheny statement", - "I9003", - 9003 - ); - } - QueryContext context = QueryContext.builder() - .query( piStatement.getQuery() ) - .language( QueryLanguage.from( "sql" ) ) - .namespaceId( piStatement.getNamespace().id ) - .origin( ORIGIN ) - .build(); - List implementations = LanguageManager.getINSTANCE().anyPrepareQuery( context, statement ); - piStatement.setImplementation( implementations.get( 0 ).getImplementation() ); - } - -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIPreparedIndexedStatement.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIPreparedIndexedStatement.java deleted file mode 100644 index 37e462c9e8..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/PIPreparedIndexedStatement.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface.statements; - -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Collectors; -import lombok.Getter; -import lombok.Setter; -import org.polypheny.db.PolyImplementation; -import org.polypheny.db.algebra.type.AlgDataType; -import org.polypheny.db.catalog.entity.logical.LogicalNamespace; -import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.protointerface.PIClient; -import org.polypheny.db.protointerface.proto.StatementResult; -import org.polypheny.db.protointerface.statementProcessing.StatementProcessor; -import org.polypheny.db.transaction.Statement; -import org.polypheny.db.transaction.Transaction; -import org.polypheny.db.type.entity.PolyValue; - -@Getter -public class PIPreparedIndexedStatement extends PIPreparedStatement { - - protected String query; - protected Statement statement; - @Setter - protected PolyImplementation implementation; - - - public PIPreparedIndexedStatement( - int id, - PIClient client, - QueryLanguage language, - LogicalNamespace namespace, - String query ) { - super( - id, client, language, namespace - ); - this.query = query; - } - - - public List executeBatch( List> valuesBatch ) throws Exception { - List updateCounts = new LinkedList<>(); - synchronized ( client ) { - if ( statement == null ) { - statement = client.getCurrentOrCreateNewTransaction().createStatement(); - } else { - statement.getDataContext().resetParameterValues(); - } - List types = valuesBatch.stream() - .map( v -> v.get( 0 ).getType() ) - .map( v -> statement.getTransaction().getTypeFactory().createPolyType( v ) ) - .collect( Collectors.toList() ); - int i = 0; - for ( List column : valuesBatch ) { - statement.getDataContext().addParameterValues( i, types.get( i++ ), column ); - } - StatementProcessor.implement( this ); - updateCounts.add( StatementProcessor.executeAndGetResult( this ).getScalar() ); - } - return updateCounts; - } - - - @SuppressWarnings("Duplicates") - public StatementResult execute( List values, int fetchSize ) throws Exception { - synchronized ( client ) { - Transaction transaction; - if ( statement == null ) { - statement = client.getCurrentOrCreateNewTransaction().createStatement(); - } else { - statement.getDataContext().resetParameterValues(); - } - long index = 0; - for ( PolyValue value : values ) { - if ( value != null ) { - AlgDataType algDataType = statement.getTransaction().getTypeFactory().createPolyType( value.getType() ); - statement.getDataContext().addParameterValues( index++, algDataType, List.of( value ) ); - } - } - StatementProcessor.implement( this ); - return StatementProcessor.executeAndGetResult( this, fetchSize ); - } - } - - - @Override - public Transaction getTransaction() { - return statement.getTransaction(); - } - -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/StatementManager.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/StatementManager.java deleted file mode 100644 index 9fcfb7f1fe..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/statements/StatementManager.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface.statements; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; -import lombok.extern.slf4j.Slf4j; -import org.polypheny.db.catalog.Catalog; -import org.polypheny.db.catalog.entity.logical.LogicalNamespace; -import org.polypheny.db.languages.LanguageManager; -import org.polypheny.db.languages.QueryLanguage; -import org.polypheny.db.protointerface.PIClient; -import org.polypheny.db.protointerface.PIServiceException; -import org.polypheny.db.protointerface.proto.ExecuteUnparameterizedStatementRequest; -import org.polypheny.db.protointerface.proto.PrepareStatementRequest; - -@Slf4j -public class StatementManager { - - private final AtomicInteger statementIdGenerator; - private final PIClient client; - private ConcurrentHashMap openStatements; - private ConcurrentHashMap openUnparameterizedBatches; - - - public StatementManager( PIClient client ) { - this.client = client; - statementIdGenerator = new AtomicInteger( 1 ); - openStatements = new ConcurrentHashMap<>(); - openUnparameterizedBatches = new ConcurrentHashMap<>(); - } - - - private LogicalNamespace getNamespace( String namespaceName ) { - Optional optionalNamespace = Catalog.getInstance().getSnapshot().getNamespace( namespaceName ); - - if ( optionalNamespace.isEmpty() ) { - throw new PIServiceException( "Getting namespace " + namespaceName + " failed." ); - } - return optionalNamespace.get(); - } - - - public PIUnparameterizedStatement createUnparameterizedStatement( ExecuteUnparameterizedStatementRequest request ) throws PIServiceException { - synchronized ( client ) { - String languageName = request.getLanguageName(); - if ( !isSupportedLanguage( languageName ) ) { - throw new PIServiceException( "Language " + languageName + " not supported." ); - } - int statementId = statementIdGenerator.getAndIncrement(); - LogicalNamespace namespace = client.getNamespace(); - if ( request.hasNamespaceName() ) { - namespace = getNamespace( request.getNamespaceName() ); - } - assert namespace != null; - PIUnparameterizedStatement statement = new PIUnparameterizedStatement( - statementId, - client, - QueryLanguage.from( languageName ), - namespace, - request.getStatement() - ); - openStatements.put( statementId, statement ); - if ( log.isTraceEnabled() ) { - log.trace( "created request {}", statement ); - } - return statement; - } - } - - - public PIUnparameterizedStatementBatch createUnparameterizedStatementBatch( List statements ) { - synchronized ( client ) { - List PIUnparameterizedStatements = statements.stream() - .map( this::createUnparameterizedStatement ) - .collect( Collectors.toList() ); - final int batchId = statementIdGenerator.getAndIncrement(); - final PIUnparameterizedStatementBatch batch = new PIUnparameterizedStatementBatch( batchId, client, PIUnparameterizedStatements ); - openUnparameterizedBatches.put( batchId, batch ); - if ( log.isTraceEnabled() ) { - log.trace( "created batch {}", batch ); - } - return batch; - } - } - - - public PIPreparedIndexedStatement createIndexedPreparedInterfaceStatement( PrepareStatementRequest request ) throws PIServiceException { - synchronized ( client ) { - String languageName = request.getLanguageName(); - if ( !isSupportedLanguage( languageName ) ) { - throw new PIServiceException( "Language " + languageName + " not supported." ); - } - int statementId = statementIdGenerator.getAndIncrement(); - LogicalNamespace namespace = client.getNamespace(); - if ( request.hasNamespaceName() ) { - namespace = getNamespace( request.getNamespaceName() ); - } - assert namespace != null; - PIPreparedIndexedStatement statement = new PIPreparedIndexedStatement( - statementId, - client, - QueryLanguage.from( languageName ), - namespace, - request.getStatement() - ); - openStatements.put( statementId, statement ); - if ( log.isTraceEnabled() ) { - log.trace( "created named prepared statement {}", statement ); - } - return statement; - } - } - - - public PIPreparedNamedStatement createNamedPreparedInterfaceStatement( PrepareStatementRequest request ) throws PIServiceException { - synchronized ( client ) { - String languageName = request.getLanguageName(); - if ( !isSupportedLanguage( languageName ) ) { - throw new PIServiceException( "Language " + languageName + " not supported." ); - } - final int statementId = statementIdGenerator.getAndIncrement(); - LogicalNamespace namespace = client.getNamespace(); - if ( request.hasNamespaceName() ) { - namespace = getNamespace( request.getNamespaceName() ); - } - assert namespace != null; - PIPreparedNamedStatement statement = new PIPreparedNamedStatement( - statementId, - client, - QueryLanguage.from( languageName ), - namespace, - request.getStatement() - ); - openStatements.put( statementId, statement ); - if ( log.isTraceEnabled() ) { - log.trace( "created named prepared statement {}", statement ); - } - return statement; - } - } - - - public void closeAll() { - synchronized ( client ) { - openUnparameterizedBatches.values().forEach( this::closeBatch ); - openStatements.values().forEach( s -> closeStatement( s.getId() ) ); - } - } - - - public void closeBatch( PIUnparameterizedStatementBatch toClose ) { - synchronized ( client ) { - toClose.getStatements().forEach( s -> closeStatementOrBatch( s.getId() ) ); - } - } - - - private void closeStatement( int statementId ) { - synchronized ( client ) { - PIStatement statementToClose = openStatements.remove( statementId ); - if ( statementToClose == null ) { - return; - } - statementToClose.closeResults(); - } - } - - - public void closeStatementOrBatch( int statementId ) { - synchronized ( client ) { - PIUnparameterizedStatementBatch batchToClose = openUnparameterizedBatches.remove( statementId ); - if ( batchToClose != null ) { - closeBatch( batchToClose ); - return; - } - closeStatement( statementId ); - } - } - - - public PIStatement getStatement( int statementId ) { - if ( statementId == 0 ) { - throw new PIServiceException( "Invalid statement id: 0 (possibly uninitialized protobuf field)" ); - } - PIStatement statement = openStatements.get( statementId ); - if ( statement == null ) { - throw new PIServiceException( "A statement with id " + statementId + " does not exist for that client" ); - } - return statement; - } - - - public PIPreparedNamedStatement getNamedPreparedStatement( int statementId ) throws PIServiceException { - PIStatement statement = getStatement( statementId ); - if ( !(statement instanceof PIPreparedNamedStatement) ) { - throw new PIServiceException( "A named prepared statement with id " + statementId + " does not exist for that client" ); - } - return (PIPreparedNamedStatement) statement; - } - - - public PIPreparedIndexedStatement getIndexedPreparedStatement( int statementId ) throws PIServiceException { - PIStatement statement = getStatement( statementId ); - if ( !(statement instanceof PIPreparedIndexedStatement) ) { - throw new PIServiceException( "A prepared indexed statement with id " + statementId + " does not exist for that client" ); - } - return (PIPreparedIndexedStatement) statement; - } - - - public boolean isSupportedLanguage( String statementLanguageName ) { - return LanguageManager.getLanguages() - .stream() - .map( QueryLanguage::getSerializedName ) - .collect( Collectors.toSet() ) - .contains( statementLanguageName ); - } - -} diff --git a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/utils/PolyValueSerializer.java b/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/utils/PolyValueSerializer.java deleted file mode 100644 index 9c526abe38..0000000000 --- a/plugins/proto-interface/src/main/java/org/polypheny/db/protointerface/utils/PolyValueSerializer.java +++ /dev/null @@ -1,551 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface.utils; - -import com.google.protobuf.ByteString; -import java.math.BigDecimal; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Collectors; -import org.apache.commons.lang3.NotImplementedException; -import org.polypheny.db.protointerface.proto.ProtoBigDecimal; -import org.polypheny.db.protointerface.proto.ProtoBinary; -import org.polypheny.db.protointerface.proto.ProtoBoolean; -import org.polypheny.db.protointerface.proto.ProtoDate; -import org.polypheny.db.protointerface.proto.ProtoDocument; -import org.polypheny.db.protointerface.proto.ProtoDouble; -import org.polypheny.db.protointerface.proto.ProtoEdge; -import org.polypheny.db.protointerface.proto.ProtoEntry; -import org.polypheny.db.protointerface.proto.ProtoFloat; -import org.polypheny.db.protointerface.proto.ProtoGraph; -import org.polypheny.db.protointerface.proto.ProtoGraphPropertyHolder; -import org.polypheny.db.protointerface.proto.ProtoInteger; -import org.polypheny.db.protointerface.proto.ProtoInterval; -import org.polypheny.db.protointerface.proto.ProtoList; -import org.polypheny.db.protointerface.proto.ProtoLong; -import org.polypheny.db.protointerface.proto.ProtoMap; -import org.polypheny.db.protointerface.proto.ProtoNode; -import org.polypheny.db.protointerface.proto.ProtoNull; -import org.polypheny.db.protointerface.proto.ProtoPath; -import org.polypheny.db.protointerface.proto.ProtoPolyType; -import org.polypheny.db.protointerface.proto.ProtoSegment; -import org.polypheny.db.protointerface.proto.ProtoString; -import org.polypheny.db.protointerface.proto.ProtoTime; -import org.polypheny.db.protointerface.proto.ProtoTimeStamp; -import org.polypheny.db.protointerface.proto.ProtoUserDefinedType; -import org.polypheny.db.protointerface.proto.ProtoValue; -import org.polypheny.db.type.PolyType; -import org.polypheny.db.type.entity.PolyBinary; -import org.polypheny.db.type.entity.PolyBoolean; -import org.polypheny.db.type.entity.PolyInterval; -import org.polypheny.db.type.entity.PolyList; -import org.polypheny.db.type.entity.PolyLong; -import org.polypheny.db.type.entity.PolyNull; -import org.polypheny.db.type.entity.PolyString; -import org.polypheny.db.type.entity.PolyUserDefinedValue; -import org.polypheny.db.type.entity.PolyValue; -import org.polypheny.db.type.entity.category.PolyBlob; -import org.polypheny.db.type.entity.document.PolyDocument; -import org.polypheny.db.type.entity.graph.GraphPropertyHolder; -import org.polypheny.db.type.entity.graph.PolyEdge; -import org.polypheny.db.type.entity.graph.PolyGraph; -import org.polypheny.db.type.entity.graph.PolyNode; -import org.polypheny.db.type.entity.graph.PolyPath; -import org.polypheny.db.type.entity.graph.PolyPath.PolySegment; -import org.polypheny.db.type.entity.numerical.PolyBigDecimal; -import org.polypheny.db.type.entity.numerical.PolyDouble; -import org.polypheny.db.type.entity.numerical.PolyFloat; -import org.polypheny.db.type.entity.numerical.PolyInteger; -import org.polypheny.db.type.entity.relational.PolyMap; -import org.polypheny.db.type.entity.temporal.PolyDate; -import org.polypheny.db.type.entity.temporal.PolyTime; -import org.polypheny.db.type.entity.temporal.PolyTimestamp; - -public class PolyValueSerializer { - - - public static List serializeList( List valuesList ) { - return valuesList.stream().map( PolyValueSerializer::serialize ).collect( Collectors.toList() ); - } - - - private static Map serializeValueMap( Map valueMap ) { - return valueMap.entrySet().stream().collect( Collectors.toMap( Entry::getKey, e -> serialize( e.getValue() ) ) ); - } - - - public static Map convertTypeMap( Map typeMap ) { - return typeMap.entrySet().stream().collect( Collectors.toMap( Entry::getKey, e -> getType( e.getValue() ) ) ); - } - - - public static List serializeToProtoEntryList( PolyMap polyMap ) { - return polyMap.entrySet().stream().map( PolyValueSerializer::serializeToProtoEntry ).collect( Collectors.toList() ); - } - - - public static ProtoEntry serializeToProtoEntry( Map.Entry polyMapEntry ) { - return ProtoEntry.newBuilder() - .setKey( serialize( polyMapEntry.getKey() ) ) - .setValue( serialize( polyMapEntry.getValue() ) ) - .build(); - } - - - public static ProtoValue serialize( PolyValue polyValue ) { - if ( polyValue == null ) { - return serializeAsProtoNull(); - } - switch ( polyValue.getType() ) { - case BOOLEAN: - // used by PolyBoolean - return serializeAsProtoBoolean( polyValue.asBoolean() ); - case TINYINT: - case SMALLINT: - case INTEGER: - // used by PolyInteger - return serializeAsProtoInteger( polyValue.asInteger() ); - case BIGINT: - // used by PolyLong - return serializeAsProtoLong( polyValue.asLong() ); - case DECIMAL: - // used by PolyBigDecimal - return serializeAsProtoBigDecimal( polyValue.asBigDecimal() ); - case REAL: - case FLOAT: - // used by PolyFloat - return serializeAsProtoFloat( polyValue.asFloat() ); - case DOUBLE: - // used by PolyDouble - return serializeAsProtoDouble( polyValue.asDouble() ); - case DATE: - // used by PolyDate - return serializeAsProtoDate( polyValue.asDate() ); - case TIME: - // used by PolyTime - case TIME_WITH_LOCAL_TIME_ZONE: - return serializeAsProtoTime( polyValue.asTime() ); - case TIMESTAMP: - // used by PolyTimeStamp - case TIMESTAMP_WITH_LOCAL_TIME_ZONE: - return serializeAsProtoTimeStamp( polyValue.asTimestamp() ); - case INTERVAL_SECOND: - // used by PolyInterval - case INTERVAL_MINUTE_SECOND: - // used by PolyInterval - case INTERVAL_MINUTE: - // used by PolyInterval - case INTERVAL_HOUR_SECOND: - // used by PolyInterval - case INTERVAL_HOUR_MINUTE: - // used by PolyInterval - case INTERVAL_HOUR: - // used by PolyInterval - case INTERVAL_DAY_SECOND: - // used by PolyInterval - case INTERVAL_DAY_MINUTE: - // used by PolyInterval - case INTERVAL_DAY_HOUR: - // used by PolyInterval - case INTERVAL_DAY: - // used by PolyInterval - case INTERVAL_MONTH: - // used by PolyInterval - case INTERVAL_YEAR_MONTH: - // used by PolyInterval - case INTERVAL_YEAR: - return serializeAsProtoInterval( polyValue.asInterval() ); - case CHAR: - case VARCHAR: - // used by PolyString - return serializeAsProtoString( polyValue.asString() ); - case BINARY: - case VARBINARY: - // used by PolyBinary - return serializeAsProtoBinary( polyValue.asBinary() ); - case NULL: - // used by PolyNull - return serializeAsProtoNull( polyValue.asNull() ); - case ARRAY: - // used by PolyList - return serializeAsProtoList( polyValue.asList() ); - case MAP: - // used by PolyDictionary - // used by PolyMap - return serializeAsProtoMap( polyValue.asMap() ); - case DOCUMENT: - // used by PolyDocument - return serializeAsProtoDocument( polyValue.asDocument() ); - case GRAPH: - // used by PolyGraph - return serializeAsProtoGraph( polyValue.asGraph() ); - case NODE: - // used by PolyNode - return serializeAsProtoNode( polyValue.asNode() ); - case EDGE: - // used by PolyEdge - return serializeAsProtoEdge( polyValue.asEdge() ); - case PATH: - // used by PolyPath - serializeAsProtoPath( polyValue.asPath() ); - case IMAGE: - case VIDEO: - case AUDIO: - case FILE: - // used by PolyFile - return serializeAsProtoFile( polyValue.asBlob() ); - case DISTINCT: - case STRUCTURED: - case ROW: - case OTHER: - case CURSOR: - case COLUMN_LIST: - case DYNAMIC_STAR: - case GEOMETRY: - case SYMBOL: // used - case JSON: // used - case MULTISET: - case ANY: - throw new NotImplementedException( "Serialization of " + polyValue.getType() + " to proto not implemented" ); - case USER_DEFINED_TYPE: - // used by PolyUserDefinedType - return serializeAsProtoUserDefinedType( polyValue.asUserDefinedValue() ); - } - throw new NotImplementedException(); - } - - - private static ProtoValue serializeAsProtoPath( PolyPath polyPath ) { - ProtoPath protoPath = ProtoPath.newBuilder() - .setNodes( serializeToProtoList( polyPath.getNodes().asList() ) ) - .setEdges( serializeToProtoList( polyPath.getEdges().asList() ) ) - .setNames( serializeToProtoList( polyPath.getNames().asList() ) ) - .addAllPaths( serializeToProtoGraphPropertyHolderList( polyPath.getPath() ) ) - .addAllSegments( serializeToProtoSegmentList( polyPath.getSegments() ) ) - .build(); - return ProtoValue.newBuilder() - .setPath( protoPath ) - .build(); - } - - - private static ProtoValue serializeAsProtoEdge( PolyEdge polyEdge ) { - return ProtoValue.newBuilder() - .setEdge( serializeToProtoEdge( polyEdge ) ) - .build(); - - } - - - private static ProtoEdge serializeToProtoEdge( PolyEdge polyEdge ) { - return ProtoEdge.newBuilder() - .setGraphPropertyHolder( serializeToProtoGraphPropertyHolder( polyEdge ) ) - .setSource( serializeToProtoString( polyEdge.getSource() ) ) - .setTarget( serializeToProtoString( polyEdge.getTarget() ) ) - .setEdgeDirection( getEdgeDirection( polyEdge.getDirection() ) ) - .build(); - } - - - private static ProtoEdge.EdgeDirection getEdgeDirection( PolyEdge.EdgeDirection edgeDirection ) { - return ProtoEdge.EdgeDirection.valueOf( edgeDirection.name() ); - } - - - private static ProtoValue serializeAsProtoNode( PolyNode polyNode ) { - return ProtoValue.newBuilder() - .setNode( serializeToProtoNode( polyNode ) ) - .build(); - } - - - private static ProtoNode serializeToProtoNode( PolyNode polyNode ) { - return ProtoNode.newBuilder() - .setGraphPropertyHolder( serializeToProtoGraphPropertyHolder( polyNode ) ) - .build(); - } - - - private static ProtoGraphPropertyHolder serializeToProtoGraphPropertyHolder( GraphPropertyHolder polyGraphPropertyHolder ) { - return ProtoGraphPropertyHolder.newBuilder() - .setId( serializeToProtoString( polyGraphPropertyHolder.getId() ) ) - .setVariableName( serializeToProtoString( polyGraphPropertyHolder.getVariableName() ) ) - .setProperties( serializeToProtoMap( polyGraphPropertyHolder.getProperties().asMap() ) ) - .setLabels( serializeToProtoList( polyGraphPropertyHolder.getLabels().asList() ) ) - .build(); - } - - - private static ProtoSegment serializeToProtoSegment( PolySegment polySegment ) { - return ProtoSegment.newBuilder() - .setId( serializeToProtoString( polySegment.getId() ) ) - .setVariableName( serializeToProtoString( polySegment.getVariableName() ) ) - .setSourceId( serializeToProtoString( polySegment.getSourceId() ) ) - .setEdgeId( serializeToProtoString( polySegment.getEdgeId() ) ) - .setTargetId( serializeToProtoString( polySegment.getTargetId() ) ) - .setSource( serializeToProtoNode( polySegment.getSource() ) ) - .setEdge( serializeToProtoEdge( polySegment.getEdge() ) ) - .setTarget( serializeToProtoNode( polySegment.getTarget() ) ) - .setIsRef( polySegment.isRef() ) - .setEdgeDirection( getEdgeDirection( polySegment.getDirection() ) ) - .build(); - } - - - private static List serializeToProtoGraphPropertyHolderList( List polyGraphPropertyHolders ) { - return polyGraphPropertyHolders.stream().map( PolyValueSerializer::serializeToProtoGraphPropertyHolder ).collect( Collectors.toList() ); - } - - - private static List serializeToProtoSegmentList( List polySegments ) { - return polySegments.stream().map( PolyValueSerializer::serializeToProtoSegment ).toList(); - } - - - private static ProtoValue serializeAsProtoGraph( PolyGraph polyGraph ) { - ProtoGraph protoGraph = ProtoGraph.newBuilder() - .setId( serializeToProtoString( polyGraph.getId() ) ) - .setVariableName( serializeToProtoString( polyGraph.getVariableName() ) ) - .setNodes( serializeToProtoMap( polyGraph.getNodes().asMap() ) ) - .setEdges( serializeToProtoMap( polyGraph.getEdges().asMap() ) ) - .build(); - return ProtoValue.newBuilder() - .setGraph( protoGraph ) - .build(); - } - - - public static ProtoDocument buildProtoDocument( PolyDocument polyDocument ) { - return ProtoDocument.newBuilder() - .addAllEntries( serializeToProtoEntryList( polyDocument.asMap() ) ) - .build(); - } - - - private static ProtoValue serializeAsProtoDocument( PolyDocument polyDocument ) { - return ProtoValue.newBuilder() - .setDocument( buildProtoDocument( polyDocument ) ) - .build(); - } - - - private static ProtoValue serializeAsProtoMap( PolyMap polyMap ) { - return ProtoValue.newBuilder() - .setMap( serializeToProtoMap( polyMap ) ) - .build(); - } - - - private static ProtoMap serializeToProtoMap( PolyMap polyMap ) { - return ProtoMap.newBuilder() - .addAllEntries( serializeToProtoEntryList( polyMap ) ) - .build(); - } - - - private static ProtoValue serializeAsProtoList( PolyList polyList ) { - return ProtoValue.newBuilder() - .setList( serializeToProtoList( polyList ) ) - .build(); - - } - - - private static ProtoList serializeToProtoList( PolyList polyList ) { - return ProtoList.newBuilder() - .addAllValues( serializeList( polyList.getValue() ) ) - .build(); - } - - - private static ProtoValue serializeAsProtoFile( PolyBlob polyBlob ) { - ProtoBinary protoBinary = ProtoBinary.newBuilder() - .setBinary( ByteString.copyFrom( polyBlob.getValue() ) ) - .build(); - return ProtoValue.newBuilder() - .setBinary( protoBinary ) - .build(); - } - - - private static ProtoValue serializeAsProtoUserDefinedType( PolyUserDefinedValue userDefinedValue ) { - ProtoUserDefinedType protoUserDefinedType = ProtoUserDefinedType.newBuilder() - .putAllTemplate( convertTypeMap( userDefinedValue.getTemplate() ) ) - .putAllValue( serializeValueMap( userDefinedValue.getValue() ) ) - .build(); - return ProtoValue.newBuilder() - .setUserDefinedType( protoUserDefinedType ) - .build(); - } - - - private static ProtoValue serializeAsProtoInterval( PolyInterval polyInterval ) { - ProtoInterval protoInterval = ProtoInterval.newBuilder() - .setValue( serializeBigDecimal( polyInterval.getValue() ) ) - .build(); - return ProtoValue.newBuilder() - .setInterval( protoInterval ) - .build(); - } - - - private static ProtoPolyType getType( PolyValue polyValue ) { - return getType( polyValue.getType() ); - } - - - private static ProtoPolyType getType( PolyType polyType ) { - return ProtoPolyType.valueOf( polyType.getName() ); - } - - - public static ProtoValue serializeAsProtoBoolean( PolyBoolean polyBoolean ) { - ProtoBoolean protoBoolean = ProtoBoolean.newBuilder() - .setBoolean( polyBoolean.getValue() ) - .build(); - return ProtoValue.newBuilder() - .setBoolean( protoBoolean ) - .build(); - } - - - public static ProtoValue serializeAsProtoInteger( PolyInteger polyInteger ) { - ProtoInteger protoInteger = ProtoInteger.newBuilder() - .setInteger( polyInteger.getValue() ) - .build(); - return ProtoValue.newBuilder() - .setInteger( protoInteger ) - .build(); - } - - - public static ProtoValue serializeAsProtoLong( PolyLong polyLong ) { - ProtoLong protoLong = ProtoLong.newBuilder() - .setLong( polyLong.value ) - .build(); - return ProtoValue.newBuilder() - .setLong( protoLong ) - .build(); - } - - - public static ProtoValue serializeAsProtoBinary( PolyBinary polyBinary ) { - ProtoBinary protoBinary = ProtoBinary.newBuilder() - .setBinary( ByteString.copyFrom( polyBinary.getValue().getBytes() ) ) - .build(); - return ProtoValue.newBuilder() - .setBinary( protoBinary ) - .build(); - } - - - public static ProtoValue serializeAsProtoDate( PolyDate polyDate ) { - ProtoDate protoDate = ProtoDate.newBuilder() - .setDate( polyDate.getMillisSinceEpoch() ) - .build(); - return ProtoValue.newBuilder() - .setDate( protoDate ) - .build(); - } - - - public static ProtoValue serializeAsProtoDouble( PolyDouble polyDouble ) { - ProtoDouble protoDouble = ProtoDouble.newBuilder() - .setDouble( polyDouble.doubleValue() ) - .build(); - return ProtoValue.newBuilder() - .setDouble( protoDouble ) - .build(); - } - - - public static ProtoValue serializeAsProtoFloat( PolyFloat polyFloat ) { - ProtoFloat protoFloat = ProtoFloat.newBuilder() - .setFloat( polyFloat.floatValue() ) - .build(); - return ProtoValue.newBuilder() - .setFloat( protoFloat ) - .build(); - } - - - public static ProtoValue serializeAsProtoString( PolyString polyString ) { - return ProtoValue.newBuilder() - .setString( serializeToProtoString( polyString ) ) - .build(); - } - - - public static ProtoString serializeToProtoString( PolyString polyString ) { - return ProtoString.newBuilder() - .setString( polyString.getValue() ) - .build(); - } - - - public static ProtoValue serializeAsProtoTime( PolyTime polyTime ) { - ProtoTime protoTime = ProtoTime.newBuilder() - .setValue( polyTime.ofDay ) - .build(); - return ProtoValue.newBuilder() - .setTime( protoTime ) - .build(); - } - - - public static ProtoValue serializeAsProtoTimeStamp( PolyTimestamp polyTimeStamp ) { - ProtoTimeStamp protoTimeStamp = ProtoTimeStamp.newBuilder() - .setTimeStamp( polyTimeStamp.getMillisSinceEpoch() ) - .build(); - return ProtoValue.newBuilder() - .setTimeStamp( protoTimeStamp ) - .build(); - } - - - public static ProtoValue serializeAsProtoNull( PolyNull polyNull ) { - return ProtoValue.newBuilder() - .setNull( ProtoNull.newBuilder().build() ) - .build(); - } - - - private static ProtoValue serializeAsProtoNull() { - return ProtoValue.newBuilder() - .setNull( ProtoNull.newBuilder().build() ) - .build(); - } - - - public static ProtoValue serializeAsProtoBigDecimal( PolyBigDecimal polyBigDecimal ) { - ProtoBigDecimal protoBigDecimal = serializeBigDecimal( polyBigDecimal.getValue() ); - return ProtoValue.newBuilder() - .setBigDecimal( protoBigDecimal ) - .build(); - } - - - private static ProtoBigDecimal serializeBigDecimal( BigDecimal bigDecimal ) { - return ProtoBigDecimal.newBuilder() - .setUnscaledValue( ByteString.copyFrom( bigDecimal.unscaledValue().toByteArray() ) ) - .setScale( bigDecimal.scale() ) - .setPrecision( bigDecimal.precision() ) - .build(); - } - -} diff --git a/plugins/proto-interface/src/main/proto/connection_requests.proto b/plugins/proto-interface/src/main/proto/connection_requests.proto deleted file mode 100644 index e46468b8d1..0000000000 --- a/plugins/proto-interface/src/main/proto/connection_requests.proto +++ /dev/null @@ -1,31 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "ConnectionRequests"; - -package polypheny.protointerface; - -message ConnectionRequest { - int32 major_api_version = 1; - int32 minor_api_version = 2; - string client_uuid = 3; - optional string username = 5; - optional string password = 6; - optional ConnectionProperties connection_properties = 4; -} - -message ConnectionPropertiesUpdateRequest { - ConnectionProperties connection_properties = 4; -} - -message ConnectionProperties { - optional bool is_auto_commit = 1; - optional string namespace_name = 2; -} - -message DisconnectRequest { -} - -message ConnectionCheckRequest { -} diff --git a/plugins/proto-interface/src/main/proto/connection_responses.proto b/plugins/proto-interface/src/main/proto/connection_responses.proto deleted file mode 100644 index e423fb58f8..0000000000 --- a/plugins/proto-interface/src/main/proto/connection_responses.proto +++ /dev/null @@ -1,23 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "ConnectionResponses"; - -package polypheny.protointerface; - -message ConnectionResponse { - bool is_compatible = 1; - int32 major_api_version = 2; - int32 minor_api_version = 3; - optional int64 heartbeat_interval = 4; -} - -message DisconnectionResponse { -} - -message ConnectionCheckResponse { -} - -message ConnectionPropertiesUpdateResponse { -} diff --git a/plugins/proto-interface/src/main/proto/document_frame.proto b/plugins/proto-interface/src/main/proto/document_frame.proto deleted file mode 100644 index 2497a5eb53..0000000000 --- a/plugins/proto-interface/src/main/proto/document_frame.proto +++ /dev/null @@ -1,13 +0,0 @@ -syntax = "proto3"; - -import "value.proto"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "DocumentResultProto"; - -package polypheny.protointerface; - -message DocumentFrame { - repeated ProtoDocument documents = 1; -} \ No newline at end of file diff --git a/plugins/proto-interface/src/main/proto/error.proto b/plugins/proto-interface/src/main/proto/error.proto deleted file mode 100644 index 0960c87208..0000000000 --- a/plugins/proto-interface/src/main/proto/error.proto +++ /dev/null @@ -1,13 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "Error"; - -package polypheny.protointerface; - -message ErrorDetails { - optional int32 error_code = 1; - optional string state = 2; - optional string message = 3; -} diff --git a/plugins/proto-interface/src/main/proto/graph_frame.proto b/plugins/proto-interface/src/main/proto/graph_frame.proto deleted file mode 100644 index 12ccc26e85..0000000000 --- a/plugins/proto-interface/src/main/proto/graph_frame.proto +++ /dev/null @@ -1,10 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "GraphResultProto"; - -package polypheny.protointerface; - -message GraphFrame { -} \ No newline at end of file diff --git a/plugins/proto-interface/src/main/proto/meta_requests.proto b/plugins/proto-interface/src/main/proto/meta_requests.proto deleted file mode 100644 index f354687ba5..0000000000 --- a/plugins/proto-interface/src/main/proto/meta_requests.proto +++ /dev/null @@ -1,56 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "MetaRequests"; - -package polypheny.protointerface; - -message DbmsVersionRequest { -} - -message LanguageRequest { -} - -message DatabasesRequest { -} - -message TableTypesRequest { -} - -message TypesRequest { -} - -message UserDefinedTypesRequest { -} - -message SqlStringFunctionsRequest { -} - -message SqlSystemFunctionsRequest { -} - -message SqlTimeDateFunctionsRequest { -} - -message SqlNumericFunctionsRequest { -} - -message SqlKeywordsRequest { -} - -message ProceduresRequest { - string language = 1; - optional string procedure_name_pattern = 3; -} - -message ClientInfoPropertiesRequest { -} - -message ClientInfoPropertyMetaRequest { -} - -message FunctionsRequest { - string query_language = 1; - string function_category = 2; -} diff --git a/plugins/proto-interface/src/main/proto/meta_responses.proto b/plugins/proto-interface/src/main/proto/meta_responses.proto deleted file mode 100644 index ce52c42d8f..0000000000 --- a/plugins/proto-interface/src/main/proto/meta_responses.proto +++ /dev/null @@ -1,118 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "MetaResponses"; - -package polypheny.protointerface; - -message DbmsVersionResponse { - string dbms_name = 1; - string version_name = 2; - int32 major_version = 3; - int32 minor_version = 4; -} - -message LanguageResponse { - repeated string language_names = 1; -} - -message DatabasesResponse { - repeated Database databases = 1; -} - -message Database { - string database_name = 1; - string owner_name = 2; - string default_namespace_name = 3; -} - -message TableTypesResponse { - repeated TableType table_types = 1; -} - -message TableType { - string table_type = 1; -} - -message TypesResponse { - repeated Type types = 1; -} - -message Type { - string type_name = 1; - int32 precision = 2; - optional string literal_prefix = 3; - optional string literal_suffix = 4; - bool is_case_sensitive = 5; - int32 is_searchable = 6; - bool is_auto_increment = 7; - int32 min_scale = 8; - int32 max_scale = 9; - int32 radix = 10; -} - -message UserDefinedTypesResponse { - repeated UserDefinedType user_defined_types = 1; -} - -message UserDefinedType { - string type_name = 1; - repeated ValueMeta value_metas= 2; -} - -message ValueMeta { - string value_name = 1; -} - -message MetaStringResponse { - string string = 1; -} - -message ProceduresResponse { - repeated Procedure procedures = 1; -} - -message Procedure { - string trivial_name = 1; - string input_parameters = 2; - string description = 3; - ReturnType return_type = 4; - string unique_name = 5; - - enum ReturnType { - UNSPECIFIED = 0; - UNKNOWN = 1; - NO_RESULT = 2; - RESULT = 3; - } -} - -message ClientInfoProperties { - map properties = 1; -} - -message ClientInfoPropertiesResponse { -} - -message ClientInfoPropertyMetaResponse { - repeated ClientInfoPropertyMeta client_info_property_metas = 1; -} - -message ClientInfoPropertyMeta { - string key = 1; - string default_value = 2; - int32 maxlength = 3; - string description = 4; -} - -message FunctionsResponse { - repeated Function functions = 1; -} - -message Function { - string name = 1; - string syntax = 2; - string function_category = 3; - bool is_table_function = 4; -} \ No newline at end of file diff --git a/plugins/proto-interface/src/main/proto/namespace_meta_requests.proto b/plugins/proto-interface/src/main/proto/namespace_meta_requests.proto deleted file mode 100644 index 7c9c9946c3..0000000000 --- a/plugins/proto-interface/src/main/proto/namespace_meta_requests.proto +++ /dev/null @@ -1,21 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "NamespaceMeta"; - -package polypheny.protointerface; - -message NamespaceRequest { - string namespace_name = 1; -} - -message NamespacesRequest { - optional string namespace_pattern = 1; - optional string namespace_type = 2; -} - -message EntitiesRequest { - string namespace_name = 1; - optional string entity_pattern = 2; -} diff --git a/plugins/proto-interface/src/main/proto/namespace_meta_responses.proto b/plugins/proto-interface/src/main/proto/namespace_meta_responses.proto deleted file mode 100644 index 940d3f8eb0..0000000000 --- a/plugins/proto-interface/src/main/proto/namespace_meta_responses.proto +++ /dev/null @@ -1,102 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "NamespaceMetaResponses"; - -package polypheny.protointerface; - -message NamespacesResponse { - repeated Namespace namespaces = 1; -} - -message EntitiesResponse { - repeated Entity entities = 1; -} - -message Namespace { - string namespace_name = 1; - string database_name = 2; - string owner_name = 3; - bool is_case_sensitive = 4; - optional string namespace_type = 5; -} - -message Entity { - oneof Entity { - Table table = 1; - Graph graph = 2; - Document document= 3; - // add other entities known in other models - } -} - -message Table { - string source_database_name = 1; - string namespace_name = 2; - string table_name = 3; - string table_type = 4; - string owner_name = 5; - repeated Column columns = 6; - optional PrimaryKey primary_key = 7; - repeated ForeignKey foreign_keys = 8; - repeated ForeignKey exported_keys = 9; - repeated Index indexes = 10; -} - -message Column { - string database_name = 1; - string namespace_name = 2; - string table_name = 3; - string column_name = 4; - string type_name = 5; - optional int32 type_length = 6; - optional int32 type_scale = 7; - bool is_nullable = 8; - optional string default_value_as_string = 9; - int32 column_index = 10; - optional string collation = 11; - bool is_hidden = 12; - ColumnType column_type = 13; - enum ColumnType { - UNSPECIFIED = 0; - REGULAR = 1; - CALUCLATED = 2; - VERSION = 3; - } -} - -message PrimaryKey { - string database_name = 1; - string namespace_name = 2; - string table_name = 3; - repeated Column columns = 4; -} - -message ForeignKey { - optional string referenced_database_name = 1; - optional string referenced_namespace_name = 2; - string referenced_table_name = 3; - repeated Column referenced_columns = 4; - repeated Column foreign_columns = 8; - int32 update_rule = 10; - int32 delete_rule = 11; - optional string key_name = 12; -} - -message Index { - string database_name = 1; - string namespace_name = 2; - string table_name = 3; - bool unique = 4; - string index_name = 5; - repeated Column columns = 6; - int64 location = 8; - int32 index_type = 9; -} - -message Graph { -} - -message Document { -} \ No newline at end of file diff --git a/plugins/proto-interface/src/main/proto/protointerface.proto b/plugins/proto-interface/src/main/proto/protointerface.proto deleted file mode 100644 index 1f3bdd354b..0000000000 --- a/plugins/proto-interface/src/main/proto/protointerface.proto +++ /dev/null @@ -1,64 +0,0 @@ -syntax = "proto3"; - -import "connection_requests.proto"; -import "connection_responses.proto"; -import "meta_requests.proto"; -import "meta_responses.proto"; -import "namespace_meta_requests.proto"; -import "namespace_meta_responses.proto"; -import "statement_requests.proto"; -import "statement_responses.proto"; -import "transaction_requests.proto"; -import "transaction_responses.proto"; -import "value.proto"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "ProtoInterfaceProto"; - -package polypheny.protointerface; - -service ProtoInterface { - // Meta functions - rpc GetDbmsVersion(DbmsVersionRequest) returns (DbmsVersionResponse) {} - rpc GetSupportedLanguages(LanguageRequest) returns (LanguageResponse) {} - rpc GetDatabases(DatabasesRequest) returns (DatabasesResponse) {} - rpc GetTableTypes(TableTypesRequest) returns (TableTypesResponse) {} - rpc GetTypes(TypesRequest) returns (TypesResponse) {} - rpc GetUserDefinedTypes(UserDefinedTypesRequest) returns (UserDefinedTypesResponse) {} - rpc GetClientInfoPropertyMetas(ClientInfoPropertyMetaRequest) returns (ClientInfoPropertyMetaResponse) {} - rpc SearchProcedures(ProceduresRequest) returns (ProceduresResponse) {} - rpc SearchFunctions(FunctionsRequest) returns (FunctionsResponse) {} - rpc SearchNamespaces(NamespacesRequest) returns (NamespacesResponse) {} - rpc GetNamespace(NamespaceRequest) returns (Namespace) {} - rpc SearchEntities(EntitiesRequest) returns (EntitiesResponse) {} - rpc GetSqlStringFunctions(SqlStringFunctionsRequest) returns (MetaStringResponse) {} - rpc GetSqlSystemFunctions(SqlSystemFunctionsRequest) returns (MetaStringResponse) {} - rpc GetSqlTimeDateFunctions(SqlTimeDateFunctionsRequest) returns (MetaStringResponse) {} - rpc GetSqlNumericFunctions(SqlNumericFunctionsRequest) returns (MetaStringResponse) {} - rpc GetSqlKeywords(SqlKeywordsRequest) returns (MetaStringResponse) {} - // Connection related - rpc Connect(ConnectionRequest) returns (ConnectionResponse) {} - rpc CheckConnection(ConnectionCheckRequest) returns (ConnectionCheckResponse) {} - rpc Disconnect(DisconnectRequest) returns (DisconnectionResponse) {} - rpc GetClientInfoProperties(ClientInfoPropertiesRequest) returns (ClientInfoProperties) {} - rpc SetClientInfoProperties(ClientInfoProperties) returns (ClientInfoPropertiesResponse) {} - // Unparameterized statements - rpc ExecuteUnparameterizedStatement(ExecuteUnparameterizedStatementRequest) returns (stream StatementResponse) {} - rpc ExecuteUnparameterizedStatementBatch(ExecuteUnparameterizedStatementBatchRequest) returns (stream StatementBatchResponse) {} - // Prepared Statements - rpc PrepareIndexedStatement(PrepareStatementRequest) returns (PreparedStatementSignature) {} - rpc ExecuteIndexedStatement(ExecuteIndexedStatementRequest) returns (StatementResult) {} - rpc ExecuteIndexedStatementBatch(ExecuteIndexedStatementBatchRequest) returns (StatementBatchResponse) {} - rpc PrepareNamedStatement(PrepareStatementRequest) returns (PreparedStatementSignature) {} - rpc ExecuteNamedStatement(ExecuteNamedStatementRequest) returns (StatementResult) {} - // ExecuteNamedStatementBatch ? - // Results - rpc FetchResult(FetchRequest) returns (Frame) {} - // Transaction handling - rpc CloseStatement(CloseStatementRequest) returns (CloseStatementResponse) {} - rpc CommitTransaction(CommitRequest) returns (CommitResponse) {} - rpc RollbackTransaction(RollbackRequest) returns (RollbackResponse) {} - // Properties - rpc UpdateConnectionProperties(ConnectionPropertiesUpdateRequest) returns (ConnectionPropertiesUpdateResponse) {} -} diff --git a/plugins/proto-interface/src/main/proto/relational_frame.proto b/plugins/proto-interface/src/main/proto/relational_frame.proto deleted file mode 100644 index 309f4f4852..0000000000 --- a/plugins/proto-interface/src/main/proto/relational_frame.proto +++ /dev/null @@ -1,56 +0,0 @@ -syntax = "proto3"; - -import "value.proto"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "RelationalResult"; - -package polypheny.protointerface; - -message RelationalFrame { - repeated ColumnMeta column_meta = 1; - repeated Row rows = 2; -} - -message ColumnMeta { - int32 column_index = 1; - bool is_nullable = 2; - int32 length = 3; - string column_label = 4; - string column_name = 5; - int32 precision = 6; - string entity_name = 7; - string schema_name = 8; - TypeMeta type_meta = 9; - int32 scale = 10; - string namespace = 11; -} - -message FieldMeta { - int32 field_index = 1; - bool is_nullable = 2; - int32 length = 3; - string field_name = 4; - int32 precision = 6; - TypeMeta type_meta = 9; - int32 scale = 10; -} - -message TypeMeta { - ProtoPolyType proto_value_type = 1; - optional StructMeta struct_meta = 2; - optional ArrayMeta array_meta = 3; -} - -message StructMeta { - repeated FieldMeta field_metas = 1; -} - -message ArrayMeta { - TypeMeta element_type = 1; -} - -message Row { - repeated ProtoValue values = 1; -} \ No newline at end of file diff --git a/plugins/proto-interface/src/main/proto/statement_requests.proto b/plugins/proto-interface/src/main/proto/statement_requests.proto deleted file mode 100644 index f2286396a4..0000000000 --- a/plugins/proto-interface/src/main/proto/statement_requests.proto +++ /dev/null @@ -1,60 +0,0 @@ -syntax = "proto3"; - -import "value.proto"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "StatementRequests"; - -package polypheny.protointerface; - -message ExecuteUnparameterizedStatementRequest { - string language_name = 1; - string statement = 2; - optional int32 fetch_size = 3; - optional string namespace_name = 4; -} - -message ExecuteUnparameterizedStatementBatchRequest { - repeated ExecuteUnparameterizedStatementRequest statements = 1; -} - -message PrepareStatementRequest { - string language_name = 1; - string statement = 2; - optional string namespace_name = 3; -} - -message ExecuteIndexedStatementRequest { - int32 statement_id = 1; - IndexedParameters parameters = 2; - optional int32 fetch_size = 3; -} - -message ExecuteIndexedStatementBatchRequest { - int32 statement_id = 1; - repeated IndexedParameters parameters = 2; -} - -message ExecuteNamedStatementRequest { - int32 statement_id = 1; - NamedParameters parameters = 2; - optional int32 fetch_size = 3; -} - -message IndexedParameters { - repeated ProtoValue parameters = 1; -} - -message NamedParameters { - map parameters = 1; -} - -message CloseStatementRequest { - int32 statement_id = 1; -} - -message FetchRequest { - int32 statement_id = 1; - optional int32 fetch_size = 2; -} diff --git a/plugins/proto-interface/src/main/proto/statement_responses.proto b/plugins/proto-interface/src/main/proto/statement_responses.proto deleted file mode 100644 index 9b76e7f0e2..0000000000 --- a/plugins/proto-interface/src/main/proto/statement_responses.proto +++ /dev/null @@ -1,51 +0,0 @@ -syntax = "proto3"; - -import "relational_frame.proto"; -import "graph_frame.proto"; -import "document_frame.proto"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "StatementResponses"; - -package polypheny.protointerface; - -message StatementResponse { - int32 statement_id = 1; - optional StatementResult result = 2; -} - -message StatementResult { - int64 scalar = 1; - optional Frame frame = 2; -} - -message StatementBatchResponse { - int32 batch_id = 1; - repeated int64 scalars = 2; -} - -message PreparedStatementSignature { - int32 statement_id = 1; - repeated ParameterMeta parameter_metas = 2; -} - -message ParameterMeta { - int32 precision = 1; - int32 scale = 2; - string type_name = 3; - optional string parameter_name = 4; - string name = 5; -} - -message CloseStatementResponse { -} - -message Frame { - bool is_last = 1; - oneof result { - RelationalFrame relational_frame = 2; - GraphFrame graph_frame = 3; - DocumentFrame document_frame = 4; - } -} diff --git a/plugins/proto-interface/src/main/proto/transaction_requests.proto b/plugins/proto-interface/src/main/proto/transaction_requests.proto deleted file mode 100644 index 42aa33a8e1..0000000000 --- a/plugins/proto-interface/src/main/proto/transaction_requests.proto +++ /dev/null @@ -1,13 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "TransactionRequests"; - -package polypheny.protointerface; - -message CommitRequest { -} - -message RollbackRequest { -} \ No newline at end of file diff --git a/plugins/proto-interface/src/main/proto/transaction_responses.proto b/plugins/proto-interface/src/main/proto/transaction_responses.proto deleted file mode 100644 index 375502a1d5..0000000000 --- a/plugins/proto-interface/src/main/proto/transaction_responses.proto +++ /dev/null @@ -1,13 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "TransactionResponses"; - -package polypheny.protointerface; - -message CommitResponse { -} - -message RollbackResponse { -} \ No newline at end of file diff --git a/plugins/proto-interface/src/main/proto/value.proto b/plugins/proto-interface/src/main/proto/value.proto deleted file mode 100644 index 03ec2e6616..0000000000 --- a/plugins/proto-interface/src/main/proto/value.proto +++ /dev/null @@ -1,237 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "org.polypheny.db.protointerface.proto"; -option java_outer_classname = "ProtoValueProto"; - -package polypheny.protointerface; - -enum ProtoPolyType { - UNSPECIFIED = 0; - BOOLEAN = 1; - TINYINT = 2; - SMALLINT = 3; - INTEGER = 4; - BIGINT = 5; - DECIMAL = 6; - REAL = 7; - FLOAT = 8; - DOUBLE = 9; - DATE = 10; - TIME = 11; - TIME_WITH_LOCAL_TIME_ZONE = 12; - TIMESTAMP = 13; - TIMESTAMP_WITH_LOCAL_TIME_ZONE = 14; - INTERVAL_SECOND = 15; - INTERVAL_MINUTE_SECOND = 16; - INTERVAL_MINUTE = 17; - INTERVAL_HOUR_SECOND = 18; - INTERVAL_HOUR_MINUTE = 19; - INTERVAL_HOUR = 20; - INTERVAL_DAY_SECOND = 21; - INTERVAL_DAY_MINUTE = 22; - INTERVAL_DAY_HOUR = 23; - INTERVAL_DAY = 24; - INTERVAL_MONTH = 25; - INTERVAL_YEAR_MONTH = 26; - INTERVAL_YEAR = 27; - CHAR = 28; - VARCHAR = 29; - BINARY = 30; - VARBINARY = 31; - NULL = 32; - ARRAY = 33; - MAP = 34; - DOCUMENT = 35; - GRAPH = 36; - NODE = 37; - EDGE = 38; - PATH = 39; - IMAGE = 40; - VIDEO = 41; - AUDIO = 42; - FILE = 43; - DISTINCT = 44; - STRUCTURED = 45; - ROW = 46; - OTHER = 47; - CURSOR = 48; - COLUMN_LIST = 49; - DYNAMIC_STAR = 50; - GEOMETRY= 51; - SYMBOL = 52; - JSON = 53; - MULTISET = 54; - ANY = 55; - USER_DEFINED_TYPE = 56; - ROW_ID = 57; -} - -message ProtoValue { - oneof value { - ProtoBoolean boolean = 1; - ProtoInteger integer = 2; - ProtoLong long = 3; - ProtoBinary binary = 4; - ProtoDate date = 5; - ProtoDouble double = 6; - ProtoFloat float = 7; - ProtoString string = 8; - ProtoTime time = 9; - ProtoTimeStamp time_stamp = 10; - ProtoNull null = 11; - ProtoBigDecimal big_decimal = 12; - ProtoInterval interval = 13; - ProtoUserDefinedType user_defined_type = 14; - ProtoFile file = 15; - ProtoList list = 16; - ProtoMap map = 17; - ProtoDocument document = 18; - ProtoNode node = 19; - ProtoEdge edge = 20; - ProtoPath path = 21; // This is Graph! - ProtoGraph graph = 22; - ProtoRowId row_id= 23; - } -} - -message ProtoBoolean { - bool boolean = 1; -} - -message ProtoInteger { - int32 integer = 1; -} - -message ProtoLong { - int64 long = 1; -} - -message ProtoBinary { - bytes binary = 1; -} - -message ProtoDate { - int64 date = 1; -} - -message ProtoDouble { - double double = 1; -} - -message ProtoFloat { - float float = 1; -} - -message ProtoNull { -} - -message ProtoString { - string string = 1; -} - -message ProtoTime { - int32 value = 1; -} - -message ProtoTimeStamp { - int64 time_stamp = 1; -} - -message ProtoBigDecimal { - uint32 scale = 1; - uint32 precision = 2; - bytes unscaled_value = 3; -} - -message ProtoStructured { - repeated ProtoValue fields = 1; -} - -message ProtoInterval { - ProtoBigDecimal value = 1; -} - -message ProtoUserDefinedType { - map template = 2; - map value = 3; -} - -message ProtoFile { - bytes bytes = 1; -} - -message ProtoList { - repeated ProtoValue values = 1; -} - -message ProtoMap { - repeated ProtoEntry entries = 1; -} - -message ProtoEntry { - ProtoValue key = 1; - ProtoValue value = 2; -} - -message ProtoDocument { - repeated ProtoEntry entries = 1; -} - -message ProtoEdge { - ProtoGraphPropertyHolder graph_property_holder = 1; - ProtoString source = 2; - ProtoString target = 3; - EdgeDirection edge_direction = 4; - - enum EdgeDirection { - UNSPECIFIED = 0; - LEFT_TO_RIGHT = 1; - RIGHT_TO_LEFT = 2; - NONE = 3; - } -} - -message ProtoNode { - ProtoGraphPropertyHolder graph_property_holder = 1; -} - -message ProtoGraph { - ProtoString id = 1; - ProtoString variable_name = 2; - ProtoMap nodes = 3; - ProtoMap edges = 4; -} - -message ProtoPath { - ProtoList nodes = 1; - ProtoList edges = 2; - ProtoList names = 3; - // used directly instead of list as ProtoGraphPropertyHolder should not be used as ProtoValue - repeated ProtoGraphPropertyHolder paths = 5; - repeated ProtoSegment segments = 6; -} - -message ProtoSegment { - ProtoString id = 1; - ProtoString variable_name = 2; - ProtoString source_id = 3; - ProtoString edge_id = 4; - ProtoString target_id = 5; - ProtoNode source = 6; - ProtoEdge edge = 7; - ProtoNode target = 8; - bool is_ref = 9; - ProtoEdge.EdgeDirection edge_direction = 10; -} - -message ProtoGraphPropertyHolder { - ProtoString id = 1; - ProtoString variable_name = 2; - ProtoMap properties = 3; //Dictionary = ProtoMap - ProtoList labels = 4; //ProtoList -} - -message ProtoRowId { - string row_id = 1; -} diff --git a/plugins/proto-interface/src/test/java/org/polypheny/db/protointerface/NamedValueProcssorTest.java b/plugins/proto-interface/src/test/java/org/polypheny/db/protointerface/NamedValueProcssorTest.java deleted file mode 100644 index 876e98df17..0000000000 --- a/plugins/proto-interface/src/test/java/org/polypheny/db/protointerface/NamedValueProcssorTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2019-2023 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.protointerface; - -import static org.junit.Assert.fail; - -import java.util.HashMap; -import java.util.Map; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.polypheny.db.type.entity.PolyString; -import org.polypheny.db.type.entity.PolyValue; - - -public class NamedValueProcssorTest { - - @BeforeClass - public static void setUpClass() { - } - - - @AfterClass - public static void tearDownClass() { - } - - - @Before - public void setUp() { - } - - - @After - public void tearDown() { - } - - - @Test(expected = PIServiceException.class) - public void replacePlaceholders__missingValue() throws Exception { - final String statement = "select * from people where (first_name = :first_name or last_name= :last_name) and project = :project);"; - - final Map values = new HashMap<>(); - values.put( "first_name", PolyString.of( "tobias" ) ); - values.put( "last_name", PolyString.of( "hafner" ) ); - - //NamedValueProcessor namedValueProcessor = new NamedValueProcessor( statement ); - - - - fail( "No ProtoInterfaceServiceException thrown" ); - } - -} diff --git a/plugins/rest-interface/src/main/java/org/polypheny/db/restapi/RestInterfacePlugin.java b/plugins/rest-interface/src/main/java/org/polypheny/db/restapi/RestInterfacePlugin.java index 3621c52f07..a43ede1d6b 100644 --- a/plugins/rest-interface/src/main/java/org/polypheny/db/restapi/RestInterfacePlugin.java +++ b/plugins/rest-interface/src/main/java/org/polypheny/db/restapi/RestInterfacePlugin.java @@ -85,20 +85,16 @@ public RestInterfacePlugin( PluginContext context ) { } - @Override public void afterCatalogInit() { // Add REST interface - Map restSettings = new HashMap<>(); - restSettings.put( "port", "8089" ); - restSettings.put( "maxUploadSizeMb", "10000" ); - QueryInterfaceManager.addInterfaceType( "rest", HttpRestServer.class, restSettings ); + QueryInterfaceManager.addInterfaceTemplate( HttpRestServer.INTERFACE_NAME, HttpRestServer.INTERFACE_DESCRIPTION, HttpRestServer.AVAILABLE_SETTINGS, HttpRestServer::new ); } @Override public void stop() { - QueryInterfaceManager.removeInterfaceType( HttpRestServer.class ); + QueryInterfaceManager.removeInterfaceType( HttpRestServer.INTERFACE_NAME ); } @@ -133,8 +129,8 @@ public static class HttpRestServer extends QueryInterface { private Javalin restServer; - public HttpRestServer( TransactionManager transactionManager, Authenticator authenticator, long ifaceId, String uniqueName, Map settings ) { - super( transactionManager, authenticator, ifaceId, uniqueName, settings, true, false ); + public HttpRestServer( TransactionManager transactionManager, Authenticator authenticator, String uniqueName, Map settings ) { + super( transactionManager, authenticator, uniqueName, settings, true, false ); this.requestParser = new RequestParser( transactionManager, authenticator, Catalog.USER_NAME, Catalog.DATABASE_NAME ); this.uniqueName = uniqueName; this.port = Integer.parseInt( settings.get( "port" ) ); diff --git a/plugins/sql-language/src/main/codegen/Parser.jj b/plugins/sql-language/src/main/codegen/Parser.jj index 84a05dbdcb..80f59b9509 100644 --- a/plugins/sql-language/src/main/codegen/Parser.jj +++ b/plugins/sql-language/src/main/codegen/Parser.jj @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 The Polypheny Project + * 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. @@ -1950,17 +1950,33 @@ void TableElement(List list) : final String collation; SqlIdentifier refTable; SqlIdentifier refColumn; + final boolean primary; } { id = SimpleIdentifier() ( type = DataType() ( - { nullable = true; } + + { + primary = true; + nullable = false; + } | - { nullable = false; } + { + primary = false; + nullable = true; + } | - { nullable = true; } + { + primary = false; + nullable = false; + } + | + { + nullable = true; + primary = false; + } ) ( [ ] @@ -1998,6 +2014,9 @@ void TableElement(List list) : ) { list.add( SqlDdlNodes.column(s.add(id).end(this), id, type.withNullable(nullable), collation, e, strategy)); + if ( primary ) { + list.add( SqlDdlNodes.primary( s.add(id ).end( this ), id, new SqlNodeList( List.of( id ), s.end(this) ) ) ); + } } | { list.add(id); } diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java index af9ba9dcea..f4c224aa4f 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java @@ -2529,6 +2529,7 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec private static void register( OperatorName key, Operator operator ) { OperatorRegistry.register( key, operator ); + OperatorRegistry.register( QueryLanguage.from( "sql" ), key, operator ); } } diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/SqlDataTypeSpec.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/SqlDataTypeSpec.java index 7986732ab6..477fa7cfa6 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/SqlDataTypeSpec.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/SqlDataTypeSpec.java @@ -54,25 +54,17 @@ * * Currently it only supports simple datatypes like CHAR, VARCHAR and DOUBLE, with optional precision and scale. */ +@Getter public class SqlDataTypeSpec extends SqlNode implements DataTypeSpec { - @Getter private final SqlIdentifier collectionsTypeName; - @Getter private final SqlIdentifier typeName; - @Getter private final SqlIdentifier baseTypeName; - @Getter private final int scale; - @Getter private final int precision; - @Getter private final int dimension; - @Getter private final int cardinality; - @Getter private final String charSetName; - @Getter private final TimeZone timeZone; /** @@ -80,7 +72,6 @@ public class SqlDataTypeSpec extends SqlNode implements DataTypeSpec { *

* Nullable is nullable! Null means "not specified". E.g. {@code CAST(x AS INTEGER)} preserves has the same nullability as {@code x}. */ - @Getter private Boolean nullable; diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/SqlDialect.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/SqlDialect.java index 7030a525bc..e071b0e821 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/SqlDialect.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/SqlDialect.java @@ -24,6 +24,7 @@ import java.text.SimpleDateFormat; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; import lombok.NonNull; @@ -35,6 +36,7 @@ import org.apache.calcite.linq4j.function.Experimental; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; +import org.apache.calcite.linq4j.tree.ParameterExpression; import org.polypheny.db.algebra.AlgFieldCollation; import org.polypheny.db.algebra.constant.Kind; import org.polypheny.db.algebra.constant.NullCollation; @@ -58,7 +60,9 @@ import org.polypheny.db.type.BasicPolyType; import org.polypheny.db.type.PolyType; import org.polypheny.db.type.PolyTypeFactoryImpl; +import org.polypheny.db.type.entity.PolyBinary; import org.polypheny.db.type.entity.PolyString; +import org.polypheny.db.type.entity.category.PolyBlob; import org.polypheny.db.util.temporal.DateTimeUtils; import org.polypheny.db.util.temporal.TimeUnit; @@ -732,9 +736,12 @@ public boolean supportsComplexBinary() { } - public Expression getExpression( AlgDataType fieldType, Expression child ) { + public Expression handleRetrieval( AlgDataType fieldType, Expression child, ParameterExpression resultSet_, int index ) { + final String methodName = fieldType.isNullable() ? "ofNullable" : "of"; return switch ( fieldType.getPolyType() ) { - case TEXT -> Expressions.call( PolyString.class, fieldType.isNullable() ? "ofNullable" : "of", Expressions.convert_( child, String.class ) ); + case FILE, AUDIO, VIDEO, IMAGE -> Expressions.call( PolyBlob.class, methodName, Expressions.convert_( child, byte[].class ) ); + case TEXT -> Expressions.call( PolyString.class, methodName, Expressions.convert_( child, String.class ) ); + case VARBINARY -> Expressions.call( PolyBinary.class, "fromTypedJson", Expressions.convert_( child, String.class ), Expressions.constant( PolyBinary.class ) ); default -> child; }; @@ -779,6 +786,18 @@ public boolean handlesUtcIncorrectly() { } + /** + * Some adapters support different handling for missing length parameters. + * Like they allow for VARCHAR instead of VARCHAR(length) or VARCHAR VARYING if no length is provided. + * + * @param type the type for which a length is supported but not provided. + * @return the handling used if no length is provided, empty if no handling is necessary. + */ + public Optional handleMissingLength( PolyType type ) { + return Optional.empty(); + } + + public enum IntervalParameterStrategy {CAST, MULTIPLICATION, NONE} diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/ddl/SqlAlterInterfacesAdd.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/ddl/SqlAlterInterfacesAdd.java index d31ce825bd..2d487024f0 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/ddl/SqlAlterInterfacesAdd.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/ddl/SqlAlterInterfacesAdd.java @@ -24,7 +24,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.polypheny.db.algebra.constant.Kind; -import org.polypheny.db.catalog.Catalog; import org.polypheny.db.catalog.exceptions.GenericRuntimeException; import org.polypheny.db.iface.QueryInterfaceManager; import org.polypheny.db.languages.ParserPos; @@ -95,7 +94,7 @@ public void execute( Context context, Statement statement, ParsedQueryContext pa Map configMap = null; try { configMap = mapper.readValue( removeQuotationMarks( config.toString() ), Map.class ); - QueryInterfaceManager.getInstance().addQueryInterface( Catalog.getInstance(), clazzNameStr, uniqueNameStr, configMap ); + QueryInterfaceManager.getInstance().createQueryInterface( clazzNameStr, uniqueNameStr, configMap ); } catch ( Exception e ) { throw new GenericRuntimeException( "Unable to deploy query interface", e ); } diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/pretty/SqlPrettyWriter.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/pretty/SqlPrettyWriter.java index ae59a74f21..5983e8e259 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/pretty/SqlPrettyWriter.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/pretty/SqlPrettyWriter.java @@ -145,25 +145,25 @@ public class SqlPrettyWriter implements SqlWriter { @Setter private boolean selectListItemsOnSeparateLines; /** - * Sets whether to use a fix for SELECT list indentations. - *

    - *
  • If set to "false": - *
    -     *  SELECT
    -     *      A as A
    -     *          B as B
    -     *          C as C
    -     *      D
    -     *  
  • - *
  • If set to "true": - *
    -     *  SELECT
    -     *      A as A
    -     *      B as B
    -     *      C as C
    -     *      D
    -     *  
  • - *
+ * Sets whether to use a fix for SELECT list indentations. + *
    + *
  • If set to "false": + *
    +     * SELECT
    +     *     A as A
    +     *         B as B
    +     *         C as C
    +     *     D
    +     * 
  • + *
  • If set to "true": + *
    +     * SELECT
    +     *     A as A
    +     *     B as B
    +     *     C as C
    +     *     D
    +     * 
  • + *
*/ @Setter private boolean selectListExtraIndentFlag; @@ -177,13 +177,13 @@ public class SqlPrettyWriter implements SqlWriter { @Setter private SubQueryStyle subQueryStyle; /** - * Sets whether to print a newline before each AND or OR (whichever is higher level) in WHERE clauses. NOTE: Ignored when alwaysUseParentheses is set to true. + * Sets whether to print a newline before each AND or OR (whichever is higher level) in WHERE clauses. NOTE: Ignored when alwaysUseParentheses is set to true. */ @Setter private boolean whereListItemsOnSeparateLines; /** - * Sets whether the WHEN, THEN and ELSE clauses of a CASE expression appear at the start of a new line. The default is false. + * Sets whether the WHEN, THEN and ELSE clauses of a CASE expression appear at the start of a new line. The default is false. */ @Setter private boolean caseClausesOnNewLines; @@ -352,6 +352,8 @@ public void newlineAndIndent() { void indent( int indent ) { if ( indent < 0 ) { throw new IllegalArgumentException( "negative indent " + indent ); + } else if ( indent == 0 ) { + return; } Spaces.append( pw, indent ); charCount += indent; diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/validate/SqlValidatorImpl.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/validate/SqlValidatorImpl.java index a6c1f9dea1..5e1d115edf 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/validate/SqlValidatorImpl.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/validate/SqlValidatorImpl.java @@ -1646,7 +1646,7 @@ protected void inferUnknownTypes( @Nonnull AlgDataType inferredType, @Nonnull Sq if ( (node instanceof SqlDynamicParam) || isNullLiteral ) { if ( inferredType.equals( unknownType ) ) { if ( isNullLiteral ) { - throw newValidationError( node, RESOURCE.nullIllegal() ); + inferredType = typeFactory.createTypeWithNullability( typeFactory.createPolyType( PolyType.ANY ), true ); } else { throw newValidationError( node, RESOURCE.dynamicParamIllegal() ); } @@ -5381,7 +5381,7 @@ public SqlNode visit( Literal literal ) { final int intValue = literal.intValue( false ); if ( intValue >= 0 ) { if ( intValue < 1 || intValue > aliasList.size() ) { - throw newValidationError( (Node) literal, RESOURCE.orderByOrdinalOutOfRange() ); + throw newValidationError( literal, RESOURCE.orderByOrdinalOutOfRange() ); } // SQL ordinals are 1-based, but Sort's are 0-based diff --git a/plugins/sql-language/src/test/java/org/polypheny/db/sql/RexExecutorTest.java b/plugins/sql-language/src/test/java/org/polypheny/db/sql/RexExecutorTest.java index c4e5a4dd41..19e4b13b3a 100644 --- a/plugins/sql-language/src/test/java/org/polypheny/db/sql/RexExecutorTest.java +++ b/plugins/sql-language/src/test/java/org/polypheny/db/sql/RexExecutorTest.java @@ -36,6 +36,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.calcite.avatica.util.ByteString; import org.apache.calcite.linq4j.QueryProvider; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.polypheny.db.adapter.DataContext; import org.polypheny.db.adapter.java.JavaTypeFactory; @@ -362,7 +363,7 @@ public Statement getStatement() { @Override - public void addParameterValues( long index, AlgDataType type, List data ) { + public void addParameterValues( long index, @NotNull AlgDataType type, List data ) { throw new UnsupportedOperationException(); } diff --git a/plugins/sql-language/src/test/java/org/polypheny/db/sql/SqlLanguagePluginTest.java b/plugins/sql-language/src/test/java/org/polypheny/db/sql/SqlLanguagePluginTest.java index b887ce0de3..674d189509 100644 --- a/plugins/sql-language/src/test/java/org/polypheny/db/sql/SqlLanguagePluginTest.java +++ b/plugins/sql-language/src/test/java/org/polypheny/db/sql/SqlLanguagePluginTest.java @@ -53,7 +53,7 @@ public QueryContext getContext( String query ) { public void testQueryWithSemicolon() { QueryContext context = getContext( "SELECT * FROM employee WHERE ename = ';'" ); - List res = sql.splitter().apply( context ); + List res = sql.parser().apply( context ); assertEquals( 1, res.size() ); } @@ -62,7 +62,7 @@ public void testQueryWithSemicolon() { public void testTwoQueries() { QueryContext context = getContext( "SELECT * FROM employee WHERE ename = 'a'; SELECT * FROM employee WHERE ename = 'b'" ); - List res = sql.splitter().apply( context ); + List res = sql.parser().apply( context ); assertEquals( 2, res.size() ); } @@ -71,7 +71,7 @@ public void testTwoQueries() { public void testTwoQueriesWithSemicolon() { QueryContext context = getContext( "SELECT * FROM employee WHERE ename = ';'; SELECT * FROM employee WHERE ename = ';'" ); - List res = sql.splitter().apply( context ); + List res = sql.parser().apply( context ); assertEquals( 2, res.size() ); } diff --git a/settings.gradle b/settings.gradle index bfd1aee10c..550a4704c5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ plugins { - id("org.gradle.toolchains.foojay-resolver") version "0.7.0" + id("org.gradle.toolchains.foojay-resolver") version "0.8.0" } rootProject.name = 'Polypheny-DB' @@ -43,6 +43,7 @@ include 'plugins:jdbc-adapter-framework' include 'plugins:avatica-interface' include 'plugins:rest-interface' include 'plugins:http-interface' +include 'plugins:prism-interface' // adapters plugins include 'plugins:hsqldb-adapter' diff --git a/webui/src/main/java/org/polypheny/db/webui/Crud.java b/webui/src/main/java/org/polypheny/db/webui/Crud.java index b6a9d2d86e..3bf2998b87 100644 --- a/webui/src/main/java/org/polypheny/db/webui/Crud.java +++ b/webui/src/main/java/org/polypheny/db/webui/Crud.java @@ -131,17 +131,17 @@ import org.polypheny.db.docker.DockerSetupHelper; import org.polypheny.db.docker.HandshakeManager; import org.polypheny.db.docker.exceptions.DockerUserException; -import org.polypheny.db.docker.models.CreateDockerResponse; import org.polypheny.db.docker.models.AutoDockerResult; import org.polypheny.db.docker.models.CreateDockerRequest; +import org.polypheny.db.docker.models.CreateDockerResponse; import org.polypheny.db.docker.models.DockerSettings; -import org.polypheny.db.docker.models.UpdateDockerRequest; import org.polypheny.db.docker.models.HandshakeInfo; import org.polypheny.db.docker.models.InstancesAndAutoDocker; +import org.polypheny.db.docker.models.UpdateDockerRequest; import org.polypheny.db.iface.QueryInterface; import org.polypheny.db.iface.QueryInterfaceManager; -import org.polypheny.db.iface.QueryInterfaceManager.QueryInterfaceInformation; import org.polypheny.db.iface.QueryInterfaceManager.QueryInterfaceInformationRequest; +import org.polypheny.db.iface.QueryInterfaceManager.QueryInterfaceTemplate; import org.polypheny.db.information.InformationGroup; import org.polypheny.db.information.InformationManager; import org.polypheny.db.information.InformationObserver; @@ -2219,47 +2219,42 @@ void getQueryInterfaces( final Context ctx ) { void getAvailableQueryInterfaces( final Context ctx ) { QueryInterfaceManager qim = QueryInterfaceManager.getInstance(); - List interfaces = qim.getAvailableQueryInterfaceTypes(); - ctx.result( QueryInterfaceInformation.toJson( interfaces.toArray( new QueryInterfaceInformation[0] ) ) ); + List interfaces = qim.getAvailableQueryInterfaceTemplates(); + ctx.json( interfaces ); } void addQueryInterface( final Context ctx ) { - QueryInterfaceManager qim = QueryInterfaceManager.getInstance(); QueryInterfaceInformationRequest request = ctx.bodyAsClass( QueryInterfaceInformationRequest.class ); - String generatedQuery = String.format( "ALTER INTERFACES ADD \"%s\" USING '%s' WITH '%s'", request.uniqueName, request.clazzName, gson.toJson( request.currentSettings ) ); try { - qim.addQueryInterface( Catalog.getInstance(), request.clazzName, request.uniqueName, request.currentSettings ); - ctx.json( RelationalResult.builder().affectedTuples( 1 ).query( generatedQuery ).build() ); + QueryInterfaceManager.getInstance().createQueryInterface( request.interfaceName(), request.uniqueName(), request.currentSettings() ); + ctx.status( 200 ); } catch ( RuntimeException e ) { log.error( "Exception while deploying query interface", e ); - ctx.json( RelationalResult.builder().error( e.getMessage() ).query( generatedQuery ).build() ); + ctx.status( 500 ).result( e.getMessage() ); } } void updateQueryInterfaceSettings( final Context ctx ) { QueryInterfaceModel request = ctx.bodyAsClass( QueryInterfaceModel.class ); - QueryInterfaceManager qim = QueryInterfaceManager.getInstance(); try { - qim.getQueryInterface( request.uniqueName ).updateSettings( request.currentSettings ); - ctx.json( RelationalResult.builder().affectedTuples( 1 ).build() ); + QueryInterfaceManager.getInstance().getQueryInterface( request.uniqueName ).updateSettings( request.currentSettings ); + ctx.status( 200 ); } catch ( Exception e ) { - ctx.json( RelationalResult.builder().error( e.getMessage() ).build() ); + ctx.status( 500 ).result( e.getMessage() ); } } void removeQueryInterface( final Context ctx ) { String uniqueName = ctx.body(); - QueryInterfaceManager qim = QueryInterfaceManager.getInstance(); - String generatedQuery = String.format( "ALTER INTERFACES DROP \"%s\"", uniqueName ); try { - qim.removeQueryInterface( Catalog.getInstance(), uniqueName ); - ctx.json( RelationalResult.builder().affectedTuples( 1 ).query( generatedQuery ).build() ); + QueryInterfaceManager.getInstance().removeQueryInterface( Catalog.getInstance(), uniqueName ); + ctx.status( 200 ); } catch ( RuntimeException e ) { log.error( "Could not remove query interface {}", ctx.body(), e ); - ctx.json( RelationalResult.builder().error( e.getMessage() ).query( generatedQuery ).build() ); + ctx.status( 500 ).result( e.getMessage() ); } } diff --git a/webui/src/main/java/org/polypheny/db/webui/HttpServer.java b/webui/src/main/java/org/polypheny/db/webui/HttpServer.java index e291e4712e..9bbfef4308 100644 --- a/webui/src/main/java/org/polypheny/db/webui/HttpServer.java +++ b/webui/src/main/java/org/polypheny/db/webui/HttpServer.java @@ -35,6 +35,7 @@ import java.util.function.BiFunction; import java.util.function.Consumer; import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.polypheny.db.StatusNotificationService; import org.polypheny.db.catalog.exceptions.GenericRuntimeException; @@ -59,6 +60,8 @@ public class HttpServer implements Runnable { private static HttpServer INSTANCE = null; @Getter private WebSocket webSocketHandler; + @Setter + private boolean isReady = false; public static HttpServer getInstance() { @@ -283,6 +286,8 @@ private void attachRoutes( Javalin webuiServer, Crud crud ) { webuiServer.get( "/product", ctx -> ctx.result( "Polypheny-DB" ) ); + webuiServer.get( "/isReady", ctx -> ctx.result( String.valueOf( isReady ) ) ); + } diff --git a/webui/src/main/java/org/polypheny/db/webui/models/AssetsModel.java b/webui/src/main/java/org/polypheny/db/webui/models/AssetsModel.java index 4d4c135f8b..b80d7f1057 100644 --- a/webui/src/main/java/org/polypheny/db/webui/models/AssetsModel.java +++ b/webui/src/main/java/org/polypheny/db/webui/models/AssetsModel.java @@ -35,4 +35,4 @@ public class AssetsModel { public final String SOURCE_ICON = "fa fa-plug"; -} +} \ No newline at end of file