Skip to content

Registration of columns for generated keys is now dialect dependent. #602

New issue

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

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

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-r2dbc</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.0-483-read-camel-case-SNAPSHOT</version>

<name>Spring Data R2DBC</name>
<description>Spring Data module for R2DBC</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
*
* @author Mark Paluch
* @author Louis Morgan
* @author Jens Schauder
*/
public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStrategy {

Expand Down Expand Up @@ -360,6 +361,11 @@ public R2dbcConverter getConverter() {
return this.mappingContext;
}

@Override
public String renderForGeneratedKeys(SqlIdentifier identifier) {
return dialect.renderForGeneratedKeys(identifier);
}

private RelationalPersistentEntity<?> getRequiredPersistentEntity(Class<?> typeToRead) {
return this.mappingContext.getRequiredPersistentEntity(typeToRead);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
*
* @author Mark Paluch
* @author Bogdan Ilchyshyn
* @author Jens Schauder
* @since 1.1
*/
public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAware, ApplicationContextAware {
Expand Down Expand Up @@ -633,7 +634,7 @@ private <T> Mono<T> doInsert(T entity, SqlIdentifier tableName, OutboundRow outb
return statement.returnGeneratedValues();
}

return statement.returnGeneratedValues(dataAccessStrategy.toSql(identifierColumns.get(0)));
return statement.returnGeneratedValues(dataAccessStrategy.renderForGeneratedKeys(identifierColumns.get(0)));
})
.map(this.dataAccessStrategy.getConverter().populateIdIfNecessary(entity)) //
.all() //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
import org.springframework.lang.Nullable;
import org.springframework.r2dbc.core.Parameter;
import org.springframework.r2dbc.core.PreparedOperation;
import org.springframework.util.Assert;

/**
* Data access strategy that generalizes convenience operations using mapped entities. Typically used internally by
* {@link DatabaseClient} and repository support. SQL creation is limited to single-table operations and single-column
* primary keys.
*
* @author Mark Paluch
* @author Jens Schauder
* @see org.springframework.r2dbc.core.PreparedOperation
* @deprecated since 1.2 in favor of using direct usage of {@link StatementMapper},
* {@link org.springframework.data.r2dbc.query.UpdateMapper} and {@link R2dbcConverter}.
Expand Down Expand Up @@ -135,6 +137,19 @@ public interface ReactiveDataAccessStrategy {
*/
String toSql(SqlIdentifier identifier);

/**
* Render a {@link SqlIdentifier} in a way suitable for registering it as a generated key with a statement.
*
* @param identifier to render. Must not be {@literal null}.
* @return rendered identifier. Guaranteed to be not {@literal null}.
*/
default String renderForGeneratedKeys(SqlIdentifier identifier) {

Assert.notNull(identifier, "Indentifier must not be null.");

return identifier.toSql(IdentifierProcessing.NONE);
}

/**
* Interface to retrieve parameters for named parameter processing.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package org.springframework.data.r2dbc.dialect;

import org.springframework.data.relational.core.sql.SqlIdentifier;

/**
* An SQL dialect for H2 in Postgres Compatibility mode.
*
* @author Mark Paluch
* @author Jens Schauder
*/
public class H2Dialect extends PostgresDialect {

/**
* Singleton instance.
*/
public static final H2Dialect INSTANCE = new H2Dialect();

@Override
public String renderForGeneratedKeys(SqlIdentifier identifier) {
return identifier.getReference(getIdentifierProcessing());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.r2dbc.core.binding.BindMarkersFactory;

/**
* An SQL dialect for MySQL.
*
* @author Mark Paluch
* @author Jens Schauder
*/
public class MySqlDialect extends org.springframework.data.relational.core.dialect.MySqlDialect
implements R2dbcDialect {
Expand Down Expand Up @@ -103,6 +105,11 @@ public Boolean convert(Byte s) {
}
}

@Override
public String renderForGeneratedKeys(SqlIdentifier identifier) {
return identifier.getReference(getIdentifierProcessing());
}

/**
* Simple singleton to convert {@link Boolean}s to their {@link Byte} representation. MySQL does not have a built-in
* boolean type by default, so relies on using a byte instead. {@literal true} maps to {@code 1}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.r2dbc.mapping.R2dbcSimpleTypeHolder;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.r2dbc.core.binding.BindMarkersFactory;

/**
Expand Down Expand Up @@ -59,4 +60,16 @@ default SimpleTypeHolder getSimpleTypeHolder() {
default Collection<Object> getConverters() {
return Collections.emptySet();
}

/**
* Render a {@link SqlIdentifier} in a way suitable for registering it as a generated key with a statement. The
* default implementation renders it as it would render a SQL representation of the identifier, i.e. with quotes where
* applicable.
*
* @param identifier to render. Must not be {@literal null}.
* @return rendered identifier. Guaranteed to be not {@literal null}.
*/
default String renderForGeneratedKeys(SqlIdentifier identifier) {
return identifier.toSql(getIdentifierProcessing());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ public QueryMapper(R2dbcDialect dialect, R2dbcConverter converter) {

/**
* Render a {@link SqlIdentifier} for SQL usage.
* The resulting String might contain quoting characters.
*
* @param identifier
* @return
* @param identifier the identifier to be rendered.
* @return an identifier String.
* @since 1.1
*/
public String toSql(SqlIdentifier identifier) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2021 the original author or authors.
*
* 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
*
* https://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.springframework.data.r2dbc.repository;

import static org.assertj.core.api.Assertions.*;

import io.r2dbc.spi.ConnectionFactory;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import reactor.test.StepVerifier;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import javax.sql.DataSource;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.r2dbc.testing.R2dbcIntegrationTestSupport;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.lang.Nullable;

/**
* Abstract base class for integration tests for {@link LegoSetRepository} with table and column names that contain
* upper and lower case characters.
*
* @author Jens Schauder
*/
public abstract class AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests extends R2dbcIntegrationTestSupport {

@Autowired private LegoSetRepository repository;
protected JdbcTemplate jdbc;

@BeforeEach
void before() {

this.jdbc = createJdbcTemplate(createDataSource());

try {
this.jdbc.execute(getDropTableStatement());
} catch (DataAccessException e) {}

this.jdbc.execute(getCreateTableStatement());
}

/**
* Creates a {@link DataSource} to be used in this test.
*
* @return the {@link DataSource} to be used in this test.
*/
protected abstract DataSource createDataSource();

/**
* Creates a {@link ConnectionFactory} to be used in this test.
*
* @return the {@link ConnectionFactory} to be used in this test.
*/
protected abstract ConnectionFactory createConnectionFactory();

/**
* Returns the CREATE TABLE statement for table {@code legoset} with the following three columns:
* <ul>
* <li>id integer (primary key), not null, auto-increment</li>
* <li>name varchar(255), nullable</li>
* <li>manual integer, nullable</li>
* </ul>
*
* @return the CREATE TABLE statement for table {@code legoset} with three columns.
*/
protected abstract String getCreateTableStatement();

/**
* Returns the the DROP TABLE statement for table {@code LegoSet}.
*
* @return the DROP TABLE statement for table {@code LegoSet}.
*/
protected abstract String getDropTableStatement();

@Test
void insertAndReadEntities() {

LegoSet legoSet1 = new LegoSet(null, "SCHAUFELRADBAGGER", 12);
LegoSet legoSet2 = new LegoSet(null, "FORSCHUNGSSCHIFF", 13);

repository.saveAll(Arrays.asList(legoSet1, legoSet2)) //
.as(StepVerifier::create) //
.expectNextCount(2) //
.verifyComplete();

List<LegoSet> legoSets = repository //
.findAll() //
.collectList() //
.block(Duration.ofMillis(500));

assertThat(legoSets).containsExactlyInAnyOrder(legoSet1, legoSet2);
}

interface LegoSetRepository extends ReactiveCrudRepository<LegoSet, Integer> {}

@Getter
@Setter
@Table("LegoSet")
@NoArgsConstructor
public static class LegoSet {

@Nullable @Column("Id") @Id Integer id;

@Column("Name") String name;

@Column("Manual") Integer manual;

@PersistenceConstructor
LegoSet(@Nullable Integer id, String name, Integer manual) {
this.id = id;
this.name = name;
this.manual = manual;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
LegoSet legoSet = (LegoSet) o;
return Objects.equals(id, legoSet.id) && Objects.equals(name, legoSet.name)
&& Objects.equals(manual, legoSet.manual);
}

@Override
public int hashCode() {
return Objects.hash(id, name, manual);
}
}
}
Loading