/*
* Copyright 2010 Outerthought bvba
*
* 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.lilyproject.repository.impl.test;
import static org.easymock.EasyMock.createControl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.apache.hadoop.hbase.util.Bytes;
import org.easymock.IMocksControl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.lilyproject.bytes.api.ByteArray;
import org.lilyproject.repository.api.Blob;
import org.lilyproject.repository.api.CompareOp;
import org.lilyproject.repository.api.ConcurrentRecordUpdateException;
import org.lilyproject.repository.api.FieldNotFoundException;
import org.lilyproject.repository.api.FieldType;
import org.lilyproject.repository.api.IdGenerator;
import org.lilyproject.repository.api.IdRecord;
import org.lilyproject.repository.api.IdRecordScanner;
import org.lilyproject.repository.api.InvalidRecordException;
import org.lilyproject.repository.api.Link;
import org.lilyproject.repository.api.Metadata;
import org.lilyproject.repository.api.MetadataBuilder;
import org.lilyproject.repository.api.MutationCondition;
import org.lilyproject.repository.api.QName;
import org.lilyproject.repository.api.Record;
import org.lilyproject.repository.api.RecordBuilder;
import org.lilyproject.repository.api.RecordException;
import org.lilyproject.repository.api.RecordExistsException;
import org.lilyproject.repository.api.RecordId;
import org.lilyproject.repository.api.RecordNotFoundException;
import org.lilyproject.repository.api.RecordScan;
import org.lilyproject.repository.api.RecordScanner;
import org.lilyproject.repository.api.RecordType;
import org.lilyproject.repository.api.RecordTypeNotFoundException;
import org.lilyproject.repository.api.Repository;
import org.lilyproject.repository.api.RepositoryException;
import org.lilyproject.repository.api.ResponseStatus;
import org.lilyproject.repository.api.ReturnFields;
import org.lilyproject.repository.api.SchemaId;
import org.lilyproject.repository.api.Scope;
import org.lilyproject.repository.api.TypeManager;
import org.lilyproject.repository.api.ValueType;
import org.lilyproject.repository.api.VersionNotFoundException;
import org.lilyproject.repository.api.filter.FieldValueFilter;
import org.lilyproject.repository.api.filter.RecordFilterList;
import org.lilyproject.repository.api.filter.RecordIdPrefixFilter;
import org.lilyproject.repository.api.filter.RecordTypeFilter;
import org.lilyproject.repository.api.filter.RecordVariantFilter;
import org.lilyproject.repotestfw.RepositorySetup;
import org.lilyproject.util.Pair;
public abstract class AbstractRepositoryTest {
protected static final RepositorySetup repoSetup = new RepositorySetup();
protected static boolean avro = false;
protected static IdGenerator idGenerator;
protected static TypeManager typeManager;
protected static Repository repository;
protected static FieldType fieldType1;
private static FieldType fieldType1B;
private static FieldType fieldType2;
private static FieldType fieldType3;
private static FieldType fieldType4;
private static FieldType fieldType5;
private static FieldType fieldType6;
protected static RecordType recordType1;
private static RecordType recordType1B;
private static RecordType recordType2;
private static RecordType recordType3;
private static String namespace = "/test/repository";
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
protected static void setupTypes() throws Exception {
setupFieldTypes();
setupRecordTypes();
}
private static void setupFieldTypes() throws Exception {
fieldType1 = typeManager.createFieldType(typeManager
.newFieldType(typeManager.getValueType("STRING"), new QName(namespace, "field1"), Scope.NON_VERSIONED));
fieldType1B = typeManager.createFieldType(typeManager
.newFieldType(typeManager.getValueType("STRING"), new QName(namespace, "field1B"),
Scope.NON_VERSIONED));
fieldType2 = typeManager.createFieldType(typeManager
.newFieldType(typeManager.getValueType("INTEGER"), new QName(namespace, "field2"), Scope.VERSIONED));
fieldType3 = typeManager.createFieldType(
typeManager.newFieldType(typeManager.getValueType("BOOLEAN"), new QName(namespace, "field3"),
Scope.VERSIONED_MUTABLE));
fieldType4 = typeManager.createFieldType(typeManager
.newFieldType(typeManager.getValueType("INTEGER"), new QName(namespace, "field4"),
Scope.NON_VERSIONED));
fieldType5 = typeManager.createFieldType(typeManager
.newFieldType(typeManager.getValueType("BOOLEAN"), new QName(namespace, "field5"), Scope.VERSIONED));
fieldType6 = typeManager.createFieldType(typeManager
.newFieldType(typeManager.getValueType("STRING"), new QName(namespace, "field6"),
Scope.VERSIONED_MUTABLE));
}
private static void setupRecordTypes() throws Exception {
recordType1 = typeManager.newRecordType(new QName(namespace, "RT1"));
recordType1.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType1.getId(), false));
recordType1.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType2.getId(), false));
recordType1.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType3.getId(), false));
recordType1 = typeManager.createRecordType(recordType1);
recordType1B = recordType1.clone();
recordType1B.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType1B.getId(), false));
recordType1B = typeManager.updateRecordType(recordType1B);
recordType2 = typeManager.newRecordType(new QName(namespace, "RT2"));
recordType2.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType4.getId(), false));
recordType2.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType5.getId(), false));
recordType2.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType6.getId(), false));
recordType2 = typeManager.createRecordType(recordType2);
recordType3 = typeManager.newRecordType(new QName(namespace, "RT3"));
recordType3.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType1.getId(), false));
recordType3.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType2.getId(), false));
recordType3.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType3.getId(), false));
recordType3 = typeManager.createRecordType(recordType3);
recordType3.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType1.getId(), true));
recordType3 = typeManager.updateRecordType(recordType3);
recordType3.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType3.getId(), true));
recordType3 = typeManager.updateRecordType(recordType3);
recordType3.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType6.getId(), false));
recordType3 = typeManager.updateRecordType(recordType3);
}
@Test
public void testRecordCreateWithoutRecordType() throws Exception {
IMocksControl control = createControl();
control.replay();
Record record = repository.newRecord(idGenerator.newRecordId());
try {
if (avro) {
System.out.println("Expecting InvalidRecordException");
}
record = repository.create(record);
fail();
} catch (InvalidRecordException expected) {
}
control.verify();
}
@Test
public void testEmptyRecordCreate() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
try {
if (avro) {
System.out.println("Expecting InvalidRecordException");
}
record = repository.create(record);
fail();
} catch (InvalidRecordException expected) {
}
}
@Test
public void testCreate() throws Exception {
IMocksControl control = createControl();
control.replay();
Record createdRecord = createDefaultRecord();
assertEquals(Long.valueOf(1), createdRecord.getVersion());
assertEquals("value1", createdRecord.getField(fieldType1.getName()));
assertEquals(123, createdRecord.getField(fieldType2.getName()));
assertTrue((Boolean) createdRecord.getField(fieldType3.getName()));
assertEquals(recordType1.getName(), createdRecord.getRecordTypeName());
assertEquals(Long.valueOf(1), createdRecord.getRecordTypeVersion());
assertEquals(recordType1.getName(), createdRecord.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(Long.valueOf(1), createdRecord.getRecordTypeVersion(Scope.NON_VERSIONED));
assertEquals(recordType1.getName(), createdRecord.getRecordTypeName(Scope.VERSIONED));
assertEquals(Long.valueOf(1), createdRecord.getRecordTypeVersion(Scope.VERSIONED));
assertEquals(recordType1.getName(), createdRecord.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(Long.valueOf(1), createdRecord.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
assertEquals(createdRecord, repository.read(createdRecord.getId()));
control.verify();
}
@Test
public void testCreateTwice() throws Exception {
IMocksControl control = createControl();
control.replay();
Record createdRecord = createDefaultRecord();
Record record = repository.newRecord(createdRecord.getId());
record.setRecordType(recordType1.getName(), recordType1.getVersion());
record.setField(fieldType1.getName(), "value1");
record.setField(fieldType2.getName(), 123);
record.setField(fieldType3.getName(), true);
try {
repository.create(record);
fail();
} catch (RecordExistsException expected) {
}
control.verify();
}
@Test
public void testCreateNoVersions() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName(), recordType1.getVersion());
record.setField(fieldType1.getName(), "value1");
record = repository.create(record);
assertEquals(null, record.getVersion());
}
@Test
public void testCreateOnlyVersionAndCheckRecordType() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName(), recordType1.getVersion());
record.setField(fieldType2.getName(), 123);
record = repository.create(record);
Record readRecord = repository.read(record.getId());
// Check that the 'global' record type of the read record is also filled in
assertEquals(recordType1.getName(), readRecord.getRecordTypeName());
assertEquals(recordType1.getVersion(), readRecord.getRecordTypeVersion());
// The record type for the versioned scope (only field present) should be returned
assertEquals(recordType1.getName(), readRecord.getRecordTypeName(Scope.VERSIONED));
assertEquals(recordType1.getVersion(), readRecord.getRecordTypeVersion(Scope.VERSIONED));
// The record type for the version-mutable scope should not be returned since no such field is present
assertEquals(null, readRecord.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(null, readRecord.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
}
protected Record createDefaultRecord() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName(), recordType1.getVersion());
record.setField(fieldType1.getName(), "value1");
record.setField(fieldType2.getName(), 123);
record.setField(fieldType3.getName(), true);
return repository.create(record);
}
@Test
public void testCreateWithNonExistingRecordTypeFails() throws Exception {
Record record = repository.newRecord(idGenerator.newRecordId());
record.setRecordType(new QName("foo", "bar"));
record.setField(fieldType1.getName(), "value1");
try {
if (avro) {
System.out.println("Expecting RecordTypeNotFoundException");
}
repository.create(record);
fail();
} catch (RecordTypeNotFoundException expected) {
}
}
@Test
public void testCreateUsesLatestRecordType() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "value1");
Record createdRecord = repository.create(record);
assertEquals(recordType1.getName(), createdRecord.getRecordTypeName());
assertEquals(Long.valueOf(2), createdRecord.getRecordTypeVersion());
assertEquals(recordType1.getName(), createdRecord.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(Long.valueOf(2), createdRecord.getRecordTypeVersion(Scope.NON_VERSIONED));
assertNull(createdRecord.getRecordTypeName(Scope.VERSIONED));
assertNull(createdRecord.getRecordTypeVersion(Scope.VERSIONED));
assertNull(createdRecord.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertNull(createdRecord.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
assertEquals(createdRecord, repository.read(createdRecord.getId()));
}
@Test
public void testCreateVariant() throws Exception {
Record record = createDefaultRecord();
Map<String, String> variantProperties = new HashMap<String, String>();
variantProperties.put("dimension1", "dimval1");
Record variant = repository.newRecord(idGenerator.newRecordId(record.getId(), variantProperties));
variant.setRecordType(recordType1.getName());
variant.setField(fieldType1.getName(), "value2");
variant.setField(fieldType2.getName(), 567);
variant.setField(fieldType3.getName(), false);
Record createdVariant = repository.create(variant);
assertEquals(Long.valueOf(1), createdVariant.getVersion());
assertEquals("value2", createdVariant.getField(fieldType1.getName()));
assertEquals(567, createdVariant.getField(fieldType2.getName()));
assertFalse((Boolean) createdVariant.getField(fieldType3.getName()));
assertEquals(createdVariant, repository.read(variant.getId()));
Set<RecordId> variants = repository.getVariants(record.getId());
assertEquals(2, variants.size());
assertTrue(variants.contains(record.getId()));
assertTrue(variants.contains(createdVariant.getId()));
}
@Test
public void testUpdate() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord.setField(fieldType3.getName(), false);
Record updatedRecord = repository.update(updateRecord);
assertEquals(Long.valueOf(2), updatedRecord.getVersion());
assertEquals("value2", updatedRecord.getField(fieldType1.getName()));
assertEquals(789, updatedRecord.getField(fieldType2.getName()));
assertEquals(false, updatedRecord.getField(fieldType3.getName()));
assertEquals(updatedRecord, repository.read(record.getId()));
}
@Test
public void testUpdateWithoutRecordType() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord.setField(fieldType3.getName(), false);
Record updatedRecord = repository.update(updateRecord);
assertEquals(record.getRecordTypeName(), updatedRecord.getRecordTypeName());
assertEquals(Long.valueOf(2), updatedRecord.getRecordTypeVersion());
assertEquals(Long.valueOf(2), updatedRecord.getVersion());
assertEquals("value2", updatedRecord.getField(fieldType1.getName()));
assertEquals(789, updatedRecord.getField(fieldType2.getName()));
assertEquals(false, updatedRecord.getField(fieldType3.getName()));
assertEquals(updatedRecord, repository.read(record.getId()));
}
@Test
public void testUpdateOnlyOneField() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(record.getRecordTypeName(), record.getRecordTypeVersion());
updateRecord.setField(fieldType1.getName(), "value2");
Record updatedRecord = repository.update(updateRecord);
assertEquals(Long.valueOf(1), updatedRecord.getVersion());
assertEquals("value2", updatedRecord.getField(fieldType1.getName()));
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
updatedRecord.getField(fieldType2.getName());
fail();
} catch (FieldNotFoundException expected) {
}
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
updatedRecord.getField(fieldType3.getName());
fail();
} catch (FieldNotFoundException expected) {
}
Record readRecord = repository.read(record.getId());
assertEquals("value2", readRecord.getField(fieldType1.getName()));
assertEquals(123, readRecord.getField(fieldType2.getName()));
assertEquals(true, readRecord.getField(fieldType3.getName()));
}
@Test
public void testEmptyUpdate() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(record.getRecordTypeName(), record.getRecordTypeVersion());
Record updatedRecord = repository.update(updateRecord);
assertEquals(Long.valueOf(1), updatedRecord.getVersion());
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
updatedRecord.getField(fieldType1.getName());
fail();
} catch (FieldNotFoundException expected) {
}
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
updatedRecord.getField(fieldType2.getName());
fail();
} catch (FieldNotFoundException expected) {
}
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
updatedRecord.getField(fieldType3.getName());
fail();
} catch (FieldNotFoundException expected) {
}
assertEquals(record, repository.read(record.getId()));
}
@Test
public void testIdempotentUpdate() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
Record updatedRecord = repository.update(updateRecord);
assertEquals(Long.valueOf(1), updatedRecord.getVersion());
assertEquals("value1", updatedRecord.getField(fieldType1.getName()));
assertEquals(123, updatedRecord.getField(fieldType2.getName()));
assertEquals(true, updatedRecord.getField(fieldType3.getName()));
assertEquals(record, repository.read(record.getId()));
}
@Test
public void testUpdateIgnoresVersion() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
updateRecord.setVersion(Long.valueOf(99));
updateRecord.setField(fieldType1.getName(), "value2");
Record updatedRecord = repository.update(updateRecord);
assertEquals(Long.valueOf(1), updatedRecord.getVersion());
assertEquals(updatedRecord, repository.read(record.getId()));
}
@Test
public void testUpdateNonVersionable() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(record.getRecordTypeName());
updateRecord.setField(fieldType1.getName(), "aNewValue");
repository.update(updateRecord);
Record readRecord = repository.read(record.getId());
assertEquals(Long.valueOf(1), readRecord.getVersion());
assertEquals("aNewValue", readRecord.getField(fieldType1.getName()));
}
@Test
public void testReadOlderVersions() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord.setField(fieldType3.getName(), false);
// This update will use the latest version of the RecordType
// I.e. version2 of recordType1 instead of version 1
repository.update(updateRecord);
record.setRecordType(recordType1B.getName(), recordType1B.getVersion());
record.setField(fieldType1.getName(), "value2");
assertEquals(record, repository.read(record.getId(), 1L));
try {
if (avro) {
System.out.println("Expecting RecordNotFoundException");
}
repository.read(record.getId(), 0L);
fail();
} catch (RecordNotFoundException expected) {
}
}
@Test
public void testReadAllVersions() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord.setField(fieldType3.getName(), false);
repository.update(updateRecord);
List<Record> list = repository.readVersions(record.getId(), 1L, 2L, (QName[]) null);
assertEquals(2, list.size());
assertTrue(list.contains(repository.read(record.getId(), 1L)));
assertTrue(list.contains(repository.read(record.getId(), 2L)));
}
@Test
public void testReadVersionsWideBoundaries() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord.setField(fieldType3.getName(), false);
repository.update(updateRecord);
List<Record> list = repository.readVersions(record.getId(), 0L, 5L, (QName[]) null);
assertEquals(2, list.size());
assertTrue(list.contains(repository.read(record.getId(), 1L)));
assertTrue(list.contains(repository.read(record.getId(), 2L)));
}
@Test
public void testReadVersionsNarrowBoundaries() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord.setField(fieldType3.getName(), false);
repository.update(updateRecord);
updateRecord = record.cloneRecord();
updateRecord.setField(fieldType2.getName(), 790);
repository.update(updateRecord);
updateRecord = record.cloneRecord();
updateRecord.setField(fieldType2.getName(), 791);
repository.update(updateRecord);
List<Record> list = repository.readVersions(record.getId(), 2L, 3L);
assertEquals(2, list.size());
assertTrue(list.contains(repository.read(record.getId(), 2L)));
assertTrue(list.contains(repository.read(record.getId(), 3L)));
}
@Test
public void testReadSpecificVersions() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
// Don't update this field, as a test that the internal version inheritance code works correctly
// updateRecord.setField(fieldType3.getName(), false);
repository.update(updateRecord);
// Now update this field again
updateRecord.setField(fieldType3.getName(), false);
repository.update(updateRecord);
Record record1 = repository.read(record.getId(), 1L);
Record record2 = repository.read(record.getId(), 2L);
Record record3 = repository.read(record.getId(), 3L);
List<Record> records = repository.readVersions(record.getId(), Arrays.asList(1L, 2L, 3L), (QName[]) null);
assertEquals(3, records.size());
assertTrue(records.contains(record1));
assertTrue(records.contains(record2));
assertTrue(records.contains(record3));
records = repository.readVersions(record.getId(), new ArrayList<Long>(), (QName[]) null);
assertEquals(0, records.size());
records = repository.readVersions(record.getId(), Arrays.asList(1L, 5L), (QName[]) null);
assertEquals(1, records.size());
assertTrue(records.contains(record1));
}
@Test
public void testReadNonExistingRecord() throws Exception {
try {
if (avro) {
System.out.println("Expecting RecordNotFoundException");
}
repository.read(idGenerator.newRecordId());
fail();
} catch (RecordNotFoundException expected) {
}
}
@Test
public void testReadTooRecentRecord() throws Exception {
Record record = createDefaultRecord();
try {
if (avro) {
System.out.println("Expecting VersionNotFoundException");
}
repository.read(record.getId(), Long.valueOf(2));
fail();
} catch (VersionNotFoundException expected) {
}
}
@Test
public void testReadSpecificFields() throws Exception {
Record record = createDefaultRecord();
Record readRecord =
repository.read(record.getId(), fieldType1.getName(), fieldType2.getName(), fieldType3.getName());
assertEquals(repository.read(record.getId()), readRecord);
}
@Test
public void testUpdateWithNewRecordTypeVersion() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(recordType1B.getName(), recordType1B.getVersion());
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord.setField(fieldType3.getName(), false);
Record updatedRecord = repository.update(updateRecord);
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName());
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion());
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion(Scope.NON_VERSIONED));
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName(Scope.VERSIONED));
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion(Scope.VERSIONED));
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
Record recordV1 = repository.read(record.getId(), Long.valueOf(1));
assertEquals(recordType1B.getName(), recordV1.getRecordTypeName());
assertEquals(recordType1B.getVersion(), recordV1.getRecordTypeVersion());
assertEquals(recordType1B.getName(), recordV1.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(recordType1B.getVersion(), recordV1.getRecordTypeVersion(Scope.NON_VERSIONED));
assertEquals(recordType1.getName(), recordV1.getRecordTypeName(Scope.VERSIONED));
assertEquals(recordType1.getVersion(), recordV1.getRecordTypeVersion(Scope.VERSIONED));
assertEquals(recordType1.getName(), recordV1.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(recordType1.getVersion(), recordV1.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
}
@Test
public void testUpdateWithNewRecordTypeVersionWithoutOtherChanges() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(recordType1B.getName(), recordType1B.getVersion());
Record updatedRecord = repository.update(updateRecord);
// this should have done nothing
assertEquals(record, repository.read(record.getId()));
}
@Test
public void testUpdateWithNewRecordTypeVersionWithoutOtherChangesWithUseLatestRecordTypeFalse() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(recordType1B.getName(), recordType1B.getVersion());
// update with a version set and with useLatestRecordType=false, this should actually set the version to the
// requested version (even though nothing else changed in the record)
Record updatedRecord = repository.update(updateRecord, false, false);
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName());
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion());
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion(Scope.NON_VERSIONED));
assertEquals(recordType1.getName(), updatedRecord.getRecordTypeName(Scope.VERSIONED));
assertEquals(recordType1.getVersion(), updatedRecord.getRecordTypeVersion(Scope.VERSIONED));
assertEquals(recordType1.getName(), updatedRecord.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(recordType1.getVersion(), updatedRecord.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
Record recordV1 = repository.read(record.getId(), Long.valueOf(1));
assertEquals(recordType1B.getName(), recordV1.getRecordTypeName());
assertEquals(recordType1B.getVersion(), recordV1.getRecordTypeVersion());
assertEquals(recordType1B.getName(), recordV1.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(recordType1B.getVersion(), recordV1.getRecordTypeVersion(Scope.NON_VERSIONED));
assertEquals(recordType1.getName(), recordV1.getRecordTypeName(Scope.VERSIONED));
assertEquals(recordType1.getVersion(), recordV1.getRecordTypeVersion(Scope.VERSIONED));
assertEquals(recordType1.getName(), recordV1.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(recordType1.getVersion(), recordV1.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
}
@Test
public void testUpdateWithNewRecordTypeVersionOnlyOneFieldUpdated() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(recordType1B.getName(), recordType1B.getVersion());
updateRecord.setField(fieldType2.getName(), 789);
// changed a versioned field, so versioned and non versioned scope should be on the new record type version
Record updatedRecord = repository.update(updateRecord);
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName());
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion());
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName(Scope.VERSIONED));
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion(Scope.VERSIONED));
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion(Scope.NON_VERSIONED));
// double check
Record readRecord = repository.read(record.getId());
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName());
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion());
assertEquals(recordType1B.getName(), readRecord.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(recordType1B.getVersion(), readRecord.getRecordTypeVersion(Scope.NON_VERSIONED));
assertEquals(recordType1B.getName(), updatedRecord.getRecordTypeName(Scope.VERSIONED));
assertEquals(recordType1B.getVersion(), updatedRecord.getRecordTypeVersion(Scope.VERSIONED));
assertEquals(recordType1.getName(), readRecord.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(recordType1.getVersion(), readRecord.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
}
@Test
public void testUpdateWithNewRecordType() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(recordType2.getName(), recordType2.getVersion());
updateRecord.setField(fieldType4.getName(), 1024);
updateRecord.setField(fieldType5.getName(), false);
updateRecord.setField(fieldType6.getName(), "value2");
Record updatedRecord = repository.update(updateRecord);
assertEquals(recordType2.getName(), updatedRecord.getRecordTypeName());
assertEquals(recordType2.getVersion(), updatedRecord.getRecordTypeVersion());
assertEquals(recordType2.getName(), updatedRecord.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(recordType2.getVersion(), updatedRecord.getRecordTypeVersion(Scope.NON_VERSIONED));
assertEquals(recordType2.getName(), updatedRecord.getRecordTypeName(Scope.VERSIONED));
assertEquals(recordType2.getVersion(), updatedRecord.getRecordTypeVersion(Scope.VERSIONED));
assertEquals(recordType2.getName(), updatedRecord.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(recordType2.getVersion(), updatedRecord.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
assertEquals(3, updatedRecord.getFields().size());
Record readRecord = repository.read(record.getId());
// Nothing got deleted
assertEquals(6, readRecord.getFields().size());
assertEquals("value1", readRecord.getField(fieldType1.getName()));
assertEquals(1024, readRecord.getField(fieldType4.getName()));
assertEquals(123, readRecord.getField(fieldType2.getName()));
assertFalse((Boolean) readRecord.getField(fieldType5.getName()));
assertTrue((Boolean) readRecord.getField(fieldType3.getName()));
assertEquals("value2", readRecord.getField(fieldType6.getName()));
}
@Test
public void testDeleteField() throws Exception {
Record record = createDefaultRecord();
Record deleteRecord = repository.newRecord(record.getId());
deleteRecord.setRecordType(record.getRecordTypeName());
deleteRecord.addFieldsToDelete(Arrays.asList(fieldType1.getName(), fieldType2.getName(), fieldType3.getName()));
repository.update(deleteRecord);
Record readRecord = repository.read(record.getId());
assertEquals(0, readRecord.getFields().size());
}
@Test
public void testDeleteFieldFollowedBySet() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName(), recordType1.getVersion());
record.setField(fieldType1.getName(), "hello");
record = repository.create(record);
// Delete the field
record.delete(fieldType1.getName(), true);
record = repository.update(record);
assertFalse(record.getFieldsToDelete().contains(fieldType1.getName()));
// Set the field again
record.setField(fieldType1.getName(), "hello");
record = repository.update(record);
assertEquals("hello", record.getField(fieldType1.getName()));
// Check it also there after a fresh read
record = repository.read(record.getId());
assertEquals("hello", record.getField(fieldType1.getName()));
// Calling delete field followed by set field should remove it from the deleted fields
record.delete(fieldType1.getName(), true);
assertTrue(record.getFieldsToDelete().contains(fieldType1.getName()));
record.setField(fieldType1.getName(), "hello");
assertFalse(record.getFieldsToDelete().contains(fieldType1.getName()));
}
@Test
public void testDeleteFieldsNoLongerInRecordType() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(recordType2.getName(), recordType2.getVersion());
updateRecord.setField(fieldType4.getName(), 2222);
updateRecord.setField(fieldType5.getName(), false);
updateRecord.setField(fieldType6.getName(), "value2");
repository.update(updateRecord);
Record deleteRecord = repository.newRecord(record.getId());
deleteRecord.setRecordType(recordType1.getName(), recordType1.getVersion());
deleteRecord.addFieldsToDelete(Arrays.asList(fieldType1.getName()));
repository.update(deleteRecord);
Record readRecord = repository.read(record.getId());
assertEquals(Long.valueOf(2), readRecord.getVersion());
assertEquals(5, readRecord.getFields().size());
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
readRecord.getField(fieldType1.getName());
fail();
} catch (FieldNotFoundException expected) {
}
assertEquals("value2", readRecord.getField(fieldType6.getName()));
assertEquals(2222, readRecord.getField(fieldType4.getName()));
deleteRecord.addFieldsToDelete(Arrays.asList(fieldType2.getName(), fieldType3.getName()));
repository.update(deleteRecord);
readRecord = repository.read(record.getId());
assertEquals(Long.valueOf(3), readRecord.getVersion());
assertEquals(3, readRecord.getFields().size());
assertEquals(2222, readRecord.getField(fieldType4.getName()));
assertEquals(false, readRecord.getField(fieldType5.getName()));
assertEquals("value2", readRecord.getField(fieldType6.getName()));
}
@Test
public void testDeleteFieldTwice() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(recordType2.getName(), recordType2.getVersion());
updateRecord.setField(fieldType4.getName(), 2222);
updateRecord.setField(fieldType5.getName(), false);
updateRecord.setField(fieldType6.getName(), "value2");
repository.update(updateRecord);
Record deleteRecord = repository.newRecord(record.getId());
deleteRecord.setRecordType(recordType1.getName(), recordType1.getVersion());
deleteRecord.addFieldsToDelete(Arrays.asList(fieldType1.getName()));
repository.update(deleteRecord);
repository.update(deleteRecord);
}
@Test
public void testUpdateAfterDelete() throws Exception {
Record record = createDefaultRecord();
Record deleteRecord = repository.newRecord(record.getId());
deleteRecord.setRecordType(record.getRecordTypeName(), record.getRecordTypeVersion());
deleteRecord.addFieldsToDelete(Arrays.asList(fieldType2.getName()));
repository.update(deleteRecord);
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(record.getRecordTypeName(), record.getRecordTypeVersion());
updateRecord.setField(fieldType2.getName(), 3333);
repository.update(updateRecord);
// Read version 3
Record readRecord = repository.read(record.getId());
assertEquals(Long.valueOf(3), readRecord.getVersion());
assertEquals(3333, readRecord.getField(fieldType2.getName()));
// Read version 2
readRecord = repository.read(record.getId(), Long.valueOf(2));
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
readRecord.getField(fieldType2.getName());
fail();
} catch (FieldNotFoundException expected) {
}
// Read version 1
readRecord = repository.read(record.getId(), Long.valueOf(1));
assertEquals(123, readRecord.getField(fieldType2.getName()));
}
@Test
public void testDeleteNonVersionableFieldAndUpdateVersionableField() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(record.getRecordTypeName(), record.getRecordTypeVersion());
updateRecord.setField(fieldType2.getName(), 999);
updateRecord.addFieldsToDelete(Arrays.asList(fieldType1.getName()));
repository.update(updateRecord);
Record readRecord = repository.read(record.getId());
assertEquals(999, readRecord.getField(fieldType2.getName()));
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
readRecord.getField(fieldType1.getName());
fail();
} catch (FieldNotFoundException expected) {
}
readRecord = repository.read(record.getId(), Long.valueOf(1));
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
readRecord.getField(fieldType1.getName());
fail();
} catch (FieldNotFoundException expected) {
}
}
@Test
public void testUpdateAndDeleteSameField() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(record.getRecordTypeName(), record.getRecordTypeVersion());
updateRecord.setField(fieldType2.getName(), 789);
updateRecord.addFieldsToDelete(Arrays.asList(fieldType2.getName()));
repository.update(updateRecord);
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
repository.read(record.getId()).getField(fieldType2.getName());
fail();
} catch (FieldNotFoundException expected) {
}
}
@Test
public void testDeleteRecordById() throws Exception {
Record record = createDefaultRecord();
repository.delete(record.getId());
try {
if (avro) {
System.out.println("Expecting RecordNotFoundException");
}
repository.read(record.getId());
fail();
} catch (RecordNotFoundException expected) {
}
try {
if (avro) {
System.out.println("Expecting RecordNotFoundException");
}
repository.update(record);
fail();
} catch (RecordNotFoundException expected) {
}
try {
if (avro) {
System.out.println("Expecting RecordNotFoundException");
}
repository.delete(record.getId());
fail();
} catch (RecordNotFoundException expected) {
}
}
@Test
public void testDeleteRecord() throws Exception {
Record record = createDefaultRecord();
repository.delete(record);
try {
if (avro) {
System.out.println("Expecting RecordNotFoundException");
}
repository.read(record.getId());
fail();
} catch (RecordNotFoundException expected) {
}
try {
if (avro) {
System.out.println("Expecting RecordNotFoundException");
}
repository.update(record);
fail();
} catch (RecordNotFoundException expected) {
}
try {
if (avro) {
System.out.println("Expecting RecordNotFoundException");
}
repository.delete(record.getId());
fail();
} catch (RecordNotFoundException expected) {
}
}
@Test
public void testDeleteRecordCleansUpData() throws Exception {
Record record = createDefaultRecord();
RecordId recordId = record.getId();
repository.delete(recordId);
record = repository.newRecord(recordId);
record.setRecordType(recordType2.getName(), recordType2.getVersion());
record.setField(fieldType4.getName(), 555);
record.setField(fieldType5.getName(), false);
record.setField(fieldType6.getName(), "zzz");
repository.create(record);
Record readRecord = repository.read(recordId);
assertEquals(Long.valueOf(2), readRecord.getVersion());
try {
readRecord.getField(fieldType1.getName());
fail();
} catch (FieldNotFoundException expected) {
}
try {
readRecord.getField(fieldType2.getName());
fail();
} catch (FieldNotFoundException expected) {
}
try {
readRecord.getField(fieldType3.getName());
fail();
} catch (FieldNotFoundException expected) {
}
assertEquals(555, readRecord.getField(fieldType4.getName()));
assertFalse((Boolean) readRecord.getField(fieldType5.getName()));
assertEquals("zzz", readRecord.getField(fieldType6.getName()));
}
@Test
public void testRecordRecreateFromVersionedToNonVersioned() throws Exception {
QName vfield = new QName("recreate", "vfield");
QName nvfield = new QName("recreate", "nvfield");
FieldType vfieldtype = typeManager.newFieldType(typeManager.getValueType("STRING"), vfield, Scope.VERSIONED);
vfieldtype = typeManager.createFieldType(vfieldtype);
FieldType nvfieldtype =
typeManager.newFieldType(typeManager.getValueType("STRING"), nvfield, Scope.NON_VERSIONED);
nvfieldtype = typeManager.createFieldType(nvfieldtype);
RecordType rt = typeManager.newRecordType(new QName("reinc", "rt"));
rt.addFieldTypeEntry(vfieldtype.getId(), false);
rt.addFieldTypeEntry(nvfieldtype.getId(), false);
rt = typeManager.createRecordType(rt);
// Create a record with versions
RecordId recordId = repository.getIdGenerator().newRecordId();
Record record = repository.newRecord(recordId);
record.setRecordType(rt.getName());
record.setField(vfield, "value 1");
record = repository.createOrUpdate(record);
record.setField(vfield, "value 2");
record = repository.createOrUpdate(record);
assertEquals(2L, record.getVersion().longValue());
// Delete the record
repository.delete(recordId);
// Re-create the record, this time without versions
record = repository.newRecord(recordId);
record.setRecordType(rt.getName());
record.setField(nvfield, "nv value 1");
record = repository.createOrUpdate(record);
assertEquals(null, record.getVersion());
assertEquals(rt.getName(), record.getRecordTypeName());
// Now add a version again, reusing last value from previously deleted record
record.setField(vfield, "value 2");
record = repository.createOrUpdate(record);
assertEquals(3L, record.getVersion().longValue());
}
@Test
public void testRecordRecreateOnlyVersionedFields() throws Exception {
QName versionedOnlyQN = new QName(namespace, "VersionedOnly");
RecordType versionedOnlyRT = typeManager.newRecordType(versionedOnlyQN);
versionedOnlyRT.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType2.getId(), false));
versionedOnlyRT = typeManager.createRecordType(versionedOnlyRT);
Record record = repository.newRecord();
record.setRecordType(versionedOnlyQN);
record.setField(fieldType2.getName(), 111);
record = repository.create(record);
RecordId id = record.getId();
repository.delete(id);
record = repository.newRecord(id);
record.setRecordType(versionedOnlyQN);
record.setField(fieldType2.getName(), 222);
record = repository.create(record);
assertEquals(versionedOnlyQN, record.getRecordTypeName());
record = repository.read(id);
assertEquals(versionedOnlyQN, record.getRecordTypeName());
assertEquals(versionedOnlyQN, record.getRecordTypeName(Scope.VERSIONED));
}
@Test
public void testRecordRecreateNonVersionedOnly() throws Exception {
QName nvfield = new QName("recreate", "OnlyNonVersioned");
FieldType nvfieldtype =
typeManager.newFieldType(typeManager.getValueType("STRING"), nvfield, Scope.NON_VERSIONED);
nvfieldtype = typeManager.createFieldType(nvfieldtype);
QName rtName = new QName("recreate", "rtOnlyNonVersioned");
RecordType rt = typeManager.newRecordType(rtName);
rt.addFieldTypeEntry(nvfieldtype.getId(), false);
rt = typeManager.createRecordType(rt);
// Create a record with versions
RecordId recordId = repository.getIdGenerator().newRecordId();
Record record = repository.newRecord(recordId);
record.setRecordType(rt.getName());
record.setField(nvfield, "nv value 1");
record = repository.createOrUpdate(record);
record = repository.read(record.getId());
assertEquals("nv value 1", record.getField(nvfield));
assertEquals(rtName, record.getRecordTypeName());
assertEquals(null, record.getVersion());
// Delete the record
repository.delete(recordId);
// Re-create the record,
record = repository.newRecord(recordId);
record.setRecordType(rt.getName());
record.setField(nvfield, "nv value 2");
record = repository.createOrUpdate(record);
assertEquals(rtName, record.getRecordTypeName());
assertEquals(null, record.getVersion());
record = repository.read(record.getId());
assertEquals("nv value 2", record.getField(nvfield));
assertEquals(rtName, record.getRecordTypeName());
}
@Test
public void testUpdateMutableField() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType2.getName(), recordType2.getVersion());
record.setField(fieldType4.getName(), 123);
record.setField(fieldType5.getName(), true);
record.setField(fieldType6.getName(), "value1");
record = repository.create(record);
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType4.getName(), 456);
updateRecord.setField(fieldType5.getName(), false);
updateRecord.setField(fieldType6.getName(), "value2");
repository.update(updateRecord);
// Read version 1
Record readRecord = repository.read(record.getId(), Long.valueOf(1));
assertEquals(456, readRecord.getField(fieldType4.getName()));
assertEquals(true, readRecord.getField(fieldType5.getName()));
assertEquals("value1", readRecord.getField(fieldType6.getName()));
// Update mutable version 1
Record mutableRecord = repository.newRecord(record.getId());
mutableRecord.setRecordType(recordType2.getName(), recordType2.getVersion());
mutableRecord.setField(fieldType6.getName(), "value3");
mutableRecord.setVersion(1L);
mutableRecord = repository.update(mutableRecord, true, false);
// Read version 1 again
readRecord = repository.read(record.getId(), 1L);
assertEquals(456, readRecord.getField(fieldType4.getName()));
assertEquals(true, readRecord.getField(fieldType5.getName()));
assertEquals("value3", readRecord.getField(fieldType6.getName()));
// Update mutable version 2
mutableRecord.setVersion(2L);
mutableRecord.setField(fieldType6.getName(), "value4");
mutableRecord = repository.update(mutableRecord, true, false);
// Read version 2
readRecord = repository.read(record.getId(), 2L);
assertEquals(456, readRecord.getField(fieldType4.getName()));
assertEquals(false, readRecord.getField(fieldType5.getName()));
assertEquals("value4", readRecord.getField(fieldType6.getName()));
}
@Test
public void testUpdateMutableFieldWithNewRecordType() throws Exception {
// Create default record
Record record = createDefaultRecord();
// Update the record, creates a second version
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord.setField(fieldType3.getName(), false);
repository.update(updateRecord, false, false);
// Read the first version of the record
Record readRecord = repository.read(record.getId(), Long.valueOf(1));
assertEquals(recordType1.getName(), readRecord.getRecordTypeName());
assertEquals(recordType1.getVersion(), readRecord.getRecordTypeVersion());
assertEquals(recordType1.getName(), readRecord.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(recordType1.getVersion(), readRecord.getRecordTypeVersion(Scope.NON_VERSIONED));
assertEquals(recordType1.getName(), readRecord.getRecordTypeName(Scope.VERSIONED));
assertEquals(recordType1.getVersion(), readRecord.getRecordTypeVersion(Scope.VERSIONED));
assertEquals(recordType1.getName(), readRecord.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(recordType1.getVersion(), readRecord.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
// Do a mutable update of the first version of the record, change the record type
Record updateMutableRecord = repository.newRecord(record.getId());
updateMutableRecord.setVersion(Long.valueOf(1));
updateMutableRecord.setRecordType(recordType2.getName(), recordType2.getVersion());
updateMutableRecord.setField(fieldType4.getName(), 888);
updateMutableRecord.setField(fieldType5.getName(), false);
updateMutableRecord.setField(fieldType6.getName(), "value3");
Record updatedMutableRecord = repository.update(updateMutableRecord, true, false);
assertEquals(Long.valueOf(1), updatedMutableRecord.getVersion());
// Read the first version of the record again
readRecord = repository.read(record.getId(), Long.valueOf(1));
assertEquals(Long.valueOf(1), readRecord.getVersion());
assertEquals("value2", readRecord.getField(fieldType1.getName()));
assertEquals(123, readRecord.getField(fieldType2.getName()));
assertEquals(true, readRecord.getField(fieldType3.getName()));
// Only the mutable fields got updated
assertEquals("value3", readRecord.getField(fieldType6.getName()));
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
readRecord.getField(fieldType4.getName());
fail();
} catch (FieldNotFoundException expected) {
}
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
readRecord.getField(fieldType5.getName());
fail();
} catch (FieldNotFoundException expected) {
}
assertEquals(recordType1.getName(), readRecord.getRecordTypeName());
assertEquals(recordType1.getVersion(), readRecord.getRecordTypeVersion());
assertEquals(recordType1.getName(), readRecord.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(recordType1.getVersion(), readRecord.getRecordTypeVersion(Scope.NON_VERSIONED));
assertEquals(recordType1.getName(), readRecord.getRecordTypeName(Scope.VERSIONED));
assertEquals(recordType1.getVersion(), readRecord.getRecordTypeVersion(Scope.VERSIONED));
// The mutable record type should have been changed
assertEquals(recordType2.getName(), readRecord.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(recordType2.getVersion(), readRecord.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
// Read the second version again of the record
readRecord = repository.read(record.getId());
assertEquals(Long.valueOf(2), readRecord.getVersion());
assertEquals("value2", readRecord.getField(fieldType1.getName()));
assertEquals(789, readRecord.getField(fieldType2.getName()));
assertEquals(false, readRecord.getField(fieldType3.getName()));
assertEquals("value3", readRecord.getField(fieldType6.getName()));
assertEquals(recordType1.getName(), readRecord.getRecordTypeName());
assertEquals(recordType1.getName(), readRecord.getRecordTypeName(Scope.NON_VERSIONED));
assertEquals(recordType1.getName(), readRecord.getRecordTypeName(Scope.VERSIONED));
// The original mutable record type should have been copied to the next version
assertEquals(recordType1.getName(), readRecord.getRecordTypeName(Scope.VERSIONED_MUTABLE));
}
@Test
public void testUpdateMutableFieldCopiesValueToNext() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord = repository.update(updateRecord); // Leave mutable field
// same on first update
updateRecord.setField(fieldType3.getName(), false);
updateRecord = repository.update(updateRecord);
Record readRecord = repository.read(record.getId(), Long.valueOf(2));
assertEquals(true, readRecord.getField(fieldType3.getName()));
updateRecord = repository.newRecord(record.getId());
updateRecord.setRecordType(recordType1.getName(), recordType1.getVersion());
updateRecord.setField(fieldType3.getName(), false);
updateRecord.setVersion(1L);
repository.update(updateRecord, true, false);
readRecord = repository.read(record.getId(), Long.valueOf(1));
assertFalse((Boolean) readRecord.getField(fieldType3.getName()));
readRecord = repository.read(record.getId(), Long.valueOf(2));
assertTrue((Boolean) readRecord.getField(fieldType3.getName()));
readRecord = repository.read(record.getId(), Long.valueOf(3));
assertFalse((Boolean) readRecord.getField(fieldType3.getName()));
}
@Test
public void testDeleteMutableField() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord.setField(fieldType3.getName(), false);
repository.update(updateRecord);
Record deleteRecord = repository.newRecord(record.getId());
deleteRecord.setVersion(Long.valueOf(1));
deleteRecord.setRecordType(record.getRecordTypeName(), record.getRecordTypeVersion());
deleteRecord.addFieldsToDelete(Arrays.asList(fieldType1.getName(), fieldType2.getName(), fieldType3.getName()));
repository.update(deleteRecord, true, false);
Record readRecord = repository.read(record.getId(), Long.valueOf(1));
// The non-mutable fields were ignored
assertEquals("value2", readRecord.getField(fieldType1.getName()));
assertEquals(123, readRecord.getField(fieldType2.getName()));
try {
// The mutable field got deleted
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
readRecord.getField(fieldType3.getName());
fail();
} catch (FieldNotFoundException expected) {
}
readRecord = repository.read(record.getId());
assertEquals(false, readRecord.getField(fieldType3.getName()));
}
@Test
public void testDeleteMutableFieldCopiesValueToNext() throws Exception {
Record record = createDefaultRecord();
Record updateRecord = record.cloneRecord();
updateRecord.setField(fieldType1.getName(), "value2");
updateRecord.setField(fieldType2.getName(), 789);
updateRecord = repository.update(updateRecord); // Leave mutable field
// same on first update
updateRecord.setField(fieldType3.getName(), false);
updateRecord = repository.update(updateRecord);
Record readRecord = repository.read(record.getId(), Long.valueOf(2));
assertEquals(true, readRecord.getField(fieldType3.getName()));
Record deleteMutableFieldRecord = repository.newRecord(record.getId());
deleteMutableFieldRecord.setVersion(Long.valueOf(1));
deleteMutableFieldRecord.setRecordType(record.getRecordTypeName(), record.getRecordTypeVersion());
deleteMutableFieldRecord.addFieldsToDelete(Arrays.asList(fieldType3.getName()));
repository.update(deleteMutableFieldRecord, true, false);
readRecord = repository.read(record.getId(), Long.valueOf(1));
try {
if (avro) {
System.out.println("Expecting FieldNotFoundException");
}
readRecord.getField(fieldType3.getName());
fail();
} catch (FieldNotFoundException expected) {
}
readRecord = repository.read(record.getId(), Long.valueOf(2));
assertEquals(true, readRecord.getField(fieldType3.getName()));
readRecord = repository.read(record.getId());
assertEquals(false, readRecord.getField(fieldType3.getName()));
}
@Test
public void testSupertypeLatestVersion() throws Exception {
RecordType recordType4 = typeManager.newRecordType(new QName(namespace, "RT4"));
recordType4.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType6.getId(), false));
recordType4.addSupertype(recordType1.getId()); // In fact recordType1B should be taken as supertype
recordType4 = typeManager.createRecordType(recordType4);
Record record = repository.newRecord(idGenerator.newRecordId());
record.setRecordType(recordType4.getName(), recordType4.getVersion());
record.setField(fieldType1.getName(), "foo");
record.setField(fieldType2.getName(), 555);
record.setField(fieldType3.getName(), true);
record.setField(fieldType1B.getName(), "fromLatestSupertypeRecordTypeVersion");
record.setField(fieldType6.getName(), "bar");
record = repository.create(record);
Record readRecord = repository.read(record.getId());
assertEquals(Long.valueOf(1), readRecord.getVersion());
assertEquals("foo", readRecord.getField(fieldType1.getName()));
assertEquals(555, readRecord.getField(fieldType2.getName()));
assertEquals(true, readRecord.getField(fieldType3.getName()));
assertEquals("fromLatestSupertypeRecordTypeVersion", readRecord.getField(fieldType1B.getName()));
assertEquals("bar", readRecord.getField(fieldType6.getName()));
}
@Test
public void testNonVersionedToVersioned() throws Exception {
// Create a record with only a versioned and non-versioned field
Record record = repository.newRecord();
record.setRecordType(recordType1.getName(), recordType1.getVersion());
record.setField(fieldType1.getName(), "hello");
record.setField(fieldType2.getName(), new Integer(4));
record = repository.create(record);
// Try to read the created version
record = repository.read(record.getId(), 1L);
}
@Test
public void testIdRecord() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName(), recordType1.getVersion());
record.setField(fieldType1.getName(), "hello");
record.setField(fieldType2.getName(), new Integer(4));
record = repository.create(record);
IdRecord idRecord = repository.readWithIds(record.getId(), null, null);
assertEquals("hello", idRecord.getField(fieldType1.getId()));
assertTrue(idRecord.hasField(fieldType1.getId()));
assertEquals(new Integer(4), idRecord.getField(fieldType2.getId()));
assertTrue(idRecord.hasField(fieldType2.getId()));
Map<SchemaId, Object> fields = idRecord.getFieldsById();
assertEquals(2, fields.size());
assertEquals("hello", fields.get(fieldType1.getId()));
assertEquals(new Integer(4), fields.get(fieldType2.getId()));
assertEquals(record, idRecord.getRecord());
}
@Test
public void testVersionNumbers() throws Exception {
// Create a record without versioned fields, the record will be without versions
Record record = repository.newRecord();
record.setRecordType(recordType1.getName(), recordType1.getVersion());
record.setField(fieldType1.getName(), "hello");
record = repository.create(record);
// Check the version is null
assertEquals(null, record.getVersion());
// Check version number stays null after additional update
record.setField(fieldType1.getName(), "hello2");
repository.update(record);
record = repository.read(record.getId());
assertEquals(null, record.getVersion());
// add a versioned field to the record
record.setField(fieldType2.getName(), new Integer(4));
record = repository.update(record);
assertEquals(new Long(1), record.getVersion());
// Verify the last version number after a fresh record read
record = repository.read(record.getId());
assertEquals(new Long(1), record.getVersion());
// Read specific version
record = repository.read(record.getId(), 1L);
assertEquals(new Long(1), record.getVersion());
assertTrue(record.hasField(fieldType2.getName()));
assertEquals(2, record.getFields().size());
try {
if (avro) {
System.out.println("Expecting VersionNotFoundException");
}
record = repository.read(record.getId(), 2L);
fail("expected exception");
} catch (VersionNotFoundException e) {
// expected
}
}
@Test
public void testValidateCreate() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType3.getName(), 1L);
record.setField(fieldType2.getName(), 123);
repository.create(record);
record = repository.newRecord();
record.setRecordType(recordType3.getName(), 2L);
record.setField(fieldType2.getName(), 123);
try {
if (avro) {
System.out.println("Expecting InvalidRecordException");
}
repository.create(record);
fail();
} catch (InvalidRecordException expected) {
}
record = repository.newRecord();
record.setRecordType(recordType3.getName(), 2L);
record.setField(fieldType1.getName(), "abc");
record.setField(fieldType2.getName(), 123);
}
@Test
public void testValidateUpdate() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType3.getName(), 1L);
record.setField(fieldType2.getName(), 123);
record = repository.create(record);
record.setRecordType(recordType3.getName(), 2L);
record.setField(fieldType2.getName(), 567);
try {
if (avro) {
System.out.println("Expecting InvalidRecordException");
}
repository.update(record, false, false);
fail();
} catch (InvalidRecordException expected) {
}
record.setField(fieldType1.getName(), "abc");
repository.update(record, false, false);
}
@Test
public void testValidateMutableUpdate() throws Exception {
// Nothing mandatory
Record record = repository.newRecord();
record.setRecordType(recordType3.getName(), 1L);
record.setField(fieldType2.getName(), 123);
record = repository.create(record);
// Non-versioned field1 mandatory
record = repository.newRecord(record.getId());
record.setRecordType(recordType3.getName(), 2L);
record.setField(fieldType1.getName(), "abc");
repository.update(record, false, false); // record version 1
// Mutable field3 mandatory
record.setRecordType(recordType3.getName(), 3L);
record.setField(fieldType1.getName(), "efg");
try {
if (avro) {
System.out.println("Expecting InvalidRecordException");
}
repository.update(record, false, false);
fail();
} catch (InvalidRecordException expected) {
}
// Mutable field3 not mandatory
record = repository.newRecord(record.getId());
record.setRecordType(recordType3.getName(), 2L);
record.setField(fieldType3.getName(), true);
repository.update(record, false, false); // record version 2
// Mutable field update of record version 1 with field3 mandatory
// Field3 already exists, but in record version 2 not version 1
record = repository.newRecord(record.getId());
record.setRecordType(recordType3.getName(), 4L);
record.setField(fieldType6.getName(), "zzz");
record.setVersion(1L);
try {
if (avro) {
System.out.println("Expecting InvalidRecordException");
}
repository.update(record, true, false);
fail();
} catch (InvalidRecordException expected) {
}
record.setField(fieldType3.getName(), false);
repository.update(record, true, false);
}
@Test
public void testCreateOrUpdate() throws Exception {
RecordId id = idGenerator.newRecordId();
Record record = repository.newRecord(id);
record.setRecordType(recordType1.getName(), recordType1.getVersion());
record.setField(fieldType1.getName(), "value1");
Record resultRecord;
resultRecord = repository.createOrUpdate(record);
assertEquals(ResponseStatus.CREATED, resultRecord.getResponseStatus());
resultRecord = repository.createOrUpdate(record);
assertEquals(ResponseStatus.UP_TO_DATE, resultRecord.getResponseStatus());
record.setField(fieldType1.getName(), "value2");
resultRecord = repository.createOrUpdate(record);
assertEquals(ResponseStatus.UPDATED, resultRecord.getResponseStatus());
resultRecord = repository.createOrUpdate(record);
assertEquals(ResponseStatus.UP_TO_DATE, resultRecord.getResponseStatus());
resultRecord = repository.createOrUpdate(record);
assertEquals(ResponseStatus.UP_TO_DATE, resultRecord.getResponseStatus());
}
@Test
public void testUpdateMutableFieldsRecordType() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType2.getName(), recordType2.getVersion());
record.setField(fieldType4.getName(), 123);
record.setField(fieldType5.getName(), true);
record.setField(fieldType6.getName(), "value1");
record = repository.create(record);
// Updating versioned mutable fields should not require record type to be specified in the record
record = repository.newRecord(record.getId());
record.setVersion(1L);
record.setField(fieldType6.getName(), "value2");
record = repository.update(record, true, true);
// Change record to a different record type
RecordType recordTypeA = typeManager.newRecordType(new QName("testmut", "RTA"));
recordTypeA.addFieldTypeEntry(fieldType4.getId(), false);
recordTypeA.addFieldTypeEntry(fieldType5.getId(), false);
recordTypeA.addFieldTypeEntry(fieldType6.getId(), false);
recordTypeA = typeManager.createRecordType(recordTypeA);
// Change the record type of the non-versioned scope (at the time of this writing, could not modify
// just the record type of a record, hence also touching a field)
record = repository.read(record.getId());
record.setRecordType(recordTypeA.getName(), null);
record.setField(fieldType4.getName(), 456);
record = repository.update(record);
record = repository.read(record.getId());
assertEquals(recordTypeA.getName(), record.getRecordTypeName());
// The record type of the versioned mutable scope should still be what it was before
assertEquals(recordType2.getName(), record.getRecordTypeName(Scope.VERSIONED_MUTABLE));
// Now update a versioned-mutable field without specifying a record type, would expect it to move
// also to the new record type of the non-versioned scope.
record = repository.newRecord(record.getId());
record.setVersion(1L);
record.setField(fieldType6.getName(), "value3");
record = repository.update(record, true, true);
assertEquals(recordTypeA.getName(), record.getRecordTypeName(Scope.VERSIONED_MUTABLE));
assertEquals(recordTypeA.getVersion(), record.getRecordTypeVersion(Scope.VERSIONED_MUTABLE));
}
@Test
public void testReadMultipleRecords() throws Exception {
Record record1 = createDefaultRecord();
Record record2 = createDefaultRecord();
Record record3 = createDefaultRecord();
List<Record> readRecords = repository.read(Arrays.asList(record3.getId(), record1.getId()));
assertEquals(2, readRecords.size());
assertTrue(readRecords.contains(record1));
assertTrue(readRecords.contains(record3));
repository.delete(record2.getId());
readRecords = repository.read(Arrays.asList(record2.getId(), record1.getId()));
assertEquals(1, readRecords.size());
assertTrue(readRecords.contains(record1));
readRecords = repository.read(Collections.<RecordId>emptyList());
assertTrue(readRecords.isEmpty());
}
@Test
public void testConditionalUpdate() throws Exception {
Record record = createDefaultRecord();
//
// Single condition
//
record.setField(fieldType1.getName(), "value2");
record = repository
.update(record, Collections.singletonList(new MutationCondition(fieldType1.getName(), "xyz")));
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
assertEquals("value1", record.getField(fieldType1.getName()));
// Check repository state was really not modified
record = repository.read(record.getId());
assertEquals("value1", record.getField(fieldType1.getName()));
//
// Multiple conditions
//
List<MutationCondition> conditions = new ArrayList<MutationCondition>();
conditions.add(new MutationCondition(fieldType1.getName(), "value1")); // evals to true
conditions.add(new MutationCondition(fieldType2.getName(), 123)); // evals to true
conditions.add(new MutationCondition(fieldType3.getName(), false)); // evals to false
record.setField(fieldType1.getName(), "value2");
record = repository.update(record, conditions);
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
assertEquals("value1", record.getField(fieldType1.getName()));
// Check repository state was really not modified
record = repository.read(record.getId());
assertEquals("value1", record.getField(fieldType1.getName()));
// reset record state
record = createDefaultRecord();
//
// Not-equals condition
//
record.setField(fieldType1.getName(), "value2");
record = repository.update(record,
Collections.singletonList(new MutationCondition(fieldType1.getName(), CompareOp.NOT_EQUAL, "value1")));
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
assertEquals("value1", record.getField(fieldType1.getName()));
//
// Other than equals conditions
//
for (CompareOp op : CompareOp.values()) {
List<Integer> testValues = new ArrayList<Integer>();
switch (op) {
case LESS:
testValues.add(123);
testValues.add(122);
break;
case LESS_OR_EQUAL:
testValues.add(122);
break;
case EQUAL:
testValues.add(122);
testValues.add(124);
break;
case NOT_EQUAL:
testValues.add(123);
break;
case GREATER_OR_EQUAL:
testValues.add(124);
break;
case GREATER:
testValues.add(123);
testValues.add(124);
}
for (Integer testValue : testValues) {
record.setField(fieldType2.getName(), 999);
record = repository.update(record,
Collections.singletonList(new MutationCondition(fieldType2.getName(), op, testValue)));
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
assertEquals(123, record.getField(fieldType2.getName()));
}
}
//
// Allow missing fields
//
record.setField(fieldType1.getName(), "value2");
// note that we're testing on field 1B!
record = repository.update(record,
Collections.singletonList(
new MutationCondition(fieldType1B.getName(), CompareOp.EQUAL, "whatever", true)));
assertEquals(ResponseStatus.UPDATED, record.getResponseStatus());
assertEquals("value2", record.getField(fieldType1.getName()));
// reset record state
record.setField(fieldType1.getName(), "value1");
record = repository.update(record);
//
// Test for missing/present field
//
// Field MUST be missing
record.setField(fieldType1.getName(), "value2");
record = repository.update(record,
Collections.singletonList(new MutationCondition(fieldType1.getName(), CompareOp.EQUAL, null)));
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
assertEquals("value1", record.getField(fieldType1.getName()));
// Field MUST NOT be missing (but can have whatever value) -- note that we test on field 1B!
record.setField(fieldType1.getName(), "value2");
record = repository.update(record,
Collections.singletonList(new MutationCondition(fieldType1B.getName(), CompareOp.NOT_EQUAL, null)));
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
assertEquals("value1", record.getField(fieldType1.getName()));
// Same, successful case
record.setField(fieldType1.getName(), "value2");
record = repository.update(record,
Collections.singletonList(new MutationCondition(fieldType1.getName(), CompareOp.NOT_EQUAL, null)));
assertEquals(ResponseStatus.UPDATED, record.getResponseStatus());
assertEquals("value2", record.getField(fieldType1.getName()));
// reset record state
record.setField(fieldType1.getName(), "value1");
record = repository.update(record);
//
// Supplied values differ from field type (classcastexception)
//
// TODO
// record.setField(fieldType1.getName(), "value2");
// try {
// repository.update(record, Collections.singletonList(new MutationCondition(fieldType1.getName(), new Long(55))));
// fail("Expected an exception");
// } catch (ClassCastException e) {
// // expected
// }
//
// Test on system fields
//
final QName versionField = new QName("org.lilyproject.system", "version");
// Create new record to be sure numbering starts from 1
record = createDefaultRecord();
record.setField(fieldType2.getName(), new Integer(55));
record = repository.update(record,
Collections.singletonList(new MutationCondition(versionField, CompareOp.EQUAL, new Long(2))));
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
record.setField(fieldType2.getName(), new Integer(55));
record = repository.update(record,
Collections.singletonList(new MutationCondition(versionField, CompareOp.EQUAL, new Long(1))));
assertEquals(ResponseStatus.UPDATED, record.getResponseStatus());
assertEquals(new Long(2), record.getVersion());
assertEquals(new Integer(55), record.getField(fieldType2.getName()));
// Test behavior in case of null version
record = repository.newRecord();
record.setRecordType(recordType1.getName(), recordType1.getVersion());
record.setField(fieldType1.getName(), "value1");
record = repository.create(record);
record.setField(fieldType1.getName(), "value2");
record = repository.update(record,
Collections.singletonList(new MutationCondition(versionField, CompareOp.EQUAL, new Long(1))));
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
record.setField(fieldType1.getName(), "value2");
record = repository.update(record,
Collections.singletonList(new MutationCondition(versionField, CompareOp.EQUAL, null)));
assertEquals(ResponseStatus.UPDATED, record.getResponseStatus());
//
// Test conditional update on update of version-mutable fields
//
record = createDefaultRecord();
record.setField(fieldType3.getName(), false);
record = repository.update(record, true, true,
Collections.singletonList(new MutationCondition(fieldType3.getName(), CompareOp.EQUAL, Boolean.FALSE)));
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
record.setField(fieldType3.getName(), false);
record = repository.update(record, true, true,
Collections.singletonList(new MutationCondition(fieldType3.getName(), CompareOp.EQUAL, Boolean.TRUE)));
assertEquals(ResponseStatus.UPDATED, record.getResponseStatus());
// In case of versioned-mutable update, we can also add conditions on versioned and non-versioned fields
conditions = new ArrayList<MutationCondition>();
conditions.add(new MutationCondition(fieldType1.getName(), "value1")); // evals to true
conditions.add(new MutationCondition(fieldType2.getName(), 124)); // evals to true
record.setField(fieldType3.getName(), true);
record = repository.update(record, true, true, conditions);
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
}
@Test
public void testConditionUpdateConcurrency() throws Exception {
// This test uses MutationCondition's to let multiple threads concurrently increment a counter field
// in the same record.
// Create record with counter field initially set to 0
Record record = repository.newRecord();
record.setId(repository.getIdGenerator().newRecordId());
record.setRecordType(recordType2.getName());
record.setField(fieldType4.getName(), new Integer(0));
repository.createOrUpdate(record);
final RecordId recordId = record.getId();
// Run concurrent threads to increment the counter field
int threads = 5;
int count = 200;
ThreadPoolExecutor executor = new ThreadPoolExecutor(threads, threads, 10, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(count));
List<Future> futures = new ArrayList<Future>();
for (int i = 0; i < count; i++) {
futures.add(executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
int iteration = 0;
while (true) {
try {
iteration++;
Record record = repository.read(recordId);
int oldValue = (Integer) record.getField(fieldType4.getName());
record.setField(fieldType4.getName(), new Integer(oldValue + 1));
MutationCondition cond = new MutationCondition(fieldType4.getName(), oldValue, false);
record = repository.update(record, Lists.newArrayList(cond));
if (record.getResponseStatus() == ResponseStatus.CONFLICT) {
if (iteration > 20) {
System.out.println("cas failed, will retry, iteration " + iteration);
}
Thread.sleep((int) (Math.random() * 50));
} else if (record.getResponseStatus() == ResponseStatus.UPDATED) {
// success
return null;
} else {
fail("unexpected response status = " + record.getResponseStatus());
}
} catch (ConcurrentRecordUpdateException e) {
if (iteration > 20) {
System.out.println("concurrent update, will retry, iteration " + iteration);
}
Thread.sleep((int) (Math.random() * 50));
}
}
}
}));
}
for (Future future : futures) {
future.get();
}
executor.shutdown();
// verify the value of the counter field is as expected
record = repository.read(record.getId());
assertEquals(count, record.getField(fieldType4.getName()));
}
@Test
public void testConditionalDelete() throws Exception {
Record record = createDefaultRecord();
// perform delete with not-satisfied conditions
record = repository.delete(record.getId(),
Collections.singletonList(new MutationCondition(fieldType1.getName(), "xyz")));
assertNotNull(record);
assertEquals(ResponseStatus.CONFLICT, record.getResponseStatus());
assertEquals("value1", record.getField(fieldType1.getName()));
assertEquals(1, record.getFields().size());
// check record was surely not deleted
record = repository.read(record.getId());
// perform delete with satisfied conditions
record = repository.delete(record.getId(),
Collections.singletonList(new MutationCondition(fieldType1.getName(), "value1")));
assertNull(record);
}
@Test
public void testRecordWithLinkFields() throws Exception {
FieldType linkFieldType = typeManager.createFieldType(typeManager
.newFieldType(typeManager.getValueType("LINK"), new QName("testRecordWithLinkFields", "linkFieldType"),
Scope.NON_VERSIONED));
RecordType recordTypeWithLink = typeManager.newRecordType(new QName(namespace, "recordTypeWithLink"));
recordTypeWithLink.addFieldTypeEntry(typeManager.newFieldTypeEntry(linkFieldType.getId(), false));
recordTypeWithLink = typeManager.createRecordType(recordTypeWithLink);
// Create records to link to
Record record = createDefaultRecord();
Record record2 = createDefaultRecord();
// Create record with link to record
Record recordWithLinks = repository.newRecord();
recordWithLinks.setRecordType(recordTypeWithLink.getName());
Link link = Link.newBuilder().recordId(record.getId()).copyAll(false).create();
recordWithLinks.setField(linkFieldType.getName(), link);
recordWithLinks = repository.create(recordWithLinks);
// Validate link is created
link = (Link) recordWithLinks.getField(linkFieldType.getName());
assertEquals(record.getId(), link.getMasterRecordId());
// Read record again and validate link is there
recordWithLinks = repository.read(recordWithLinks.getId());
link = (Link) recordWithLinks.getField(linkFieldType.getName());
assertEquals(record.getId(), link.getMasterRecordId());
// Update record with link to record2
recordWithLinks = repository.newRecord(recordWithLinks.getId());
link = Link.newBuilder().recordId(record2.getId()).copyAll(false).create();
recordWithLinks.setField(linkFieldType.getName(), link);
recordWithLinks = repository.update(recordWithLinks);
// Validate link is updated
link = (Link) recordWithLinks.getField(linkFieldType.getName());
assertEquals(record2.getId(), link.getMasterRecordId());
// Read record and validate link is still updated
recordWithLinks = repository.read(recordWithLinks.getId());
link = (Link) recordWithLinks.getField(linkFieldType.getName());
assertEquals(record2.getId(), link.getMasterRecordId());
}
@Test
public void testRecordBuilder() throws Exception {
RecordBuilder builder = repository.recordBuilder();
Record record = builder.recordType(recordType1.getName())
.field(fieldType1.getName(), "abc")
.field(fieldType2.getName(), 123)
.field(fieldType3.getName(), true)
.create();
assertEquals(record, repository.read(record.getId()));
builder.reset();
Record record2 = builder.recordType(recordType2.getName())
.field(fieldType4.getName(), 999)
.field(fieldType5.getName(), true)
.field(fieldType6.getName(), "xyz")
.create();
Record readRecord = repository.read(record2.getId());
assertEquals(999, readRecord.getField(fieldType4.getName()));
try {
readRecord.getField(fieldType1.getName());
fail("FieldType1 not expected. Builder should have been reset");
} catch (FieldNotFoundException expected) {
}
}
@Test
public void testDefaultNamespace() throws Exception {
RecordBuilder builder = repository.recordBuilder();
Record record = builder.defaultNamespace(namespace)
.recordType("RT1")
.field("field1", "abc")
.field("field2", 123)
.field("field3", true)
.create();
Record readRecord = repository.read(record.getId());
assertEquals(record, readRecord);
assertEquals("abc", readRecord.getField("field1"));
readRecord.setDefaultNamespace("anotherNamespace");
try {
readRecord.getField("field1");
} catch (FieldNotFoundException expected) {
}
}
@Test
public void testRecordBuilderCreateOrUpdate() throws Exception {
try {
repository
.recordBuilder()
.defaultNamespace(namespace)
.recordType("RT1")
.field("field1", "abc")
.createOrUpdate();
fail("expected exception");
} catch (RecordException e) {
// expected
}
Record record = repository
.recordBuilder()
.assignNewUuid()
.defaultNamespace(namespace)
.recordType("RT1")
.field("field1", "abc")
.createOrUpdate();
repository
.recordBuilder()
.id(record.getId())
.defaultNamespace(namespace)
.field("field1", "def")
.createOrUpdate();
}
@Test
public void testRecordBuilderNestedRecords() throws Exception {
String NS = "testRecordBuilderNestedRecords";
typeManager
.recordTypeBuilder()
.defaultNamespace(NS)
.name("recordType")
.fieldEntry().defineField().name("field1").create().add()
.fieldEntry().defineField().name("field2").type("RECORD").create().add()
.fieldEntry().defineField().name("field3").type("LIST<RECORD>").create().add()
.create();
Record record = repository
.recordBuilder()
.defaultNamespace(NS)
.recordType("recordType")
.recordField("field2")
.recordType("recordType")
.field("field1", "value 1")
.set()
.recordListField("field3")
.recordType("recordType")
.field("field1", "value 2")
.add()
.field("field1", "value 3")
.add()
.field("field1", "value 4")
.endList()
.create();
record = repository.read(record.getId());
assertEquals("value 1", ((Record) record.getField("field2")).getField("field1"));
assertEquals("value 2", ((List<Record>) record.getField("field3")).get(0).getField("field1"));
assertEquals("value 3", ((List<Record>) record.getField("field3")).get(1).getField("field1"));
assertEquals("value 4", ((List<Record>) record.getField("field3")).get(2).getField("field1"));
// Calling create on a nested record should not work
try {
repository
.recordBuilder()
.defaultNamespace(NS)
.recordType("recordType")
.recordField("field2")
.recordType("recordType")
.field("field1", "value 1")
.create();
fail("expected exception");
} catch (IllegalStateException e) {
// expected
}
}
@Test
public void testRecordValueType() throws Exception {
String namespace = "testRecordValueType";
QName rvtRTName = new QName(namespace, "rvtRT");
QName rtName = new QName(namespace, "rt");
QName ft1Name = new QName(namespace, "ft1");
QName ft2Name = new QName(namespace, "ft2");
QName ft3Name = new QName(namespace, "ft3");
typeManager.recordTypeBuilder().name(rvtRTName).field(fieldType1.getId(), false).create();
ValueType rvt = typeManager.getValueType("RECORD<" + rvtRTName.toString() + ">");
FieldType ft1 = typeManager.createFieldType(typeManager.newFieldType(rvt, ft1Name, Scope.NON_VERSIONED));
FieldType ft2 = typeManager.createFieldType(typeManager.newFieldType(rvt, ft2Name, Scope.VERSIONED));
FieldType ft3 = typeManager.createFieldType(typeManager.newFieldType(rvt, ft3Name, Scope.VERSIONED_MUTABLE));
typeManager.recordTypeBuilder().name(rtName).field(ft1.getId(), false).field(ft2.getId(), false)
.field(ft3.getId(), false).create();
Record ft1Value1 = repository.recordBuilder().field(fieldType1.getName(), "ft1abc").build();
Record ft1Value2 = repository.recordBuilder().field(fieldType1.getName(), "ft1def").build();
Record ft2Value1 = repository.recordBuilder().field(fieldType1.getName(), "ft2abc").build();
Record ft2Value2 = repository.recordBuilder().field(fieldType1.getName(), "ft2def").build();
Record ft3Value1 = repository.recordBuilder().field(fieldType1.getName(), "ft3abc").build();
Record ft3Value2 = repository.recordBuilder().field(fieldType1.getName(), "ft3def").build();
Record ft3Value3 = repository.recordBuilder().field(fieldType1.getName(), "ft3xyz").build();
// Create record
Record createdRecord =
repository.recordBuilder().recordType(rtName).field(ft1Name, ft1Value1).field(ft2Name, ft2Value1)
.field(ft3Name, ft3Value1).create();
Record readRecord = repository.read(createdRecord.getId());
assertEquals("ft1abc", ((Record) readRecord.getField(ft1Name)).getField(fieldType1.getName()));
assertEquals("ft2abc", ((Record) readRecord.getField(ft2Name)).getField(fieldType1.getName()));
assertEquals("ft3abc", ((Record) readRecord.getField(ft3Name)).getField(fieldType1.getName()));
// Update record
repository.recordBuilder().id(createdRecord.getId()).field(ft1Name, ft1Value2).field(ft2Name, ft2Value2)
.field(ft3Name, ft3Value2).update();
readRecord = repository.read(createdRecord.getId());
assertEquals(new Long(2), readRecord.getVersion());
assertEquals("ft1def", ((Record) readRecord.getField(ft1Name)).getField(fieldType1.getName()));
assertEquals("ft2def", ((Record) readRecord.getField(ft2Name)).getField(fieldType1.getName()));
assertEquals("ft3def", ((Record) readRecord.getField(ft3Name)).getField(fieldType1.getName()));
readRecord = repository.read(createdRecord.getId(), 1L);
assertEquals(new Long(1), readRecord.getVersion());
assertEquals("ft1def", ((Record) readRecord.getField(ft1Name)).getField(fieldType1.getName()));
assertEquals("ft2abc", ((Record) readRecord.getField(ft2Name)).getField(fieldType1.getName()));
assertEquals("ft3abc", ((Record) readRecord.getField(ft3Name)).getField(fieldType1.getName()));
// Update mutable field record
repository.recordBuilder().id(createdRecord.getId()).version(1L).field(ft3Name, ft3Value3).updateVersion(true)
.update();
readRecord = repository.read(createdRecord.getId());
assertEquals(new Long(2), readRecord.getVersion());
assertEquals("ft1def", ((Record) readRecord.getField(ft1Name)).getField(fieldType1.getName()));
assertEquals("ft2def", ((Record) readRecord.getField(ft2Name)).getField(fieldType1.getName()));
assertEquals("ft3def", ((Record) readRecord.getField(ft3Name)).getField(fieldType1.getName()));
readRecord = repository.read(createdRecord.getId(), 1L);
assertEquals(new Long(1), readRecord.getVersion());
assertEquals("ft1def", ((Record) readRecord.getField(ft1Name)).getField(fieldType1.getName()));
assertEquals("ft2abc", ((Record) readRecord.getField(ft2Name)).getField(fieldType1.getName()));
assertEquals("ft3xyz", ((Record) readRecord.getField(ft3Name)).getField(fieldType1.getName()));
}
@Test
public void testRecordNestedInItself() throws Exception {
String namespace = "testRecordNestedInItself";
QName rvtRTName = new QName(namespace, "rvtRT");
QName rtName = new QName(namespace, "rt");
QName ft1Name = new QName(namespace, "ft1");
QName ft2Name = new QName(namespace, "ft2");
typeManager.recordTypeBuilder().name(rvtRTName).field(fieldType1.getId(), false).create();
ValueType rvt = typeManager.getValueType("RECORD");
FieldType ft1 = typeManager.createFieldType(typeManager.newFieldType(rvt, ft1Name, Scope.NON_VERSIONED));
FieldType ft2 = typeManager.createFieldType(typeManager.newFieldType(rvt, ft2Name, Scope.VERSIONED));
typeManager.recordTypeBuilder().name(rtName).field(ft1.getId(), false).field(ft2.getId(), false).create();
Record ft1Value1 = repository.recordBuilder().recordType(rvtRTName).field(fieldType1.getName(), "ft1abc")
.build();
// Create nested record
Record record = repository.recordBuilder().recordType(rtName).field(ft1Name, ft1Value1).build();
record.setField(ft2Name, record);
try {
repository.create(record);
fail("Expecting a Record Exception since a record may not be nested in itself");
} catch (RecordException expected) {
}
// Create with deep nesting
Record ft2Value2 = repository.recordBuilder().recordType(rvtRTName).field(ft1Name, record).build();
record.setField(ft2Name, ft2Value2);
try {
repository.create(record);
fail("Expecting a Record Exception since a record may not be nested in itself");
} catch (RecordException expected) {
}
// Update with nested record
record = repository.recordBuilder().recordType(rtName).field(ft1Name, ft1Value1).build();
record = repository.create(record);
record.setField(ft2Name, record); // Nest record in itself
try {
repository.update(record);
fail("Expecting a Record Exception since a record may not be nested in itself");
} catch (RecordException expected) {
}
}
@Test
public void testByteArrayValueType() throws Exception {
FieldType byteArrayValueType = typeManager.createFieldType("BYTEARRAY", new QName("testByteArrayValueType",
"field1"), Scope.NON_VERSIONED);
RecordType recordType = typeManager.recordTypeBuilder().defaultNamespace("testByteArrayValueType")
.name("recordType1")
.field(byteArrayValueType.getId(), false).create();
Record record = repository.recordBuilder().defaultNamespace("testByteArrayValueType").recordType("recordType1")
.field("field1", new ByteArray(Bytes.toBytes("some bytes"))).create();
Record readRecord = repository.read(record.getId());
ByteArray readValue = readRecord.getField(new QName("testByteArrayValueType", "field1"));
assertEquals("some bytes", Bytes.toString(readValue.getBytesUnsafe()));
}
@Test
public void testScannerBasics() throws Exception {
List<String> fieldValues = new ArrayList<String>();
for (int i = 'A'; i <= 'Z'; i++) {
RecordId id = idGenerator.newRecordId("Z" + (char) i);
Record record = repository.newRecord(id);
record.setRecordType(recordType1.getName());
String value = "field 1 - " + (char) i;
fieldValues.add(value);
record.setField(fieldType1.getName(), value);
repository.create(record);
}
RecordScan scan = new RecordScan();
scan.setStartRecordId(idGenerator.newRecordId("ZA"));
scan.setStopRecordId(idGenerator.newRecordId("ZZ")); // stop row is exclusive
RecordScanner scanner = repository.getScanner(scan);
int i = 0;
Record record;
while ((record = scanner.next()) != null) {
assertEquals(record.getField(fieldType1.getName()), fieldValues.get(i));
i++;
}
scanner.close();
assertEquals("Found 25 records", 25, i);
// Same using for-each loop
scanner = repository.getScanner(scan);
i = 0;
for (Record result : scanner) {
assertEquals(result.getField(fieldType1.getName()), fieldValues.get(i));
i++;
}
scanner.close();
assertEquals("Found 25 records", 25, i);
// Scan all records, this should give at least 26 results
scan = new RecordScan();
scanner = repository.getScanner(scan);
i = 0;
while (scanner.next() != null) {
i++;
}
assertTrue("Found at least 26 records", i >= 26);
}
@Test
public void testScannerWithIdRecords() throws Exception {
RecordId id = idGenerator.newRecordId();
Record record = repository.newRecord(id);
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "dummy value");
repository.create(record);
RecordScan scan = new RecordScan();
IdRecordScanner scanner = repository.getScannerWithIds(scan);
final IdRecord next = scanner.next();
assertNotNull(next);
assertFalse(next.getFieldIdToNameMapping().isEmpty());
}
@Test(expected = ClassCastException.class)
public void testScannerWithoutIdRecords() throws Exception {
RecordId id = idGenerator.newRecordId();
Record record = repository.newRecord(id);
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "dummy value");
repository.create(record);
RecordScan scan = new RecordScan();
// normal Record scanner!
RecordScanner scanner = repository.getScanner(scan);
final Record next = scanner.next();
assertNotNull(next);
// this cast will fail
final IdRecord casted = (IdRecord) next;
}
@Test
public void testRecordTypeFilter() throws Exception {
RecordType rt1 = typeManager.recordTypeBuilder()
.name("RecordTypeFilter", "rt1")
.fieldEntry().use(fieldType1).add()
.create();
RecordType rt2 = typeManager.recordTypeBuilder()
.name("RecordTypeFilter", "rt2")
.fieldEntry().use(fieldType1).add()
.create();
// create second version of the record type
rt2 = typeManager.recordTypeBuilder()
.name("RecordTypeFilter", "rt2")
.update();
assertEquals(new Long(2), rt2.getVersion());
repository.recordBuilder().recordType(rt1.getName()).field(fieldType1.getName(), "value").create();
repository.recordBuilder().recordType(rt1.getName()).field(fieldType1.getName(), "value").create();
repository.recordBuilder().recordType(rt2.getName()).field(fieldType1.getName(), "value").create();
repository.recordBuilder().recordType(rt2.getName(), 1L).field(fieldType1.getName(), "value").create();
RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rt1.getName()));
assertEquals(2, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rt2.getName()));
assertEquals(2, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rt2.getName(), 2L));
assertEquals(1, countResults(repository.getScanner(scan)));
}
@Test
public void testRecordTypeFilterInstanceOf() throws Exception {
// The following code creates the following record type hierarchy:
//
// rtA
// / \
// rtB rtC rtE
// |
// rtD
//
RecordType rtA = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOf", "rtA")
.fieldEntry().use(fieldType1).add()
.create();
RecordType rtB = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOf", "rtB")
.fieldEntry().use(fieldType1).add()
.supertype().use(rtA).add()
.create();
RecordType rtC = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOf", "rtC")
.fieldEntry().use(fieldType1).add()
.supertype().use(rtA).add()
.create();
RecordType rtD = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOf", "rtD")
.fieldEntry().use(fieldType1).add()
.supertype().use(rtC).add()
.create();
RecordType rtE = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOf", "rtE")
.fieldEntry().use(fieldType1).add()
.create();
// Create a record of each type
repository.recordBuilder().recordType(rtA.getName()).field(fieldType1.getName(), "value").create();
repository.recordBuilder().recordType(rtB.getName()).field(fieldType1.getName(), "value").create();
repository.recordBuilder().recordType(rtC.getName()).field(fieldType1.getName(), "value").create();
repository.recordBuilder().recordType(rtD.getName()).field(fieldType1.getName(), "value").create();
repository.recordBuilder().recordType(rtE.getName()).field(fieldType1.getName(), "value").create();
// Check that with "instance of" searches we get the expected number of results for each type in the hierarchy
RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtA.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(4, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtB.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(1, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtC.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(2, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtD.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(1, countResults(repository.getScanner(scan)));
}
@Test
public void testRecordTypeFilterInstanceOfRecursionLoop() throws Exception {
// Create a record type hierarchy which contains some endless loop in it:
// - the base hierarchy is C extends from B extends from A
// - A also extends from A and from C
// - B also extends from C
//
// The expected behavior is that it does not go in an endless loop but instead just stops when
// encountering a loop (i.e. it doesn't throw an exception either)
RecordType rtA = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfRecursionLoop", "rtA")
.fieldEntry().use(fieldType1).add()
.create();
RecordType rtB = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfRecursionLoop", "rtB")
.fieldEntry().use(fieldType1).add()
.create();
RecordType rtC = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfRecursionLoop", "rtC")
.fieldEntry().use(fieldType1).add()
.create();
rtA = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfRecursionLoop", "rtA")
.fieldEntry().use(fieldType1).add()
.supertype().id(rtA.getId()).version(2L).add()
.supertype().id(rtC.getId()).version(2L).add()
.update();
rtB = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfRecursionLoop", "rtB")
.fieldEntry().use(fieldType1).add()
.supertype().id(rtA.getId()).version(2L).add()
.supertype().id(rtC.getId()).version(2L).add()
.update();
rtC = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfRecursionLoop", "rtC")
.fieldEntry().use(fieldType1).add()
.supertype().id(rtB.getId()).version(2L).add()
.update();
// Create a record of each type
repository.recordBuilder().recordType(rtA.getName()).field(fieldType1.getName(), "value").create();
repository.recordBuilder().recordType(rtB.getName()).field(fieldType1.getName(), "value").create();
repository.recordBuilder().recordType(rtC.getName()).field(fieldType1.getName(), "value").create();
RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtA.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(3, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtB.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(3, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtB.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(3, countResults(repository.getScanner(scan)));
}
@Test
public void testRecordTypeFilterInstanceOfVersionSpecifics() throws Exception {
// The instance-of check for record types is version unaware, or rather, looks at the latest version
// of each record type. This is explained in the TypeManager.findSubTypes(SchemaId) docs.
// Below we create a record type A with two versions, and type B extends from the first (non-latest)
// version, and type C extends from the second (latest) version of type A.
//
// rtA-version1 rtA-version2 rtD-version1
// | | |
// rtB-version1 rtC-version2 rtC-version1
//
RecordType rtA = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfVersionSpecifics", "rtA")
.fieldEntry().use(fieldType1).add()
.create();
rtA = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfVersionSpecifics", "rtA")
.fieldEntry().use(fieldType1).add()
.fieldEntry().use(fieldType2).add()
.update();
RecordType rtB = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfVersionSpecifics", "rtB")
.fieldEntry().use(fieldType1).add()
.supertype().id(rtA.getId()).version(1L).add()
.create();
RecordType rtD = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfVersionSpecifics", "rtD")
.fieldEntry().use(fieldType1).add()
.create();
RecordType rtC = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfVersionSpecifics", "rtC")
.fieldEntry().use(fieldType1).add()
.supertype().id(rtD.getId()).version(1L).add()
.create();
rtC = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfVersionSpecifics", "rtC")
.fieldEntry().use(fieldType1).add()
.supertype().id(rtA.getId()).version(2L).add()
.update();
repository.recordBuilder().recordType(rtB.getName()).field(fieldType1.getName(), "value").create();
repository.recordBuilder().recordType(rtC.getName()).field(fieldType1.getName(), "value").create();
RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtA.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(2, countResults(repository.getScanner(scan)));
// Since it is not the latest version of C that extends from D, searching for records that are an
// instance of D will not return any results, even though C points to the latest version of D (because
// it is the latest version of C which counts).
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtD.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(0, countResults(repository.getScanner(scan)));
}
@Test
public void testRecordTypeFilterInstanceOfUpdate() throws Exception {
// Verify correctness of scans with a "instance of" record type filter in case the record type
// inheritance is updated.
//
// Initial state:
//
// rtA rtC
// \
// rtB
//
// After update of rtB:
//
// rtA rtC
// /
// rtB
//
RecordType rtA = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfUpdate", "rtA")
.fieldEntry().use(fieldType1).add()
.create();
RecordType rtB = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfUpdate", "rtB")
.fieldEntry().use(fieldType1).add()
.supertype().use(rtA).add()
.create();
RecordType rtC = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfUpdate", "rtC")
.fieldEntry().use(fieldType1).add()
.create();
repository.recordBuilder().recordType(rtB.getName()).field(fieldType1.getName(), "value").create();
RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtA.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(1, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtC.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(0, countResults(repository.getScanner(scan)));
rtB = typeManager.recordTypeBuilder()
.name("RecordTypeFilterInstanceOfUpdate", "rtB")
.fieldEntry().use(fieldType1).add()
.supertype().use(rtC).add()
.update();
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtC.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(1, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rtA.getName(), RecordTypeFilter.Operator.INSTANCE_OF));
assertEquals(0, countResults(repository.getScanner(scan)));
}
@Test
public void testFieldValueFilter() throws Exception {
FieldType fieldType =
typeManager.createFieldType("STRING", new QName("FieldValueFilter", "field"), Scope.NON_VERSIONED);
RecordType rt = typeManager.recordTypeBuilder()
.defaultNamespace("FieldValueFilter")
.name("rt1")
.fieldEntry()
.use(fieldType)
.add()
.create();
Record record =
repository.recordBuilder().recordType(rt.getName()).field(fieldType.getName(), "value1").create();
repository.recordBuilder().recordType(rt.getName()).field(fieldType.getName(), "value1").create();
repository.recordBuilder().recordType(rt.getName()).field(fieldType.getName(), "value2").create();
repository.recordBuilder().recordType(rt.getName()).field(fieldType.getName(), "value2").create();
repository.recordBuilder().recordType(rt.getName()).field(fieldType.getName(), "value2").create();
RecordScan scan = new RecordScan();
scan.setRecordFilter(new FieldValueFilter(fieldType.getName(), "value1"));
assertEquals(2, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new FieldValueFilter(fieldType.getName(), "value2"));
assertEquals(3, countResults(repository.getScanner(scan)));
scan = new RecordScan();
scan.setRecordFilter(new FieldValueFilter(fieldType.getName(), CompareOp.NOT_EQUAL, "value1"));
assertEquals(3, countResults(repository.getScanner(scan)));
// (At the time of this writing, ...) when non-versioned fields are deleted, a delete marker is
// written rather than really deleting the field. This delete marker would then also be 'not equal'
// to the value we search, and we'd get an extra result. This test verifies the implementation takes
// care of that.
record.getFieldsToDelete().add(fieldType.getName());
record.setField(fieldType1.getName(), "whatever");
record = repository.update(record);
scan = new RecordScan();
scan.setRecordFilter(new FieldValueFilter(fieldType.getName(), CompareOp.NOT_EQUAL, "value1"));
assertEquals(3, countResults(repository.getScanner(scan)));
}
@Test
public void testFilterList() throws Exception {
FieldType f1 = typeManager.createFieldType("STRING", new QName("FilterList", "field1"), Scope.NON_VERSIONED);
FieldType f2 = typeManager.createFieldType("STRING", new QName("FilterList", "field2"), Scope.NON_VERSIONED);
RecordType rt = typeManager.recordTypeBuilder().defaultNamespace("FilterList").name("rt")
.fieldEntry().use(f1).add().fieldEntry().use(f2).add().create();
repository.recordBuilder()
.recordType(rt.getName())
.field(f1.getName(), "A")
.field(f2.getName(), "B")
.create();
repository.recordBuilder()
.recordType(rt.getName())
.field(f1.getName(), "A")
.field(f2.getName(), "C")
.create();
repository.recordBuilder()
.recordType(rt.getName())
.field(f1.getName(), "D")
.field(f2.getName(), "B")
.create();
repository.recordBuilder()
.recordType(rt.getName())
.field(f1.getName(), "F")
.create();
// Test f1=A and f2=B
RecordScan scan = new RecordScan();
RecordFilterList filterList = new RecordFilterList();
filterList.addFilter(new FieldValueFilter(f1.getName(), "A"));
filterList.addFilter(new FieldValueFilter(f2.getName(), "B"));
scan.setRecordFilter(filterList);
assertEquals(1, countResults(repository.getScanner(scan)));
// Test f1=A or f2=B
scan = new RecordScan();
filterList = new RecordFilterList(RecordFilterList.Operator.MUST_PASS_ONE);
filterList.addFilter(new FieldValueFilter(f1.getName(), "A"));
filterList.addFilter(new FieldValueFilter(f2.getName(), "B"));
scan.setRecordFilter(filterList);
assertEquals(3, countResults(repository.getScanner(scan)));
// Test f1=A and (f2=B or f2=C)
scan = new RecordScan();
RecordFilterList filterList2 = new RecordFilterList(RecordFilterList.Operator.MUST_PASS_ONE);
filterList2.addFilter(new FieldValueFilter(f2.getName(), "B"));
filterList2.addFilter(new FieldValueFilter(f2.getName(), "C"));
filterList = new RecordFilterList();
filterList.addFilter(new FieldValueFilter(f1.getName(), "A"));
filterList.addFilter(filterList2);
scan.setRecordFilter(filterList);
assertEquals(2, countResults(repository.getScanner(scan)));
// Test f1=F and f2=Z
scan = new RecordScan();
filterList = new RecordFilterList();
filterList.addFilter(new FieldValueFilter(f1.getName(), "F"));
filterList.addFilter(new FieldValueFilter(f2.getName(), "Z"));
scan.setRecordFilter(filterList);
assertEquals(0, countResults(repository.getScanner(scan)));
// Test f1=F and (f2=Z with filterIfMissing=false)
scan = new RecordScan();
filterList = new RecordFilterList();
filterList.addFilter(new FieldValueFilter(f1.getName(), "F"));
FieldValueFilter fvf = new FieldValueFilter(f2.getName(), "Z");
fvf.setFilterIfMissing(false);
filterList.addFilter(fvf);
scan.setRecordFilter(filterList);
assertEquals(1, countResults(repository.getScanner(scan)));
}
@Test
public void testScanWithReturnFields() throws Exception {
FieldType f1 = typeManager.createFieldType("STRING", new QName("ReturnFieldsScan", "f1"), Scope.NON_VERSIONED);
FieldType f2 = typeManager.createFieldType("STRING", new QName("ReturnFieldsScan", "f2"), Scope.NON_VERSIONED);
FieldType f3 = typeManager.createFieldType("STRING", new QName("ReturnFieldsScan", "f3"), Scope.NON_VERSIONED);
FieldType f4 = typeManager.createFieldType("STRING", new QName("ReturnFieldsScan", "f4"), Scope.NON_VERSIONED);
RecordType rt = typeManager.recordTypeBuilder().defaultNamespace("ReturnFieldsScan").name("rt")
.fieldEntry().use(f1).add()
.fieldEntry().use(f2).add()
.fieldEntry().use(f3).add()
.fieldEntry().use(f4).add()
.create();
repository.recordBuilder()
.recordType(rt.getName())
.field(f1.getName(), "A")
.field(f2.getName(), "B")
.field(f3.getName(), "C")
.create();
// Test ALL filter
RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rt.getName()));
scan.setReturnFields(new ReturnFields(ReturnFields.Type.ALL));
Record record = repository.getScanner(scan).next();
assertEquals(3, record.getFields().size());
// Test NONE filter
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rt.getName()));
scan.setReturnFields(new ReturnFields(ReturnFields.Type.NONE));
record = repository.getScanner(scan).next();
assertEquals(0, record.getFields().size());
// Test ENUM filter
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rt.getName()));
scan.setReturnFields(new ReturnFields(f1.getName(), f2.getName()));
record = repository.getScanner(scan).next();
assertEquals(2, record.getFields().size());
assertTrue(record.hasField(f1.getName()));
assertTrue(record.hasField(f2.getName()));
assertFalse(record.hasField(f3.getName()));
// Test scanning on filtered field, should not work
scan = new RecordScan();
RecordFilterList filterList = new RecordFilterList();
filterList.addFilter(new RecordTypeFilter(rt.getName()));
filterList.addFilter(new FieldValueFilter(f1.getName(), "A"));
scan.setRecordFilter(filterList);
// without ReturnFields, we get a result
assertNotNull(repository.getScanner(scan).next());
// with ReturnFields that doesn't include f1, we don't get a result
scan.setReturnFields(new ReturnFields(f2.getName()));
// TODO disabled this test as it was sometimes failing, and sometimes not
// In the cases where it failed, it did bring up the row which is
// correct according to the filters, but which we didn't expect to
// receive because a filter was applied on a non-read column.
// The conclusion could be that while HBase can't guarantee the filter
// will work always, it sometimes will work? Needs more investigation.
//assertNull(repository.getScanner(scan).next());
}
/**
* Tests if record type is set when different settings of returnFields is used.
*/
@Test
public void testScanWithReturnFieldsRecordType() throws Exception {
String ns = "ReturnFieldsScan-RecordType";
FieldType f1 = typeManager.createFieldType("STRING", new QName(ns, "f1"), Scope.NON_VERSIONED);
FieldType f2 = typeManager.createFieldType("STRING", new QName(ns, "f2"), Scope.NON_VERSIONED);
FieldType f3 = typeManager.createFieldType("STRING", new QName(ns, "f3"), Scope.NON_VERSIONED);
FieldType f4 = typeManager.createFieldType("STRING", new QName(ns, "f4"), Scope.NON_VERSIONED);
RecordType rt = typeManager.recordTypeBuilder().defaultNamespace(ns).name("rt")
.fieldEntry().use(f1).add()
.fieldEntry().use(f2).add()
.fieldEntry().use(f3).add()
.fieldEntry().use(f4).add()
.create();
repository.recordBuilder()
.recordType(rt.getName())
.field(f1.getName(), "A")
.field(f2.getName(), "B")
.field(f3.getName(), "C")
.create();
// Test ALL filter
RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rt.getName()));
scan.setReturnFields(new ReturnFields(ReturnFields.Type.ALL));
Record record = repository.getScanner(scan).next();
assertNotNull(record.getRecordTypeName());
assertEquals(ns, record.getRecordTypeName().getNamespace());
// Test NONE filter
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rt.getName()));
scan.setReturnFields(new ReturnFields(ReturnFields.Type.NONE));
record = repository.getScanner(scan).next();
assertNotNull(record.getRecordTypeName());
assertEquals(ns, record.getRecordTypeName().getNamespace());
// Test ENUM filter
scan = new RecordScan();
scan.setRecordFilter(new RecordTypeFilter(rt.getName()));
scan.setReturnFields(new ReturnFields(f1.getName(), f2.getName()));
record = repository.getScanner(scan).next();
assertNotNull(record.getRecordTypeName());
assertEquals(ns, record.getRecordTypeName().getNamespace());
}
@Test
public void testPrefixScans() throws Exception {
repository.recordBuilder()
.id("PrefixScanTest")
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id("PrefixScanTest-suffix1")
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id("PrefixScanTest-suffix2")
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id("QPrefixScanTest")
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
RecordScan scan = new RecordScan();
scan.setStartRecordId(idGenerator.newRecordId("PrefixScanTest"));
RecordScanner scanner = repository.getScanner(scan);
assertEquals(idGenerator.newRecordId("PrefixScanTest"), scanner.next().getId());
assertEquals(idGenerator.newRecordId("PrefixScanTest-suffix1"), scanner.next().getId());
assertEquals(idGenerator.newRecordId("PrefixScanTest-suffix2"), scanner.next().getId());
// the scanner would run till the end of the table
assertNotNull(scanner.next());
scanner.close();
scan.setRecordFilter(new RecordIdPrefixFilter(idGenerator.newRecordId("PrefixScanTest")));
scanner = repository.getScanner(scan);
assertEquals(idGenerator.newRecordId("PrefixScanTest"), scanner.next().getId());
assertEquals(idGenerator.newRecordId("PrefixScanTest-suffix1"), scanner.next().getId());
assertEquals(idGenerator.newRecordId("PrefixScanTest-suffix2"), scanner.next().getId());
// due to the prefix filter, the scanner stops once there are no records left with the same prefix
assertNull(scanner.next());
scanner.close();
//
// When using UUID record ID's, prefix scans make less sense, except for retrieving
// variants
//
RecordId uuid = idGenerator.newRecordId();
RecordId varid1 = idGenerator.newRecordId(uuid, ImmutableMap.of("lang", "en", "year", "1999"));
RecordId varid2 = idGenerator.newRecordId(uuid, ImmutableMap.of("lang", "fr"));
repository.recordBuilder()
.id(uuid)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id(varid1)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id(varid2)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
scan = new RecordScan();
scan.setStartRecordId(uuid);
scan.setRecordFilter(new RecordIdPrefixFilter(uuid));
scanner = repository.getScanner(scan);
assertEquals(uuid, scanner.next().getId());
assertEquals(varid1, scanner.next().getId());
assertEquals(varid2, scanner.next().getId());
assertNull(scanner.next());
scanner.close();
}
/**
* At the time of this writing, record deletion was implemented by marking records
* as deleted. We need to make sure these are skipped when scanning, and that
* this doesn't conflict with custom filters.
*/
@Test
public void testScannerAndDeletedRecords() throws Exception {
for (int i = 0; i < 5; i++) {
RecordId id = idGenerator.newRecordId("ScanDeleteTest-" + i);
Record record = repository.newRecord(id);
record.setRecordType(recordType1.getName());
String value = "dummy";
record.setField(fieldType1.getName(), value);
repository.create(record);
}
RecordScan scan = new RecordScan();
scan.setStartRecordId(idGenerator.newRecordId("ScanDeleteTest-"));
scan.setRecordFilter(new RecordIdPrefixFilter(idGenerator.newRecordId("ScanDeleteTest-")));
RecordScanner scanner = repository.getScanner(scan);
assertEquals(5, countResults(scanner));
scanner.close();
// This is to make sure the filtering of deleted records also works when we don't
// specify a filter on our scan.
RecordScan singleScan = new RecordScan();
singleScan.setStartRecordId(idGenerator.newRecordId("ScanDeleteTest-0"));
singleScan.setStopRecordId(idGenerator.newRecordId("ScanDeleteTest-0"));
scanner = repository.getScanner(singleScan);
assertEquals(1, countResults(scanner));
scanner.close();
// Delete the records, verify the new scanner results
repository.delete(idGenerator.newRecordId("ScanDeleteTest-0"));
scanner = repository.getScanner(scan);
assertEquals(4, countResults(scanner));
scanner.close();
scanner = repository.getScanner(singleScan);
assertEquals(0, countResults(scanner));
scanner.close();
}
private int countResults(RecordScanner scanner) throws RepositoryException, InterruptedException {
int i = 0;
while (scanner.next() != null) {
i++;
}
return i;
}
@Test
public void testVariantScansWithKeysUSER() throws Exception {
doTestVariantScansWithKeys(idGenerator.newRecordId("VariantScanWithKeysTest"));
}
@Test
public void testVariantScansWithKeysUUID() throws Exception {
doTestVariantScansWithKeys(idGenerator.newRecordId());
}
private void doTestVariantScansWithKeys(RecordId master) throws Exception {
final RecordId variant = idGenerator.newRecordId(master, ImmutableMap.of("key1", "value1", "key2", "value2"));
final RecordId variantWithOtherValues =
idGenerator.newRecordId(master, ImmutableMap.of("key1", "other-value-1", "key2", "other-value-2"));
final RecordId extendedVariant =
idGenerator.newRecordId(master, ImmutableMap.of("key1", "value1", "key2", "value2", "key3", "value3"));
repository.recordBuilder()
.id(master)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id(variant)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id(extendedVariant)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id(variantWithOtherValues)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
{
// master scan
final RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordVariantFilter(master, new HashMap<String, String>()));
RecordScanner scanner = repository.getScanner(scan);
assertEquals(master, scanner.next().getId());
assertNull(scanner.next());
scanner.close();
}
{
// variant scan
final HashMap<String, String> variantProperties = new HashMap<String, String>();
variantProperties.put("foo", null);
variantProperties.put("bar", null);
final RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordVariantFilter(variant.getMaster(), variantProperties));
RecordScanner scanner = repository.getScanner(scan);
assertNull(scanner.next()); // it doesn't match anything
scanner.close();
}
{
// variant scan for something completely different
final HashMap<String, String> variantProperties = new HashMap<String, String>();
variantProperties.put("key1", null);
variantProperties.put("key2", null);
final RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordVariantFilter(variant.getMaster(), variantProperties));
RecordScanner scanner = repository.getScanner(scan);
assertEquals(variant, scanner.next().getId());
assertEquals(variantWithOtherValues, scanner.next().getId());
assertNull(scanner.next()); // it doesn't match the extendedVariant!
scanner.close();
}
{
// extended variant scan
final HashMap<String, String> variantProperties = new HashMap<String, String>();
variantProperties.put("key1", null);
variantProperties.put("key2", null);
variantProperties.put("key3", null);
final RecordScan scan = new RecordScan();
scan.setRecordFilter(
new RecordVariantFilter(extendedVariant.getMaster(), variantProperties));
RecordScanner scanner = repository.getScanner(scan);
assertEquals(extendedVariant, scanner.next().getId());
assertNull(scanner.next());
scanner.close();
}
}
@Test
public void testVariantScansWithKeysAndValues() throws Exception {
final RecordId master = idGenerator.newRecordId("VariantScanWithKeysAndValuesTest");
final RecordId variant = idGenerator.newRecordId(master, ImmutableMap.of("key1", "value1", "key2", "value2"));
final RecordId variantWithOtherValues =
idGenerator.newRecordId(master, ImmutableMap.of("key1", "other-value-1", "key2", "value2"));
final RecordId extendedVariant =
idGenerator.newRecordId(master, ImmutableMap.of("key1", "value1", "key2", "value2", "key3", "value3"));
repository.recordBuilder()
.id(master)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id(variant)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id(extendedVariant)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
repository.recordBuilder()
.id(variantWithOtherValues)
.recordType(recordType1.getName())
.field(fieldType1.getName(), "foo")
.create();
{
// variant scan with all values
final HashMap<String, String> variantProperties = new HashMap<String, String>();
variantProperties.put("key1", "value1");
variantProperties.put("key2", "value2");
final RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordVariantFilter(variant.getMaster(), variantProperties));
RecordScanner scanner = repository.getScanner(scan);
assertEquals(variant, scanner.next().getId());
assertNull(scanner.next()); // it doesn't match the other variants
scanner.close();
}
{
// variant scan with specific value for key2 only
final HashMap<String, String> variantProperties = new HashMap<String, String>();
variantProperties.put("key1", null);
variantProperties.put("key2", "value2");
final RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordVariantFilter(variant.getMaster(), variantProperties));
RecordScanner scanner = repository.getScanner(scan);
assertEquals(variant, scanner.next().getId());
assertEquals(variantWithOtherValues, scanner.next().getId()); // has same value for key2
assertNull(scanner.next()); // it doesn't match the other variants
scanner.close();
}
{
// variant scan with specific value for key1 only
final HashMap<String, String> variantProperties = new HashMap<String, String>();
variantProperties.put("key1", "value1");
variantProperties.put("key2", null);
final RecordScan scan = new RecordScan();
scan.setRecordFilter(new RecordVariantFilter(variant.getMaster(), variantProperties));
RecordScanner scanner = repository.getScanner(scan);
assertEquals(variant, scanner.next().getId());
assertNull(scanner.next()); // it doesn't match the other variants
scanner.close();
}
}
@Test
public void testMetadataSimpleStoreLoad() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "field value");
Metadata metadata = new MetadataBuilder().value("field1", "value1").value("field2", "value2").build();
record.setMetadata(fieldType1.getName(), metadata);
assertEquals(2, record.getMetadataMap().get(fieldType1.getName()).getMap().size());
record = repository.create(record);
// Check state of returned record object
Metadata returnedMetadata = record.getMetadata(fieldType1.getName());
assertNotNull(returnedMetadata);
assertEquals(2, returnedMetadata.getMap().size());
assertEquals("value1", returnedMetadata.get("field1"));
assertEquals("value2", returnedMetadata.get("field2"));
// Check state when freshly reading record
record = repository.read(record.getId());
assertEquals(1, record.getMetadataMap().size());
Metadata readMetadata = record.getMetadataMap().get(fieldType1.getName());
assertNotNull(readMetadata);
assertEquals(2, readMetadata.getMap().size());
assertEquals("value1", readMetadata.get("field1"));
assertEquals("value2", readMetadata.get("field2"));
}
@Test
public void testMetadataAllTypes() throws Exception {
// This test verifies that the change detection logic works correctly for all data types
List<Pair> testValues = new ArrayList<Pair>();
testValues.add(Pair.create("string1", "string2"));
testValues.add(Pair.create(new Integer(1), new Integer(2)));
testValues.add(Pair.create(new Long(1), new Long(2)));
testValues.add(Pair.create(new Float(1f), new Float(2f)));
testValues.add(Pair.create(new Double(1d), new Double(2d)));
testValues.add(Pair.create(Boolean.TRUE, Boolean.FALSE));
testValues.add(Pair.create(new ByteArray("A".getBytes()), new ByteArray("B".getBytes())));
for (Pair testValue : testValues) {
String ctx = "testing " + testValue.getV1() + " of type " + testValue.getV1().getClass().getName();
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType2.getName(), new Integer(1));
record.setMetadata(fieldType2.getName(), new MetadataBuilder()
.object("somefield", testValue.getV1())
.build());
record = repository.create(record);
// test the type of the metadata has been retained
assertEquals(ctx, testValue.getV1().getClass(),
record.getMetadata(fieldType2.getName()).getMap().get("somefield").getClass());
// do dummy update, should not create new version
// (apparently, this also works fine for the float & double tests)
record.setMetadata(fieldType2.getName(), new MetadataBuilder()
.object("somefield", testValue.getV1())
.build());
record = repository.update(record);
assertEquals(ctx, 1L, record.getVersion().longValue());
// do update, should create new version
record.setMetadata(fieldType2.getName(), new MetadataBuilder()
.object("somefield", testValue.getV2())
.build());
record = repository.update(record);
assertEquals(ctx, 2L, record.getVersion().longValue());
// test the type of the metadata has been retained
record = repository.read(record.getId());
assertEquals(ctx, testValue.getV1().getClass(),
record.getMetadata(fieldType2.getName()).getMap().get("somefield").getClass());
}
}
@Test
public void testMetadataPartialUpdate() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "field value");
Metadata metadata = new MetadataBuilder()
.value("field1", "value1")
.value("field2", "value2")
.value("field3", "value3").build();
record.setMetadata(fieldType1.getName(), metadata);
record = repository.create(record);
RecordId recordId = record.getId();
// Now update metadata:
// - field1 is left unchanged and not specified
// - field2 is updated
// - field3 is deleted
// - a new field4 is added
metadata = new MetadataBuilder()
.value("field2", "value2a")
.delete("field3")
.value("field4", "value4").build();
record = repository.newRecord(recordId);
record.setMetadata(fieldType1.getName(), metadata);
record = repository.update(record);
// Check state of returned record object
Metadata returnedMetadata = record.getMetadata(fieldType1.getName());
assertNotNull(returnedMetadata);
assertEquals(2, returnedMetadata.getMap().size());
assertEquals("value2a", returnedMetadata.get("field2"));
assertEquals("value4", returnedMetadata.get("field4"));
assertEquals(0, returnedMetadata.getFieldsToDelete().size());
// Check state when freshly reading record
record = repository.read(record.getId());
Metadata readMetadata = record.getMetadataMap().get(fieldType1.getName());
assertNotNull(readMetadata);
assertEquals(3, readMetadata.getMap().size());
assertEquals("value1", readMetadata.get("field1"));
assertEquals("value2a", readMetadata.get("field2"));
assertEquals("value4", readMetadata.get("field4"));
}
@Test
public void testMetadataOnUndefinedField() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "field value");
// Set metadata on a field which does not have a value, such metadata should be ignored
record.setMetadata(fieldType2.getName(), new MetadataBuilder().value("field1", "value1").build());
record = repository.create(record);
// Check state of returned record object
assertNull(record.getMetadata(fieldType1.getName()));
assertNull(record.getMetadata(fieldType2.getName()));
assertEquals(0, record.getMetadataMap().size());
// Check state of freshly read record
record = repository.read(record.getId());
assertNull(record.getMetadata(fieldType1.getName()));
assertNull(record.getMetadata(fieldType2.getName()));
assertEquals(0, record.getMetadataMap().size());
}
@Test
public void testMetadataVersionedField() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType2.getName(), new Integer(5));
record.setMetadata(fieldType2.getName(), new MetadataBuilder().value("field1", "value1").build());
record = repository.create(record);
record = repository.read(record.getId());
Metadata readMetadata = record.getMetadataMap().get(fieldType2.getName());
assertNotNull(readMetadata);
assertEquals(1, readMetadata.getMap().size());
assertEquals("value1", readMetadata.get("field1"));
}
@Test
public void testMetadataUpdateFieldAndMetadata() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType2.getName(), new Integer(5));
record.setMetadata(fieldType2.getName(), new MetadataBuilder()
.value("field1", "value1")
.value("field2", "value2").build());
record = repository.create(record);
// do update to field and metadata
record.setField(fieldType2.getName(), new Integer(6));
record.setMetadata(fieldType2.getName(), new MetadataBuilder()
.value("field1", "value1a")
.value("field3", "value3").build()); // note that we leave field2 unchanged, this tests the merging
// of old and new metadata
record = repository.update(record);
// validate state of returned record object
assertEquals(2L, record.getVersion().longValue());
assertEquals(new Integer(6), record.getField(fieldType2.getName()));
Metadata metadata = record.getMetadataMap().get(fieldType2.getName());
assertNotNull(metadata);
assertEquals(2, metadata.getMap().size());
assertEquals("value1a", metadata.get("field1"));
assertEquals("value3", metadata.get("field3"));
// validate state of read record
record = repository.read(record.getId());
assertEquals(2L, record.getVersion().longValue());
assertEquals(new Integer(6), record.getField(fieldType2.getName()));
metadata = record.getMetadataMap().get(fieldType2.getName());
assertNotNull(metadata);
assertEquals(3, metadata.getMap().size());
assertEquals("value1a", metadata.get("field1"));
assertEquals("value2", metadata.get("field2"));
assertEquals("value3", metadata.get("field3"));
}
@Test
public void testMetadataUpdateFieldOnly() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType2.getName(), new Integer(5));
record.setMetadata(fieldType2.getName(), new MetadataBuilder().value("field1", "value1").build());
record = repository.create(record);
RecordId recordId = record.getId();
// update only field value, metadata should not be lost be inherited from previous record state
record = repository.newRecord();
record.setId(recordId);
record.setField(fieldType2.getName(), new Integer(7));
record = repository.update(record);
// validate state of read record
record = repository.read(record.getId());
assertEquals(2L, record.getVersion().longValue());
assertEquals(new Integer(7), record.getField(fieldType2.getName()));
Metadata readMetadata = record.getMetadataMap().get(fieldType2.getName());
assertNotNull(readMetadata);
assertEquals(1, readMetadata.getMap().size());
assertEquals("value1", readMetadata.get("field1"));
}
@Test
public void testMetadataUpdateMetadataOnly() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType2.getName(), new Integer(5));
record.setMetadata(fieldType2.getName(), new MetadataBuilder()
.value("field1", "value1")
.value("field2", "value2").build());
record = repository.create(record);
RecordId recordId = record.getId();
// Update only metadata -- should create new version
record = repository.newRecord();
record.setId(recordId);
record.setMetadata(fieldType2.getName(), new MetadataBuilder().value("field1", "value1a").build());
record = repository.update(record);
Metadata metadata;
// validate state of returned record
assertEquals(2L, record.getVersion().longValue());
metadata = record.getMetadataMap().get(fieldType2.getName());
assertNotNull(metadata);
assertEquals(1, metadata.getMap().size());
assertEquals("value1a", metadata.get("field1"));
// validate state of read record
record = repository.read(record.getId());
assertEquals(2L, record.getVersion().longValue());
metadata = record.getMetadataMap().get(fieldType2.getName());
assertNotNull(metadata);
assertEquals(2, metadata.getMap().size());
assertEquals("value1a", metadata.get("field1"));
assertEquals("value2", metadata.get("field2"));
}
@Test
public void testMetadataNoUpdate() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType2.getName(), new Integer(1));
record.setMetadata(fieldType2.getName(), new MetadataBuilder().value("field1", "value1").build());
record = repository.create(record);
assertEquals(1, record.getVersion().longValue());
assertEquals(1, record.getMetadata(fieldType2.getName()).getMap().size());
// resubmit the same record object, this should not cause an update
record = repository.update(record);
assertEquals(1, record.getVersion().longValue());
// Delete a non-existing field from the metadata, this should also not cause an update
record.setMetadata(fieldType2.getName(), new MetadataBuilder().delete("field2").build());
record = repository.update(record);
assertEquals(1, record.getVersion().longValue());
// Once more with an empty metadata object
record.setMetadata(fieldType2.getName(), new MetadataBuilder().build());
record = repository.update(record);
assertEquals(1, record.getVersion().longValue());
// But if we do a real update, we should get a new version
record.setMetadata(fieldType2.getName(), new MetadataBuilder().value("field1", "value1a").build());
record = repository.update(record);
assertEquals(2, record.getVersion().longValue());
}
@Test
public void testMetadataOnDeletedField() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "field value");
record.setField(fieldType2.getName(), new Integer(1));
record.setMetadata(fieldType1.getName(), new MetadataBuilder().value("field1", "value1").build());
record = repository.create(record);
RecordId recordId = record.getId();
record = repository.newRecord(recordId);
record.delete(fieldType1.getName(), true);
record.setMetadata(fieldType1.getName(), new MetadataBuilder().value("field1", "value1a").build());
record = repository.update(record);
// validate state of returned record
assertFalse(record.hasField(fieldType1.getName()));
assertNull(record.getMetadata(fieldType1.getName()));
// validate state of read record
record = repository.read(recordId);
assertFalse(record.hasField(fieldType1.getName()));
assertNull(record.getMetadata(fieldType1.getName()));
assertNotNull(record.getField(fieldType2.getName()));
}
@Test
public void testMetadataSuperfluousUpdates() throws Exception {
// This test verifies that we don't write metadata if there are zero fields in it
// Record with empty Metadata object
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "field value");
record.setField(fieldType2.getName(), new Integer(1));
record.setMetadata(fieldType1.getName(), new MetadataBuilder().build());
record = repository.create(record);
// validate state of read record
record = repository.read(record.getId());
assertNull(record.getMetadata(fieldType1.getName()));
// Record with Metadata object containing only delets
record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "field value");
record.setField(fieldType2.getName(), new Integer(1));
record.setMetadata(fieldType1.getName(), new MetadataBuilder().delete("field1").build());
record = repository.create(record);
// validate state of read record
record = repository.read(record.getId());
assertNull(record.getMetadata(fieldType1.getName()));
}
@Test
public void testMetadataNotSupportedOnVersionedMutableFields() throws Exception {
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "field value");
record.setField(fieldType3.getName(), Boolean.TRUE);
record.setMetadata(fieldType3.getName(), new MetadataBuilder().value("field1", "value1").build());
try {
record = repository.create(record);
fail("Expected an exception trying to set metadata on a versioned-mutable field");
} catch (Exception e) {
assertTrue(e.getMessage().contains("Field metadata is currently not supported for versioned-mutable fields."));
}
}
@Test
public void testMetadataNotSupportedOnBlobFields() throws Exception {
String[] types = new String[]{"BLOB", "LIST<BLOB>", "LIST<PATH<BLOB>>"};
for (int i = 0; i < types.length; i++) {
FieldType blobField = typeManager
.createFieldType("BLOB", new QName("metadata-blob", "blob" + i), Scope.NON_VERSIONED);
Blob blob = new Blob("text/plain", 5L, "foo");
OutputStream os = repository.getOutputStream(blob);
os.write("12345".getBytes());
os.close();
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(blobField.getName(), blob);
record.setMetadata(blobField.getName(), new MetadataBuilder().value("field1", "value1").build());
try {
record = repository.create(record);
fail("Expected an exception trying to set metadata on a blob field");
} catch (Exception e) {
assertTrue(e.getMessage().contains("Field metadata is currently not supported for BLOB fields."));
}
}
}
@Test
public void testFieldValueFilterWhenFieldHasMetadata() throws Exception {
// The purpose of this test is to verify that the FieldValueFilter also works when there
// is metadata for the field (metadata is stored in the same cell as the value, and should be ignored
// by the FieldValueFilter)
Record record = repository.newRecord();
record.setRecordType(recordType1.getName());
record.setField(fieldType1.getName(), "stop drinking coke");
record.setMetadata(fieldType1.getName(), new MetadataBuilder().value("field1", "foobar").build());
repository.create(record);
RecordScan scan = new RecordScan();
scan.setRecordFilter(new FieldValueFilter(fieldType1.getName(), "stop drinking coke"));
assertEquals(1, countResults(repository.getScanner(scan)));
}
@Test
public void testMetadataViaRecordBuilder() throws Exception {
Record record = repository.recordBuilder()
.recordType(recordType1.getName())
.field(fieldType1.getName(), "hi")
.metadata(fieldType1.getName(), new MetadataBuilder().value("x", "y").build())
.create();
record = repository.read(record.getId());
assertEquals("y", record.getMetadata(fieldType1.getName()).get("x"));
}
@Test
public void testMetadataFieldsToDeleteNotStored() throws Exception {
Record record = repository.recordBuilder()
.recordType(recordType1.getName())
.field(fieldType1.getName(), "hi")
.metadata(fieldType1.getName(), new MetadataBuilder().value("x", "y").delete("z").build())
.create();
record = repository.read(record.getId());
assertEquals(0, record.getMetadata(fieldType1.getName()).getFieldsToDelete().size());
}
}