Skip to content

Commit

Permalink
Allow limited no-id entity operations (#1005)
Browse files Browse the repository at this point in the history
  • Loading branch information
dstepanov authored May 4, 2021
1 parent 90237aa commit 302982d
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,7 @@ private <T, R> Stream<R> findStream(@NonNull PreparedQuery<T, R> preparedQuery,
}
}
);
boolean onlySingleEndedJoins = joinFetchPaths.stream()
.flatMap(jp -> Arrays.stream(jp.getAssociationPath()))
.anyMatch(association -> association.getKind().isSingleEnded());
boolean onlySingleEndedJoins = isOnlySingleEndedJoins(getEntity(preparedQuery.getRootEntity()), joinFetchPaths);
// Cannot stream ResultSet for "many" joined query
if (!onlySingleEndedJoins) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.micronaut.data.jdbc.postgres

import io.micronaut.context.ApplicationContext
import io.micronaut.data.annotation.MappedEntity
import io.micronaut.data.jdbc.annotation.JdbcRepository
import io.micronaut.data.model.Pageable
import io.micronaut.data.model.Sort
import io.micronaut.data.model.query.builder.sql.Dialect
import io.micronaut.data.runtime.config.SchemaGenerate
import io.micronaut.transaction.SynchronousTransactionManager
import io.micronaut.transaction.TransactionCallback
import io.micronaut.transaction.TransactionStatus
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

import javax.inject.Inject

class PostgresNoIdEntitySpec extends Specification implements PostgresTestPropertyProvider {
@AutoCleanup
@Shared
ApplicationContext applicationContext = ApplicationContext.run(getProperties())

@Shared
@Inject
NoIdEntityRepository noIdEntityRepository = applicationContext.getBean(NoIdEntityRepository)

@Shared
@Inject
SynchronousTransactionManager transactionManager = applicationContext.getBean(SynchronousTransactionManager)

void setup() {
transactionManager.executeWrite(new TransactionCallback() {
@Override
Object call(TransactionStatus status) throws Exception {
status.connection.prepareStatement('''
drop sequence if exists "no_id_names_seq";
drop table if exists "no_id_names";
create sequence "no_id_names_seq" increment by 1;
create table "no_id_names"
(
"id" bigint default nextval('no_id_names_seq'::regclass) not null,
"first_name" varchar(255) ,
"last_name" varchar(255)
);
''').withCloseable { it.executeUpdate() }
return null
}

})
}

void 'test no-id entity operations'() {
when:
noIdEntityRepository.save(new NoIdEntity(firstName: "Xyz", lastName: "Abc"))
noIdEntityRepository.save(new NoIdEntity(firstName: "Qwe", lastName: "Jkl"))
def all = noIdEntityRepository.listAll()
def allPageable = noIdEntityRepository.listAll(Pageable.from(1, 1).order("firstName", Sort.Order.Direction.DESC))
then:
all.size() == 2
all.find { it.firstName == "Xyz" }
all.find { it.firstName == "Qwe" }
allPageable.size() == 1
allPageable.find { it.firstName == "Qwe" }
}

@Override
SchemaGenerate schemaGenerate() {
return SchemaGenerate.NONE
}
}

@JdbcRepository(dialect = Dialect.POSTGRES)
interface NoIdEntityRepository {

NoIdEntity save(NoIdEntity entity)

List<NoIdEntity> listAll()

List<NoIdEntity> listAll(Pageable pageable)

}

@MappedEntity("no_id_names")
class NoIdEntity {

String firstName
String lastName
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,15 @@
*/
package io.micronaut.data.jdbc.postgres

import io.micronaut.data.model.query.builder.sql.Dialect
import io.micronaut.data.runtime.config.SchemaGenerate
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import io.micronaut.test.support.TestPropertyProvider
import org.testcontainers.containers.PostgreSQLContainer
import spock.lang.Shared
import spock.lang.Specification

import javax.inject.Inject
import java.sql.Connection

@MicronautTest
class PostgresSequenceDefaultSpec extends Specification implements TestPropertyProvider {

@Shared
PostgreSQLContainer postgres

@Override
Map<String, String> getProperties() {
postgres = new PostgreSQLContainer<>("postgres:10")
.withDatabaseName("test-database")
.withUsername("test")
.withPassword("test")
postgres.start()
[
"datasources.default.url":postgres.getJdbcUrl(),
"datasources.default.username":postgres.getUsername(),
"datasources.default.password":postgres.getPassword(),
"datasources.default.dialect": Dialect.POSTGRES
] as Map<String, String>
}
class PostgresSequenceDefaultSpec extends Specification implements PostgresTestPropertyProvider {

@Inject
Connection connection
Expand All @@ -54,6 +33,8 @@ class PostgresSequenceDefaultSpec extends Specification implements TestPropertyP

void setup() {
connection.prepareStatement('''
drop sequence if exists "user_seq";
drop table if exists "user_";
create sequence "user_seq" increment by 1;
create table "user_"
(
Expand All @@ -66,11 +47,6 @@ create table "user_"
''').withCloseable { it.executeUpdate( )}
}

def cleanup() {
postgres.close()
}


void "test sequence generation with default nextval"() {
when:
User user = userRepository.save("Fred")
Expand All @@ -82,4 +58,9 @@ create table "user_"
user.id != user2.id
user.id == userRepository.findById(user.id).id
}

@Override
SchemaGenerate schemaGenerate() {
return SchemaGenerate.CREATE_DROP
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ trait PostgresTestPropertyProvider implements SharedDatabaseContainerTestPropert

@Override
int sharedSpecsCount() {
return 9
return 11
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@
import java.io.Serializable;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
Expand Down Expand Up @@ -662,9 +661,7 @@ public <T, R> Flux<R> findAll(@NonNull PreparedQuery<T, R> preparedQuery) {
}
}
);
boolean onlySingleEndedJoins = joinFetchPaths.stream()
.flatMap(jp -> Arrays.stream(jp.getAssociationPath()))
.anyMatch(association -> association.getKind().isSingleEnded());
boolean onlySingleEndedJoins = isOnlySingleEndedJoins(getEntity(preparedQuery.getRootEntity()), joinFetchPaths);
// Cannot stream ResultSet for "many" joined query
if (!onlySingleEndedJoins) {
SqlResultEntityTypeMapper.PushingMapper<Row, List<R>> manyReader = entityTypeMapper.readAllWithJoins();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import io.micronaut.core.annotation.NonNull;
import io.micronaut.data.repository.reactive.ReactiveStreamsCrudRepository;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import io.micronaut.data.model.PersistentProperty;
import io.micronaut.data.model.PersistentPropertyPath;
import io.micronaut.data.model.Sort;
import io.micronaut.data.model.query.JoinPath;
import io.micronaut.data.model.query.QueryModel;
import io.micronaut.data.model.query.QueryParameter;
import io.micronaut.data.model.query.builder.AbstractSqlLikeQueryBuilder;
Expand Down Expand Up @@ -106,7 +107,7 @@
* @author Denis Stepanov
* @since 1.0.0
*/
@SuppressWarnings("unchecked")
@SuppressWarnings("FileLength")
@Internal
public abstract class AbstractSqlRepositoryOperations<Cnt, RS, PS, Exc extends Exception> {
protected static final Logger QUERY_LOG = DataSettings.QUERY_LOG;
Expand Down Expand Up @@ -1261,6 +1262,29 @@ protected void checkOptimisticLocking(int expected, int received) {
}
}

/**
* Check if joined associated are all single ended (Can produce only one result).
*
* @param rootPersistentEntity The root entity
* @param joinFetchPaths The join paths
* @return true if there are no "many" joins
*/
protected boolean isOnlySingleEndedJoins(RuntimePersistentEntity<?> rootPersistentEntity, Set<JoinPath> joinFetchPaths) {
boolean onlySingleEndedJoins = joinFetchPaths.isEmpty() || joinFetchPaths.stream()
.flatMap(jp -> {
PersistentPropertyPath propertyPath = rootPersistentEntity.getPropertyPath(jp.getPath());
if (propertyPath == null) {
return Stream.empty();
}
if (propertyPath.getProperty() instanceof Association) {
return Stream.concat(propertyPath.getAssociations().stream(), Stream.of((Association) propertyPath.getProperty()));
}
return propertyPath.getAssociations().stream();
})
.allMatch(association -> association.getKind() == Relation.Kind.EMBEDDED || association.getKind().isSingleEnded());
return onlySingleEndedJoins;
}

private static List<Association> associated(List<Association> associations, Association association) {
if (associations == null) {
return Collections.singletonList(association);
Expand Down Expand Up @@ -1804,6 +1828,11 @@ protected SqlOperation(String query, Dialect dialect) {
this.dialect = dialect;
}

/**
* Expanded query.
*
* @return expanded query
*/
public String exandedQuery() {
return query;
}
Expand Down

0 comments on commit 302982d

Please sign in to comment.