diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/DefaultCouchbaseTypeMapper.java b/src/main/java/org/springframework/data/couchbase/core/convert/DefaultCouchbaseTypeMapper.java index 2be32ec8..c8db5fa0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/DefaultCouchbaseTypeMapper.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/DefaultCouchbaseTypeMapper.java @@ -67,6 +67,9 @@ public class DefaultCouchbaseTypeMapper extends DefaultTypeMapper entityInfo) { String typeKey = operations.getConverter().getTypeKey(); String typeValue = entityInfo.getJavaType().getName(); - return "`" + typeKey + "` = \"" + typeValue + "\""; + Alias alias = operations.getConverter().getTypeAlias(TypeInformation.of(entityInfo.getJavaType())); + if (alias != null && alias.isPresent()) { + typeValue = alias.toString(); + } + return !empty(typeKey) && !empty(typeValue) ? i(typeKey).eq(s(typeValue)).toString() : null; + } + + private static boolean empty(String s){ + return s == null || s.length() == 0; } public static class IndexDefinitionHolder implements IndexDefinition { diff --git a/src/main/java/org/springframework/data/couchbase/core/query/Query.java b/src/main/java/org/springframework/data/couchbase/core/query/Query.java index c916d7ef..4b8dcbc5 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/Query.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/Query.java @@ -353,7 +353,9 @@ public class Query { domainClass, returnClass, isCount, distinctFields, fields); final StringBuilder statement = new StringBuilder(); appendString(statement, n1ql.selectEntity); // select ... - appendWhereString(statement, n1ql.filter); // typeKey = typeValue + if (n1ql.filter != null) { + appendWhereString(statement, n1ql.filter); // typeKey = typeValue + } appendWhere(statement, new int[] { 0 }, converter); // criteria on this Query if (!isCount) { appendSort(statement); @@ -368,7 +370,9 @@ public class Query { domainClass, null, false, null, null); final StringBuilder statement = new StringBuilder(); appendString(statement, n1ql.delete); // delete ... - appendWhereString(statement, n1ql.filter); // typeKey = typeValue + if (n1ql.filter != null) { + appendWhereString(statement, n1ql.filter); // typeKey = typeValue + } appendWhere(statement, null, converter); // criteria on this Query appendString(statement, n1ql.returning); return statement.toString(); diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlMutateQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlMutateQueryCreator.java index 28ad39d1..ee60fd62 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlMutateQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlMutateQueryCreator.java @@ -37,6 +37,7 @@ import com.couchbase.client.java.json.JsonValue; * supported * * @author Subhashni Balakrishnan + * @author Michael Reiche */ public class N1qlMutateQueryCreator extends AbstractQueryCreator implements PartTreeN1qlQueryCreator { @@ -82,6 +83,9 @@ public class N1qlMutateQueryCreator extends AbstractQueryCreator N1QLExpression createCountQueryForEntity(String bucketName, CouchbaseConverter converter, CouchbaseEntityInformation entityInformation) { - return select(count(x("*")).as(x(CountFragment.COUNT_ALIAS))).from(escapedBucket(bucketName)) - .where(createWhereFilterForEntity(null, converter, entityInformation)); + N1QLExpression entityFilter = createWhereFilterForEntity(null, converter, entityInformation); + N1QLExpression expression = select( + (count(x("*")).as(x(CountFragment.COUNT_ALIAS))).from(escapedBucket(bucketName))); + if (entityFilter == null) { + return expression; + } + return expression.where(entityFilter); } /** diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java index baf1aa8f..ca6ddd15 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.data.couchbase.core.query.N1QLExpression.i; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -38,15 +39,17 @@ import java.util.UUID; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.couchbase.core.ExecutableFindByIdOperation.ExecutableFindById; import org.springframework.data.couchbase.core.ExecutableRemoveByIdOperation.ExecutableRemoveById; import org.springframework.data.couchbase.core.ExecutableReplaceByIdOperation.ExecutableReplaceById; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.couchbase.core.query.QueryCriteria; import org.springframework.data.couchbase.core.support.OneAndAllEntity; import org.springframework.data.couchbase.core.support.OneAndAllId; import org.springframework.data.couchbase.core.support.WithDurability; @@ -66,6 +69,7 @@ import org.springframework.data.couchbase.domain.UserAnnotatedDurabilityExpressi import org.springframework.data.couchbase.domain.UserAnnotatedPersistTo; import org.springframework.data.couchbase.domain.UserAnnotatedReplicateTo; import org.springframework.data.couchbase.domain.UserAnnotatedTouchOnRead; +import org.springframework.data.couchbase.domain.UserNoAlias; import org.springframework.data.couchbase.domain.UserSubmission; import org.springframework.data.couchbase.util.ClusterType; import org.springframework.data.couchbase.util.IgnoreWhen; @@ -142,6 +146,27 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests { } + @Test + void findByIdNoAlias() { + String firstname = UUID.randomUUID().toString(); + try { + UserNoAlias user = new UserNoAlias("1", firstname, "user1"); + couchbaseTemplate.upsertById(UserNoAlias.class).one(user); + UserNoAlias foundUser = couchbaseTemplate.findById(UserNoAlias.class).one(user.getId()); + user.setVersion(foundUser.getVersion());// version will have changed + assertEquals(user, foundUser); + Query query = new Query(QueryCriteria.where(i("firstname")).eq(firstname)); + List queriedUsers = couchbaseTemplate.findByQuery(UserNoAlias.class) + .withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query).all(); + assertEquals(1, queriedUsers.size(), "should have found exactly one"); + } finally { + Query query = new Query(QueryCriteria.where(i("firstname")).eq(firstname)); + List removeResult = couchbaseTemplate.removeByQuery(UserNoAlias.class) + .withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query).all(); + assertEquals(1, removeResult.size(), "should have removed exactly one"); + } + } + @Test void findByIdWithExpiry() { try { @@ -1289,6 +1314,7 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests { } @Test + @Disabled // it's finding _txn documents with source = a single 0 byte which fails to deserialize void sampleScan() { String id = "A"; String lower = null; diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java index e8d80e7c..44308111 100644 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java @@ -63,6 +63,7 @@ import org.springframework.data.couchbase.domain.Address; import org.springframework.data.couchbase.domain.Config; import org.springframework.data.couchbase.domain.Person; import org.springframework.data.couchbase.domain.User; +import org.springframework.data.couchbase.domain.UserNoAlias; import org.springframework.data.mapping.MappingException; /** @@ -75,10 +76,17 @@ public class MappingCouchbaseConverterTests { private static MappingCouchbaseConverter converter = new MappingCouchbaseConverter(); private static MappingCouchbaseConverter customConverter = (new Config()).mappingCouchbaseConverter(); + private static MappingCouchbaseConverter noTypeKeyConverter = (new Config(){ + @Override + public String typeKey() { + return ""; + } + }).mappingCouchbaseConverter(); static { converter.afterPropertiesSet(); customConverter.afterPropertiesSet(); + noTypeKeyConverter.afterPropertiesSet(); } @Test @@ -152,6 +160,43 @@ public class MappingCouchbaseConverterTests { assertThat(converted.attr0).isEqualTo(source.get("attr0")); } + @Test + void writesStringNoTypeKey() { + CouchbaseDocument converted = new CouchbaseDocument(); + StringEntity entity = new StringEntity("foobar"); + + noTypeKeyConverter.write(entity, converted); + Map result = converted.export(); + assertThat(result.get("_class")).isEqualTo(null); + assertThat(result.get("attr0")).isEqualTo(entity.attr0); + assertThat(converted.getId()).isEqualTo(BaseEntity.ID); + } + + @Test + void readsStringNoTypeKey() { + CouchbaseDocument source = new CouchbaseDocument(); + source.put("attr0", "foobar"); + StringEntity converted = noTypeKeyConverter.read(StringEntity.class, source); + assertThat(converted.attr0).isEqualTo(source.get("attr0")); + } + + @Test + void writesNoTypeAlias() { + CouchbaseDocument converted = new CouchbaseDocument(); + UserNoAlias entity = new UserNoAlias(UUID.randomUUID().toString(), "first", "last"); + noTypeKeyConverter.write(entity, converted); + Map result = converted.export(); + assertThat(result.get("_class")).isEqualTo(null); + assertThat(converted.getId()).isEqualTo(entity.getId()); + } + + @Test + void readsNoTypeAlias() { + CouchbaseDocument document = new CouchbaseDocument("001"); + UserNoAlias user = noTypeKeyConverter.read(UserNoAlias.class, document); + assertThat(user.getId()).isEqualTo("001"); + } + @Test void writesBigInteger() { CouchbaseDocument converted = new CouchbaseDocument(); diff --git a/src/test/java/org/springframework/data/couchbase/domain/UserNoAlias.java b/src/test/java/org/springframework/data/couchbase/domain/UserNoAlias.java new file mode 100644 index 00000000..ccb90082 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/domain/UserNoAlias.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012-2023 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.couchbase.domain; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.annotation.Version; +import org.springframework.data.couchbase.core.mapping.Document; + +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * User entity with an empty TypeAlias for tests + * + * @author Michael Nitschinger + * @author Michael Reiche + */ + +@Document +@TypeAlias("") +public class UserNoAlias extends AbstractUser implements Serializable { + + public JsonNode jsonNode; + public JsonObject jsonObject; + public JsonArray jsonArray; + + @PersistenceConstructor + public UserNoAlias(final String id, final String firstname, final String lastname) { + this.id = id; + this.firstname = firstname; + this.lastname = lastname; + this.subtype = AbstractingTypeMapper.Type.USER; + this.jsonNode = new ObjectNode(JsonNodeFactory.instance); + try { + jsonNode = (new ObjectNode(JsonNodeFactory.instance)).put("myNumber", uid()); + } catch (Exception e) { + e.printStackTrace(); + } + Map map = new HashMap(); + map.put("myNumber", uid()); + this.jsonObject = JsonObject.jo().put("yourNumber",Long.valueOf(uid())); + this.jsonArray = JsonArray.from(Long.valueOf(uid()), Long.valueOf(uid())); + } + + @Transient int uid=1000; + long uid(){ + return uid++; + } + + + @Version protected long version; + @Transient protected String transientInfo; + @CreatedBy protected String createdBy; + @CreatedDate protected long createdDate; + @LastModifiedBy protected String lastModifiedBy; + @LastModifiedDate protected long lastModifiedDate; + + public String getLastname() { + return lastname; + } + + public long getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(long createdDate) { + this.createdDate = createdDate; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public long getLastModifiedDate() { + return lastModifiedDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + @Override + public int hashCode() { + return Objects.hash(getId(), firstname, lastname); + } + + public String getTransientInfo() { + return transientInfo; + } + + public void setTransientInfo(String something) { + transientInfo = something; + } + +}