diff --git a/core/src/main/java/org/polypheny/db/algebra/AbstractAlgNode.java b/core/src/main/java/org/polypheny/db/algebra/AbstractAlgNode.java index 92f1dca238..3cac66e936 100644 --- a/core/src/main/java/org/polypheny/db/algebra/AbstractAlgNode.java +++ b/core/src/main/java/org/polypheny/db/algebra/AbstractAlgNode.java @@ -110,6 +110,7 @@ public abstract class AbstractAlgNode implements AlgNode { /** * Cached type of this relational expression. */ + @Getter protected AlgDataType rowType; /** diff --git a/core/src/main/java/org/polypheny/db/algebra/core/relational/RelModify.java b/core/src/main/java/org/polypheny/db/algebra/core/relational/RelModify.java index 72dc53cd38..775b78f633 100644 --- a/core/src/main/java/org/polypheny/db/algebra/core/relational/RelModify.java +++ b/core/src/main/java/org/polypheny/db/algebra/core/relational/RelModify.java @@ -26,6 +26,8 @@ import org.polypheny.db.algebra.AlgWriter; import org.polypheny.db.algebra.constant.Kind; import org.polypheny.db.algebra.core.common.Modify; +import org.polypheny.db.algebra.logical.relational.LogicalRelModify; +import org.polypheny.db.algebra.logical.relational.LogicalRelValues; import org.polypheny.db.algebra.metadata.AlgMetadataQuery; import org.polypheny.db.algebra.type.AlgDataType; import org.polypheny.db.algebra.type.AlgDataTypeFactory; @@ -37,7 +39,10 @@ import org.polypheny.db.plan.AlgTraitSet; import org.polypheny.db.rex.RexNode; import org.polypheny.db.schema.trait.ModelTrait; +import org.polypheny.db.transaction.locking.IdentifierUtils; import org.polypheny.db.type.PolyTypeUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -54,6 +59,7 @@ */ public abstract class RelModify extends Modify implements RelAlg { + private static final Logger LOGGER = LoggerFactory.getLogger( RelModify.class ); /** * The table definition. @@ -99,17 +105,33 @@ protected RelModify( this.operation = operation; this.updateColumns = updateColumns; this.sourceExpressions = sourceExpressions; - if ( operation == Operation.UPDATE ) { - Objects.requireNonNull( updateColumns ); - Objects.requireNonNull( sourceExpressions ); - Preconditions.checkArgument( sourceExpressions.size() == updateColumns.size() ); - } else { - Preconditions.checkArgument( updateColumns == null ); - Preconditions.checkArgument( sourceExpressions == null ); + switch(operation) { + case UPDATE -> { + Objects.requireNonNull( updateColumns ); + Objects.requireNonNull( sourceExpressions ); + Preconditions.checkArgument( sourceExpressions.size() == updateColumns.size() ); + } + case INSERT -> { + Preconditions.checkArgument( updateColumns == null ); + Preconditions.checkArgument( sourceExpressions == null ); + addIdentifiers(); + } + default -> { + Preconditions.checkArgument( updateColumns == null ); + Preconditions.checkArgument( sourceExpressions == null ); + } } this.flattened = flattened; } + private void addIdentifiers() { + if (!(input instanceof LogicalRelValues values) ) { + LOGGER.warn("New source type detected: {}", input); + return; + } + input = IdentifierUtils.overwriteIdentifierInInput( values ); + } + public boolean isInsert() { return operation == Operation.INSERT; diff --git a/core/src/main/java/org/polypheny/db/rex/RexLiteral.java b/core/src/main/java/org/polypheny/db/rex/RexLiteral.java index 925a87830a..abd8d2318f 100644 --- a/core/src/main/java/org/polypheny/db/rex/RexLiteral.java +++ b/core/src/main/java/org/polypheny/db/rex/RexLiteral.java @@ -46,6 +46,7 @@ import java.util.Objects; import java.util.stream.Collectors; import lombok.Getter; +import lombok.Setter; import lombok.Value; import org.polypheny.db.algebra.AlgNode; import org.polypheny.db.algebra.constant.Kind; @@ -57,6 +58,7 @@ import org.polypheny.db.type.entity.PolyValue; import org.polypheny.db.type.entity.category.PolyNumber; import org.polypheny.db.type.entity.numerical.PolyBigDecimal; +import org.polypheny.db.type.entity.numerical.PolyLong; import org.polypheny.db.util.Collation; import org.polypheny.db.util.NlsString; import org.polypheny.db.util.Pair; @@ -189,6 +191,21 @@ public RexLiteral( PolyValue value, AlgDataType type, PolyType polyType ) { this.digest = computeDigest( RexDigestIncludeType.OPTIONAL ); } + public RexLiteral( PolyValue value, AlgDataType type, PolyType polyType, String digest ) { + this.value = value; + this.type = Objects.requireNonNull( type ); + this.polyType = Objects.requireNonNull( polyType ); + if ( !valueMatchesType( value, polyType, true ) ) { + System.err.println( value ); + System.err.println( value.getClass().getCanonicalName() ); + System.err.println( type ); + System.err.println( polyType ); + throw new IllegalArgumentException(); + } + Preconditions.checkArgument( (value != null) || type.isNullable() ); + Preconditions.checkArgument( polyType != PolyType.ANY ); + this.digest = digest; + } public RexLiteral( PolyValue value, AlgDataType type, PolyType polyType, boolean raw ) { this.value = value; @@ -424,7 +441,11 @@ private static void printAsJava( PolyValue value, PrintWriter pw, PolyType typeN pw.print( value.asBoolean().value ); break; case DECIMAL: - assert value.isBigDecimal(); + assert value.isBigDecimal() || value.isLong(); + if (value.isLong()) { + pw.print( value.asLong().value ); + break; + } pw.print( value.asBigDecimal().value ); break; case DOUBLE: @@ -662,6 +683,5 @@ public PolyValue getValue( AlgDataType type ) { } return value; } - } diff --git a/core/src/main/java/org/polypheny/db/rex/RexNode.java b/core/src/main/java/org/polypheny/db/rex/RexNode.java index 3e00744e88..cd5d7692ed 100644 --- a/core/src/main/java/org/polypheny/db/rex/RexNode.java +++ b/core/src/main/java/org/polypheny/db/rex/RexNode.java @@ -35,6 +35,7 @@ import java.util.Collection; +import lombok.Getter; import org.polypheny.db.algebra.constant.Kind; import org.polypheny.db.algebra.type.AlgDataType; import org.polypheny.db.nodes.Node; @@ -54,6 +55,7 @@ public abstract class RexNode implements Wrapper { // Effectively final. Set in each sub-class constructor, and never re-set. + @Getter protected String digest; diff --git a/core/src/main/java/org/polypheny/db/transaction/locking/IdentifierRegistry.java b/core/src/main/java/org/polypheny/db/transaction/locking/IdentifierRegistry.java index 1a1363cc6d..a7d8bcd10b 100644 --- a/core/src/main/java/org/polypheny/db/transaction/locking/IdentifierRegistry.java +++ b/core/src/main/java/org/polypheny/db/transaction/locking/IdentifierRegistry.java @@ -6,10 +6,11 @@ public class IdentifierRegistry { private static final Long MAX_IDENTIFIER_VALUE = Long.MAX_VALUE; - public static final IdentifierRegistry INSTANCE = new IdentifierRegistry(MAX_IDENTIFIER_VALUE); + public static final IdentifierRegistry INSTANCE = new IdentifierRegistry( MAX_IDENTIFIER_VALUE ); private final TreeSet availableIdentifiers; + IdentifierRegistry( long maxIdentifierValue ) { this.availableIdentifiers = new TreeSet<>(); this.availableIdentifiers.add( new IdentifierInterval( 0, maxIdentifierValue ) ); diff --git a/core/src/main/java/org/polypheny/db/transaction/locking/IdentifierUtils.java b/core/src/main/java/org/polypheny/db/transaction/locking/IdentifierUtils.java index c2ed2d58a9..144b2ce555 100644 --- a/core/src/main/java/org/polypheny/db/transaction/locking/IdentifierUtils.java +++ b/core/src/main/java/org/polypheny/db/transaction/locking/IdentifierUtils.java @@ -16,14 +16,26 @@ package org.polypheny.db.transaction.locking; +import com.google.common.collect.ImmutableList; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; +import org.polypheny.db.algebra.AlgNode; +import org.polypheny.db.algebra.logical.relational.LogicalRelValues; import org.polypheny.db.catalog.logistic.Collation; import org.polypheny.db.ddl.DdlManager.ColumnTypeInformation; import org.polypheny.db.ddl.DdlManager.FieldInformation; +import org.polypheny.db.rex.RexLiteral; import org.polypheny.db.type.PolyType; +import org.polypheny.db.type.entity.numerical.PolyBigDecimal; +import org.polypheny.db.type.entity.numerical.PolyLong; public class IdentifierUtils { public static final String IDENTIFIER_KEY = "_eid"; + public static final long MISSING_IDENTIFIER = 0; public static final ColumnTypeInformation IDENTIFIER_COLUMN_TYPE = new ColumnTypeInformation( PolyType.BIGINT, // binary not supported by hsqldb TODO TH: check for other stores, datatypes @@ -39,8 +51,66 @@ public class IdentifierUtils { IDENTIFIER_KEY, IDENTIFIER_COLUMN_TYPE, Collation.CASE_INSENSITIVE, - null, + new PolyLong( MISSING_IDENTIFIER ), 1 ); + public static PolyLong getIdentifier() { + return new PolyLong( IdentifierRegistry.INSTANCE.getEntryIdentifier() ); + } + + public static void throwIllegalFieldName() { + throw new IllegalArgumentException( MessageFormat.format( + "The field {0} is reserved for internal use and cannot be used.", + IdentifierUtils.IDENTIFIER_KEY) + ); + } + + public static AlgNode overwriteIdentifierInInput( LogicalRelValues value ) { + List> newValues = new ArrayList<>(); + value.tuples.forEach(row -> { + List newRow = new ArrayList<>(row); + RexLiteral identifierLiteral = newRow.get(0); + newRow.set(0, IdentifierUtils.copyAndUpdateIdentifier(identifierLiteral, getIdentifier())); + newValues.add(newRow); + }); + + ImmutableList> immutableValues = new ImmutableList.Builder>() + .addAll(newValues.stream() + .map(ImmutableList::copyOf) + .toList()) + .build(); + + return new LogicalRelValues( + value.getCluster(), + value.getTraitSet(), + value.getRowType(), + immutableValues + ); + } + + private static RexLiteral copyAndUpdateIdentifier( RexLiteral identifierLiteral, PolyLong identifier ) { + return new RexLiteral(identifier, identifierLiteral.getType(), PolyType.DECIMAL); + } + + + public static List addIdentifierFieldIfAbsent( List fields ) { + if (fields.get(0).name().equals( IDENTIFIER_KEY )){ + return fields; + } + List newFields = fields.stream() + .map( f -> new FieldInformation(f.name(), f.typeInformation(), f.collation(), f.defaultValue(), f.position() + 1) ) + .collect( Collectors.toCollection( LinkedList::new ) ); + newFields.add(0, IDENTIFIER_FIELD_INFORMATION ); + return newFields; + } + + public static void throwIfContainsIdentifierField(List fields) { + if (fields.stream().noneMatch( f -> f.name().equals(IDENTIFIER_FIELD_INFORMATION.name()))) { + return; + + } + throwIllegalFieldName(); + } + } 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 f17e65b2f5..124584581a 100644 --- a/dbms/src/main/java/org/polypheny/db/ddl/DdlManagerImpl.java +++ b/dbms/src/main/java/org/polypheny/db/ddl/DdlManagerImpl.java @@ -110,10 +110,10 @@ import org.polypheny.db.routing.RoutingManager; import org.polypheny.db.transaction.Statement; import org.polypheny.db.transaction.TransactionException; +import org.polypheny.db.transaction.locking.IdentifierUtils; import org.polypheny.db.type.ArrayType; import org.polypheny.db.type.PolyType; import org.polypheny.db.type.entity.PolyValue; -import org.polypheny.db.type.entity.numerical.PolyLong; import org.polypheny.db.util.Pair; import org.polypheny.db.view.MaterializedViewManager; @@ -2056,8 +2056,11 @@ public void createTable( long namespaceId, String name, List f true ); // addLColumns - Map ids = new HashMap<>(); + + IdentifierUtils.throwIfContainsIdentifierField( fields ); + fields = IdentifierUtils.addIdentifierFieldIfAbsent(fields); + for ( FieldInformation information : fields ) { ids.put( information.name(), addColumn( namespaceId, information.name(), information.typeInformation(), information.collation(), information.defaultValue(), logical.id, information.position() + 1 ) ); // pos + 1 to make space for entry identifier column } diff --git a/dbms/src/test/java/org/polypheny/db/transaction/EntityIdentifierTests.java b/dbms/src/test/java/org/polypheny/db/transaction/EntityIdentifierTests.java new file mode 100644 index 0000000000..8902db108e --- /dev/null +++ b/dbms/src/test/java/org/polypheny/db/transaction/EntityIdentifierTests.java @@ -0,0 +1,717 @@ +/* + * 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.transaction; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.polypheny.db.TestHelper; +import org.polypheny.db.TestHelper.JdbcConnection; +import org.polypheny.db.transaction.locking.IdentifierUtils; +import org.polypheny.jdbc.PrismInterfaceServiceException; + +public class EntityIdentifierTests { + + private static TestHelper testHelper; + private static String identifierKey; + + + @BeforeAll + public static void start() throws SQLException { + testHelper = TestHelper.getInstance(); + identifierKey = IdentifierUtils.IDENTIFIER_KEY; + } + + + @Test + public void testCreateTable() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertUnparameterizedWithColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers (a, b) VALUES (1, 2)" ); + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertPreparedWithColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + PreparedStatement preparedStatement = connection.prepareStatement( "INSERT INTO identifiers (a, b) VALUES (?, ?)" ); + preparedStatement.setInt( 1, 1 ); + preparedStatement.setInt( 2, 2 ); + preparedStatement.executeUpdate(); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertMultipleUnparameterizedWithColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers (a, b) VALUES (1, 2), (3, 4)" ); + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertMultiplePreparedWithColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + PreparedStatement preparedStatement = connection.prepareStatement( "INSERT INTO identifiers (a, b) VALUES (?, ?)" ); + preparedStatement.setInt( 1, 1 ); + preparedStatement.setInt( 2, 2 ); + preparedStatement.addBatch(); + preparedStatement.setInt( 1, 3 ); + preparedStatement.setInt( 2, 4 ); + preparedStatement.executeBatch(); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + // This currently fails due to a mismatch between the column names in the table which are mistakenly compared somehow + @Test + public void testInsertFromTableWithDifferentColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers1 (a, b) VALUES (1, 2), (3, 4)" ); + + statement.executeUpdate( "CREATE TABLE identifiers2 (x INTEGER NOT NULL, y INTEGER, PRIMARY KEY (x))" ); + statement.executeUpdate( "INSERT INTO identifiers2 (x, y) SELECT a, b FROM identifiers1" ); + + //TODO TH: check that new identifiers had been assigned instead of copying from the first table + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + statement.executeUpdate( "DROP TABLE identifiers2" ); + connection.commit(); + } + } + } + } + + @Test + public void testInsertFromTableWithoutTargetColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers1 (a, b) VALUES (1, 2), (3, 4)" ); + + statement.executeUpdate( "CREATE TABLE identifiers2 (x INTEGER NOT NULL, y INTEGER, PRIMARY KEY (x))" ); + statement.executeUpdate( "INSERT INTO identifiers1 SELECT a, b FROM identifiers2" ); + + //TODO TH: check that new identifiers had been assigned instead of copying from the first table + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + statement.executeUpdate( "DROP TABLE identifiers2" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertFromTableWithoutColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers1 (a, b) VALUES (1, 2), (3, 4)" ); + + statement.executeUpdate( "CREATE TABLE identifiers2 (x INTEGER NOT NULL, y INTEGER, PRIMARY KEY (x))" ); + statement.executeUpdate( "INSERT INTO identifiers1 SELECT * FROM identifiers2" ); + + //TODO TH: check that new identifiers had been assigned instead of copying from the first table + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + statement.executeUpdate( "DROP TABLE identifiers2" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertUnparameterizedDefaultExplicit() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers1 (a, b) VALUES (1, DEFAULT)" ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertUnparameterizedDefaultOmitted() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers1 (a) VALUES (1)" ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertUnparameterizedDefaultExplicitNoColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers1 VALUES (1, DEFAULT)" ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertParameterizedDefaultExplicit() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + + String insertSql = "INSERT INTO identifiers1 (a, b) VALUES (?, DEFAULT)"; + try ( PreparedStatement preparedStatement = connection.prepareStatement( insertSql ) ) { + preparedStatement.setInt( 1, 1 ); + preparedStatement.executeUpdate(); + } + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertParameterizedDefaultOmitted() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + + String insertSql = "INSERT INTO identifiers1 (a) VALUES (?)"; + try ( PreparedStatement preparedStatement = connection.prepareStatement( insertSql ) ) { + preparedStatement.setInt( 1, 1 ); + preparedStatement.executeUpdate(); + } + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertParameterizedDefaultExplicitNoColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + String insertSql = "INSERT INTO identifiers1 VALUES (?, DEFAULT)"; + try ( PreparedStatement preparedStatement = connection.prepareStatement( insertSql ) ) { + preparedStatement.setInt( 1, 1 ); // Set value for 'a' + preparedStatement.executeUpdate(); + } + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertMultipleUnparameterizedDefaultExplicit() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers1 (a, b) VALUES (1, DEFAULT), (2, 3), (3, DEFAULT)" ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertMultipleUnparameterizedDefaultExplicitNoColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers1 VALUES (1, DEFAULT), (2, 3), (3, DEFAULT)" ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertMultipleUnparameterizedDefaultOmitted() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers1 (a) VALUES (1), (2), (3)" ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertMultipleParameterizedDefaultExplicit() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + + String insertSql = "INSERT INTO identifiers1 (a, b) VALUES (?, DEFAULT), (?, ?), (?, DEFAULT)"; + try ( PreparedStatement preparedStatement = connection.prepareStatement( insertSql ) ) { + preparedStatement.setInt( 1, 1 ); + preparedStatement.setInt( 2, 2 ); + preparedStatement.setInt( 3, 3 ); + preparedStatement.setInt( 4, 3 ); + preparedStatement.executeUpdate(); + } + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertMultipleParameterizedDefaultExplicitNoColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + + String insertSql = "INSERT INTO identifiers1 VALUES (?, DEFAULT), (?, ?), (?, DEFAULT)"; + try ( PreparedStatement preparedStatement = connection.prepareStatement( insertSql ) ) { + preparedStatement.setInt( 1, 1 ); + preparedStatement.setInt( 2, 2 ); + preparedStatement.setInt( 3, 3 ); + preparedStatement.setInt( 4, 3 ); + preparedStatement.executeUpdate(); + } + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertMultipleParameterizedDefaultOmitted() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers1 (a INTEGER NOT NULL, b INTEGER DEFAULT 42, PRIMARY KEY (a))" ); + + String insertSql = "INSERT INTO identifiers1 (a) VALUES (?), (?), (?)"; + try ( PreparedStatement preparedStatement = connection.prepareStatement( insertSql ) ) { + preparedStatement.setInt( 1, 1 ); + preparedStatement.setInt( 2, 2 ); + preparedStatement.setInt( 3, 3 ); + preparedStatement.executeUpdate(); + } + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers1" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertUnparameterizedNoColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers VALUES (1, 2)" ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertPreparedNoColumnNames() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + String insertSql = "INSERT INTO table_name VALUES (?, ?)"; + try ( PreparedStatement preparedStatement = connection.prepareStatement( insertSql ) ) { + preparedStatement.setInt( 1, 1 ); + preparedStatement.setInt( 2, 2 ); + preparedStatement.executeUpdate(); + } + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE table_name" ); + connection.commit(); + } + } + } + } + + + @Test + public void testInsertUnparameterizedColumnNameConflictSameType() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "CREATE TABLE identifiers (_eid BIGINT, a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ) + ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE IF EXISTS identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + //TODO TH: should fail + public void testInsertUnparameterizedColumnNameConflictDifferentType() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + assertThrows( + PrismInterfaceServiceException.class, + () -> statement.executeUpdate( "CREATE TABLE identifiers (_eid VARCHAR(15), a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ) + ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE IF EXISTS identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + //TODO TH: should fail + public void testInsertUnparameterizedIdentifierManipulationInsert() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers VALUES (1, 2, 3)" ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + //TODO TH: should fail + public void testUpdateUnparameterizedIdentifierManipulation() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers VALUES (1, 2)" ); + statement.executeUpdate( "UPDATE identifiers SET _eid = 1 WHERE a = 1 AND b = 2" ); + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + //TODO TH: should fail + public void testDropIdentifierColumnUnparameterized() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers VALUES (1, 2)" ); + statement.executeUpdate( "ALTER TABLE identifiers DROP COLUMN _eid" ); + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + //TODO TH: should fail + public void testAddIdentifierColumnUnparameterized() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "ALTER TABLE identifiers DROP COLUMN _eid" ); // this should already fail here + statement.executeUpdate( "ALTER TABLE identifiers ADD COLUMN _eid BIGINT" ); + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + //TODO TH: should fail + public void testDropMulitpleColumnUnparameterized() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "INSERT INTO identifiers VALUES (1, 2)" ); + statement.executeUpdate( "ALTER TABLE identifiers DROP COLUMN _eid DROP COLUMN b" ); + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + //TODO TH: should fail + public void testAddMulitpleColumnUnparameterized() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "ALTER TABLE identifiers ADD COLUMN _eid BIGINT ADD COLUMN c INTEGER" ); + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + //TODO TH: should fail + public void testRenameIdentifierColumnUnparameterized() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "ALTER TABLE identifiers RENAME COLUMN _eid TO nowItsBroken" ); + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + + @Test + //TODO TH: should fail + public void testChangeDataTypeOfIdentifierColumnUnparameterized() throws SQLException { + try ( JdbcConnection jdbcConnection = new JdbcConnection( true ) ) { + Connection connection = jdbcConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + try { + statement.executeUpdate( "CREATE TABLE identifiers (a INTEGER NOT NULL, b INTEGER, PRIMARY KEY (a))" ); + statement.executeUpdate( "ALTER TABLE identifiers MODIFY COLUMN _eid SET TYPE VARCHAR(15)" ); + + connection.commit(); + } finally { + statement.executeUpdate( "DROP TABLE identifiers" ); + connection.commit(); + } + } + } + } + + +} diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlProcessor.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlProcessor.java index 715bbb73df..f688807f82 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlProcessor.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlProcessor.java @@ -29,6 +29,7 @@ import org.polypheny.db.algebra.constant.ExplainFormat; import org.polypheny.db.algebra.constant.ExplainLevel; import org.polypheny.db.algebra.constant.Kind; +import org.polypheny.db.algebra.core.relational.RelModify; import org.polypheny.db.algebra.type.AlgDataType; import org.polypheny.db.catalog.Catalog; import org.polypheny.db.catalog.entity.LogicalDefaultValue; @@ -70,7 +71,6 @@ import org.polypheny.db.tools.AlgBuilder; import org.polypheny.db.transaction.Statement; import org.polypheny.db.transaction.Transaction; -import org.polypheny.db.transaction.locking.IdentifierRegistry; import org.polypheny.db.transaction.locking.IdentifierUtils; import org.polypheny.db.transaction.locking.Lockable.LockType; import org.polypheny.db.transaction.locking.LockablesRegistry; @@ -79,12 +79,16 @@ import org.polypheny.db.util.DeadlockException; import org.polypheny.db.util.Pair; import org.polypheny.db.util.SourceStringReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Setter @Slf4j public class SqlProcessor extends Processor { + private static final Logger LOGGER = LoggerFactory.getLogger( SqlProcessor.class ); + private static final ParserConfig parserConfig; private PolyphenyDbSqlValidator validator; @@ -227,8 +231,9 @@ public AlgRoot translate( Statement statement, ParsedQueryContext context ) { @Override protected void lock( Transaction transaction, ParsedQueryContext context ) throws DeadlockException { // exclusive lock + //TODO: TH check what this is used for... LogicalNamespace namespace = Catalog.getInstance().getSnapshot().getNamespace( context.getNamespaceId() ).orElseThrow(); - transaction.acquireLockable( LockablesRegistry.INSTANCE.getOrCreateLockable(namespace ), LockType.EXCLUSIVE ); + transaction.acquireLockable( LockablesRegistry.INSTANCE.getOrCreateLockable( namespace ), LockType.EXCLUSIVE ); } @@ -249,72 +254,113 @@ public List splitStatements( String statements ) { } - private void addDefaultValues(Transaction transaction, SqlInsert insert) { + private void addDefaultValues( Transaction transaction, SqlInsert insert ) { + //TODO: please fix me. Not all sources are SqlBasicCalls + if (!(insert.getSource() instanceof SqlBasicCall)) { + LOGGER.warn( "Non SqlBasicCall input encountered. Default values not added." ); + return; + } + SqlNodeList oldColumnList = insert.getTargetColumnList(); - if (oldColumnList != null) { - LogicalTable catalogTable = getTable(transaction, (SqlIdentifier) insert.getTargetTable()); - DataModel dataModel = Catalog.snapshot().getNamespace(catalogTable.namespaceId) + if ( oldColumnList != null ) { + LogicalTable catalogTable = getTable( transaction, (SqlIdentifier) insert.getTargetTable() ); + DataModel dataModel = Catalog.snapshot().getNamespace( catalogTable.namespaceId ) .orElseThrow() .dataModel; - SqlNodeList newColumnList = new SqlNodeList(ParserPos.ZERO); - int size = computeTargetSize(catalogTable, oldColumnList, dataModel); + SqlNodeList newColumnList = new SqlNodeList( ParserPos.ZERO ); + int size = computeTargetSize( catalogTable, oldColumnList, dataModel ); SqlNode[][] newValues = new SqlNode[((SqlBasicCall) insert.getSource()).getOperands().length][size]; - processColumns(catalogTable, oldColumnList, newColumnList, newValues, insert); + processColumns( catalogTable, oldColumnList, newColumnList, newValues, insert ); - if (dataModel == DataModel.DOCUMENT) { - addDocumentValues(oldColumnList, newColumnList, newValues, insert); + if ( dataModel == DataModel.DOCUMENT ) { + addDocumentValues( oldColumnList, newColumnList, newValues, insert ); } - insert.setColumns(newColumnList); - updateSourceValues(insert, newValues); + insert.setColumns( newColumnList ); + updateSourceValues( insert, newValues ); } } - private int computeTargetSize(LogicalTable catalogTable, SqlNodeList oldColumnList, DataModel dataModel) { - int size = catalogTable.getColumns().size(); - if (dataModel == DataModel.DOCUMENT) { - List columnNames = catalogTable.getColumnNames(); - size += (int) oldColumnList.getSqlList() - .stream() - .filter(column -> !columnNames.contains(((SqlIdentifier) column).names.get(0))) - .count(); + + public static void updateSourceValues( SqlInsert insert, SqlNode[][] newValues ) { + for ( int i = 0; i < newValues.length; i++ ) { + SqlBasicCall call = (SqlBasicCall) ((SqlBasicCall) insert.getSource()).getOperands()[i]; + ((SqlBasicCall) insert.getSource()).getOperands()[i] = (SqlNode) call.getOperator().createCall( + call.getFunctionQuantifier(), + call.getPos(), + newValues[i] + ); } - return size; } - private void processColumns(LogicalTable catalogTable, SqlNodeList oldColumnList, SqlNodeList newColumnList, - SqlNode[][] newValues, SqlInsert insert) { + + private void processColumns( + LogicalTable catalogTable, SqlNodeList oldColumnList, SqlNodeList newColumnList, + SqlNode[][] newValues, SqlInsert insert ) { int pos = 0; - for (LogicalColumn column : catalogTable.getColumns()) { - newColumnList.add(new SqlIdentifier(column.name, ParserPos.ZERO)); + for ( LogicalColumn column : catalogTable.getColumns() ) { + newColumnList.add( new SqlIdentifier( column.name, ParserPos.ZERO ) ); - for (int i = 0; i < ((SqlBasicCall) insert.getSource()).getOperands().length; i++) { + for ( int i = 0; i < ((SqlBasicCall) insert.getSource()).getOperands().length; i++ ) { SqlNode sqlNode = ((SqlBasicCall) insert.getSource()).getOperands()[i]; SqlBasicCall call = (SqlBasicCall) sqlNode; - int position = getPositionInSqlNodeList(oldColumnList, column.name); - if (column.name.equals( IdentifierUtils.IDENTIFIER_KEY )) { - newValues[i][pos] = createEntityIdentifierLiteral(); - } else if (position >= 0) { + int position = getPositionInSqlNodeList( oldColumnList, column.name ); + if ( position >= 0 ) { + if ( column.name.equals( IdentifierUtils.IDENTIFIER_KEY ) ) { + IdentifierUtils.throwIllegalFieldName(); + } newValues[i][pos] = call.getOperands()[position]; } else { - newValues[i][pos] = resolveDefaultValue(column); + newValues[i][pos] = resolveDefaultValue( column ); } } pos++; } } - private SqlNode resolveDefaultValue(LogicalColumn column) { - if (column.defaultValue != null) { + + public static int computeTargetSize( LogicalTable catalogTable, SqlNodeList oldColumnList, DataModel dataModel ) { + int size = catalogTable.getColumns().size(); + if ( dataModel == DataModel.DOCUMENT ) { + List columnNames = catalogTable.getColumnNames(); + size += (int) oldColumnList.getSqlList() + .stream() + .filter( column -> !columnNames.contains( ((SqlIdentifier) column).names.get( 0 ) ) ) + .count(); + } + return size; + } + + + public static LogicalTable getTable( Transaction transaction, SqlIdentifier tableName ) { + Snapshot snapshot = transaction.getSnapshot(); + long namespaceId; + String tableOldName; + if ( tableName.names.size() == 3 ) { // DatabaseName.SchemaName.TableName + namespaceId = snapshot.getNamespace( tableName.names.get( 1 ) ).orElseThrow().id; + tableOldName = tableName.names.get( 2 ); + } else if ( tableName.names.size() == 2 ) { // SchemaName.TableName + namespaceId = snapshot.getNamespace( tableName.names.get( 0 ) ).orElseThrow().id; + tableOldName = tableName.names.get( 1 ); + } else { // TableName + namespaceId = snapshot.getNamespace( transaction.getDefaultNamespace().name ).orElseThrow().id; + tableOldName = tableName.names.get( 0 ); + } + return snapshot.rel().getTable( namespaceId, tableOldName ).orElseThrow( () -> new GenericRuntimeException( "Could not find table with name " + tableName.names ) ); + } + + + private SqlNode resolveDefaultValue( LogicalColumn column ) { + if ( column.defaultValue != null ) { LogicalDefaultValue defaultValue = column.defaultValue; // TODO NH handle arrays - return createDefaultValueLiteral(column, defaultValue); - } else if (column.nullable) { + return createDefaultValueLiteral( column, defaultValue ); + } else if ( column.nullable ) { return SqlLiteral.createNull( ParserPos.ZERO ); } else { throw new PolyphenyDbException( @@ -323,7 +369,8 @@ private SqlNode resolveDefaultValue(LogicalColumn column) { } } - private SqlNode createDefaultValueLiteral(LogicalColumn column, LogicalDefaultValue defaultValue) { + + private SqlNode createDefaultValueLiteral( LogicalColumn column, LogicalDefaultValue defaultValue ) { return switch ( column.type ) { case BOOLEAN -> SqlLiteral.createBoolean( Boolean.parseBoolean( defaultValue.value.toJson() ), @@ -345,67 +392,33 @@ private SqlNode createDefaultValueLiteral(LogicalColumn column, LogicalDefaultVa }; } - private SqlNode createEntityIdentifierLiteral() { - return SqlLiteral.createExactNumeric( - String.valueOf( IdentifierRegistry.INSTANCE.getEntryIdentifier()), // ToDo TH: is there no better way to do this? - ParserPos.ZERO - ); - } - private void addDocumentValues(SqlNodeList oldColumnList, SqlNodeList newColumnList, - SqlNode[][] newValues, SqlInsert insert) { + private void addDocumentValues( + SqlNodeList oldColumnList, SqlNodeList newColumnList, + SqlNode[][] newValues, SqlInsert insert ) { List documentColumns = new ArrayList<>(); - for (SqlNode column : oldColumnList.getSqlList()) { - if (newColumnList.getSqlList() + for ( SqlNode column : oldColumnList.getSqlList() ) { + if ( newColumnList.getSqlList() .stream() - .filter(c -> c instanceof SqlIdentifier) - .map(c -> ((SqlIdentifier) c).names.get(0)) - .noneMatch(c -> c.equals(((SqlIdentifier) column).names.get(0)))) { - documentColumns.add((SqlIdentifier) column); + .filter( c -> c instanceof SqlIdentifier ) + .map( c -> ((SqlIdentifier) c).names.get( 0 ) ) + .noneMatch( c -> c.equals( ((SqlIdentifier) column).names.get( 0 ) ) ) ) { + documentColumns.add( (SqlIdentifier) column ); } } int pos = newColumnList.getSqlList().size(); - for (SqlIdentifier doc : documentColumns) { - newColumnList.add(doc); - for (int i = 0; i < ((SqlBasicCall) insert.getSource()).getOperands().length; i++) { - int position = getPositionInSqlNodeList(oldColumnList, doc.getSimple()); + for ( SqlIdentifier doc : documentColumns ) { + newColumnList.add( doc ); + for ( int i = 0; i < ((SqlBasicCall) insert.getSource()).getOperands().length; i++ ) { + int position = getPositionInSqlNodeList( oldColumnList, doc.getSimple() ); newValues[i][pos] = ((SqlBasicCall) ((SqlBasicCall) insert.getSource()).getOperands()[i]) .getOperands()[position]; } } } - private void updateSourceValues(SqlInsert insert, SqlNode[][] newValues) { - for (int i = 0; i < newValues.length; i++) { - SqlBasicCall call = (SqlBasicCall) ((SqlBasicCall) insert.getSource()).getOperands()[i]; - ((SqlBasicCall) insert.getSource()).getOperands()[i] = (SqlNode) call.getOperator().createCall( - call.getFunctionQuantifier(), - call.getPos(), - newValues[i] - ); - } - } - - - private LogicalTable getTable( Transaction transaction, SqlIdentifier tableName ) { - Snapshot snapshot = transaction.getSnapshot(); - long namespaceId; - String tableOldName; - if ( tableName.names.size() == 3 ) { // DatabaseName.SchemaName.TableName - namespaceId = snapshot.getNamespace( tableName.names.get( 1 ) ).orElseThrow().id; - tableOldName = tableName.names.get( 2 ); - } else if ( tableName.names.size() == 2 ) { // SchemaName.TableName - namespaceId = snapshot.getNamespace( tableName.names.get( 0 ) ).orElseThrow().id; - tableOldName = tableName.names.get( 1 ); - } else { // TableName - namespaceId = snapshot.getNamespace( transaction.getDefaultNamespace().name ).orElseThrow().id; - tableOldName = tableName.names.get( 0 ); - } - return snapshot.rel().getTable( namespaceId, tableOldName ).orElseThrow( () -> new GenericRuntimeException( "Could not find table with name " + tableName.names ) ); - } - private int getPositionInSqlNodeList( SqlNodeList columnList, String name ) { int i = 0; @@ -432,4 +445,5 @@ protected AlgRoot trimUnusedFields( AlgRoot root, SqlToAlgConverter sqlToAlgConv final boolean dml = Kind.DML.contains( root.kind ); return root.withAlg( sqlToAlgConverter.trimUnusedFields( dml || ordered, root.alg ) ); } + } 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 477fa7cfa6..daca411461 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 @@ -68,11 +68,11 @@ public class SqlDataTypeSpec extends SqlNode implements DataTypeSpec { private final TimeZone timeZone; /** - * Whether data type is allows nulls. + * Whether data type is allowing nulls. *

* Nullable is nullable! Null means "not specified". E.g. {@code CAST(x AS INTEGER)} preserves has the same nullability as {@code x}. */ - private Boolean nullable; + private final Boolean nullable; /** diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/ddl/SqlCreateTable.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/ddl/SqlCreateTable.java index 6b2fb27dc0..a7af66f7da 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/ddl/SqlCreateTable.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/ddl/SqlCreateTable.java @@ -55,7 +55,6 @@ import org.polypheny.db.sql.language.SqlSpecialOperator; import org.polypheny.db.sql.language.SqlWriter; import org.polypheny.db.transaction.Statement; -import org.polypheny.db.transaction.locking.IdentifierUtils; import org.polypheny.db.transaction.locking.Lockable; import org.polypheny.db.transaction.locking.Lockable.LockType; import org.polypheny.db.type.entity.PolyValue; @@ -128,6 +127,7 @@ public class SqlCreateTable extends SqlCreate implements ExecutableStatement { this.partitionGroupNamesList = partitionGroupNamesList; this.partitionQualifierList = partitionQualifierList; this.rawPartitionInfo = rawPartitionInfo; + } @@ -273,9 +273,7 @@ private Pair, List> separateColumn List fieldInformation = new ArrayList<>(); List constraintInformation = new ArrayList<>(); - fieldInformation.add( IdentifierUtils.IDENTIFIER_FIELD_INFORMATION); - - int position = 2; // first column is entry identifier + int position = 1; for ( Ord c : Ord.zip( columns.getSqlList() ) ) { if ( c.e instanceof SqlColumnDeclaration columnDeclaration ) { @@ -324,9 +322,10 @@ private ConstraintInformation getConstraintInformation( String constraintName, S }; } + @Override public Map deriveLockables( Context context, ParsedQueryContext parsedQueryContext ) { - return getMapOfNamespaceLockableOrDefault(name, context, LockType.EXCLUSIVE); + return getMapOfNamespaceLockableOrDefault( name, context, LockType.EXCLUSIVE ); } 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 5f068a9566..2546c34ddd 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 @@ -138,6 +138,7 @@ import org.polypheny.db.sql.language.fun.SqlCrossMapItemOperator; import org.polypheny.db.sql.language.util.SqlShuttle; import org.polypheny.db.sql.language.util.SqlTypeUtil; +import org.polypheny.db.transaction.locking.IdentifierUtils; import org.polypheny.db.type.ArrayType; import org.polypheny.db.type.PolyType; import org.polypheny.db.type.PolyTypeUtil; @@ -4005,7 +4006,7 @@ private void checkFieldCount( SqlNode node, Entity table, SqlNode source, AlgDat switch ( strategies.get( field.getIndex() ) ) { case NOT_NULLABLE: assert !field.getType().isNullable(); - if ( targetField == null ) { + if ( targetField == null && !field.getName().equals( IdentifierUtils.IDENTIFIER_KEY ) ) { throw newValidationError( node, RESOURCE.columnNotNullable( field.getName() ) ); } break;