/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.d2r;
import static org.junit.Assert.*;
import com.torodb.common.util.HexUtils;
import com.torodb.core.TableRef;
import com.torodb.core.TableRefFactory;
import com.torodb.core.d2r.*;
import com.torodb.core.impl.TableRefFactoryImpl;
import com.torodb.core.transaction.metainf.*;
import com.torodb.core.transaction.metainf.MetainfoRepository.SnapshotStage;
import com.torodb.kvdocument.conversion.json.JacksonJsonParser;
import com.torodb.kvdocument.conversion.json.JsonParser;
import com.torodb.kvdocument.types.MongoObjectIdType;
import com.torodb.kvdocument.types.NullType;
import com.torodb.kvdocument.values.KvDocument;
import com.torodb.kvdocument.values.KvMongoObjectId;
import com.torodb.kvdocument.values.KvValue;
import com.torodb.metainfo.cache.mvcc.MvccMetainfoRepository;
import org.junit.Before;
import org.junit.Test;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
public class Document2RelStackTest {
private final TableRefFactory tableRefFactory = new TableRefFactoryImpl();
//TODO: Change to final implementation code
private static final boolean IS_ARRAY = true;
private static final boolean IS_SUBDOCUMENT = false;
private static final Integer NO_SEQ = null;
private static final String ROOT_DOC_NAME = "";
private JsonParser parser = new JacksonJsonParser();
private static final String DB1 = "test1";
private static final String COLLA = "collA";
private static final String COLLB = "collB";
private static final String DB2 = "test2";
private static final String COLLC = "collC";
private static final String COLLD = "collD";
private static ImmutableMetaSnapshot currentView = new ImmutableMetaSnapshot.Builder()
.put(new ImmutableMetaDatabase.Builder(DB1, DB1)
.put(new ImmutableMetaCollection.Builder(COLLA, COLLA).build())
.put(new ImmutableMetaCollection.Builder(COLLB, COLLB).build()).build())
.put(new ImmutableMetaDatabase.Builder(DB2, DB2)
.put(new ImmutableMetaCollection.Builder(COLLC, COLLC).build())
.put(new ImmutableMetaCollection.Builder(COLLD, COLLD).build()).build())
.build();
private MutableMetaSnapshot mutableSnapshot;
@Before
public void setup() {
MvccMetainfoRepository mvccMetainfoRepository = new MvccMetainfoRepository(currentView);
try (SnapshotStage snapshot = mvccMetainfoRepository.startSnapshotStage()) {
mutableSnapshot = snapshot.createMutableSnapshot();
}
}
@Test
public void emptyDocumentMapsToTable() {
CollectionData collectionData = parseDocument("EmptyDocument.json");
assertNotNull(findRootDocPart(collectionData));
}
@Test
public void emptyDocumentCreatesARow() {
CollectionData collectionData = parseDocument("EmptyDocument.json");
DocPartData rootDocPart = findRootDocPart(collectionData);
assertTrue(rootDocPart.iterator().hasNext());
DocPartRow firstRow = rootDocPart.iterator().next();
assertNotNull(firstRow);
assertFalse(firstRow.getFieldValues().iterator().hasNext());
}
@Test
public void rootDocMapsToATableWithEmptyName() {
CollectionData collectionData = parseDocument("OneField.json");
assertNotNull(findDocPart(collectionData, ROOT_DOC_NAME));
}
@Test
public void aFieldMapsToAColumn() {
CollectionData collectionData = parseDocument("OneField.json");
DocPartData rootDocPart = findRootDocPart(collectionData);
DocPartRow firstRow = rootDocPart.iterator().next();
int fieldPosition = findFieldPosition(rootDocPart, "name", FieldType.STRING);
assertTrue(fieldPosition >= 0);
assertTrue(rootDocPart.iterator().hasNext());
assertExistFieldValueInPosition(firstRow, 0, "John");
}
@Test
public void multipleFieldsMapsToMultipleColumns() {
CollectionData collectionData = parseDocument("MultipleFields.json");
DocPartData rootDocPart = findRootDocPart(collectionData);
DocPartRow firstRow = rootDocPart.iterator().next();
assertFieldWithValueExists(rootDocPart, firstRow, "name", FieldType.STRING, "John");
assertFieldWithValueExists(rootDocPart, firstRow, "age", FieldType.INTEGER, 34);
}
@Test
public void nullFieldMapsToNullColumn() {
CollectionData collectionData = parseDocument("NullField.json");
DocPartData rootDocPart = findRootDocPart(collectionData);
DocPartRow firstRow = rootDocPart.iterator().next();
assertFieldWithValueExists(rootDocPart, firstRow, "age", FieldType.NULL, null);
}
@Test
public void emptyArrayMapsToChildColumn() {
CollectionData collectionData = parseDocument("EmptyArray.json");
DocPartData rootDocPart = findRootDocPart(collectionData);
DocPartRow firstRow = rootDocPart.iterator().next();
assertFieldWithValueExists(rootDocPart, firstRow, "department", FieldType.CHILD, IS_ARRAY);
}
@Test
public void arrayCreatesRowInParentTable() {
CollectionData collectionData = parseDocument("ArrayWithScalar.json");
DocPartData rootDocPart = findRootDocPart(collectionData);
DocPartRow firstRow = rootDocPart.iterator().next();
assertFieldWithValueExists(rootDocPart, firstRow, "months", FieldType.CHILD, IS_ARRAY);
}
@Test
public void arrayMapsToNewTable() {
CollectionData collectionData = parseDocument("ArrayWithScalar.json");
assertNotNull(findDocPart(collectionData, "months"));
}
@Test
public void scalarInArrayMapsToColumnWithValue() {
CollectionData collectionData = parseDocument("ArrayWithScalar.json");
DocPartData monthsDocPart = findDocPart(collectionData, "months");
DocPartRow firstRow = monthsDocPart.iterator().next();
assertScalarWithValueExists(monthsDocPart, firstRow, FieldType.INTEGER, 1);
}
@Test
public void subDocumentCreatesRowInParentTable() {
CollectionData collectionData = parseDocument("SubDocument.json");
DocPartData rootDocPart = findRootDocPart(collectionData);
DocPartRow firstRow = rootDocPart.iterator().next();
assertFieldWithValueExists(rootDocPart, firstRow, "address", FieldType.CHILD, IS_SUBDOCUMENT);
}
@Test
public void arrayWithEmptySubdocumentCreatesRow() {
CollectionData collectionData = parseDocument("ArrayWithEmptyDocument.json");
DocPartData departmentDocPart = findDocPart(collectionData, "department");
DocPartRow firstRow = departmentDocPart.iterator().next();
assertNotNull(firstRow);
assertFalse(firstRow.getFieldValues().iterator().hasNext());
}
@Test
public void subDocumentMapsToNewTable() {
CollectionData collectionData = parseDocument("SubDocument.json");
assertNotNull(findDocPart(collectionData, "address"));
}
@Test
public void subDocumentFiledsMapsIntoNewTable() {
CollectionData collectionData = parseDocument("SubDocument.json");
DocPartData addressDocPart = findDocPart(collectionData, "address");
DocPartRow firstRow = addressDocPart.iterator().next();
assertFieldWithValueExists(addressDocPart, firstRow, "street", FieldType.STRING, "My Home");
assertFieldWithValueExists(addressDocPart, firstRow, "zip", FieldType.INTEGER, 28034);
}
@Test
public void subDocumentInArrayMapsToNewTable() {
CollectionData collectionData = parseDocument("ArrayWithDocument.json");
DocPartData departmentDocPart = findDocPart(collectionData, "department");
DocPartRow firstRow = departmentDocPart.iterator().next();
assertFieldWithValueExists(departmentDocPart, firstRow, "name", FieldType.STRING, "dept1");
}
@Test
public void subDocumentHeterogeneousInArrayMapsToSameTable() {
CollectionData collectionData = parseDocument("ArrayWithHeteroDocument.json");
DocPartData rootDocPart = findRootDocPart(collectionData);
DocPartRow rootRow = rootDocPart.iterator().next();
DocPartData departmentDocPart = findDocPart(collectionData, "department");
DocPartRow firstRow = findRowSeq(departmentDocPart, rootRow.getRid(), 0);
assertFieldWithValueExists(departmentDocPart, firstRow, "name", FieldType.STRING, "dept1");
DocPartRow secondRow = findRowSeq(departmentDocPart, rootRow.getRid(), 1);
assertFieldWithValueExists(departmentDocPart, secondRow, "code", FieldType.INTEGER, 54);
}
@Test
public void subDocumentAndArrayCanMapToSameTable() {
CollectionData collectionData = parseDocument("ArrayAndObjectCollision.json");
DocPartData departmentsDocPart = findDocPart(collectionData, "departments");
DocPartRow row4Document = findRowSeq(departmentsDocPart, 0);
assertFieldWithValueExists(departmentsDocPart, row4Document, "dept", FieldType.CHILD,
IS_SUBDOCUMENT);
DocPartRow row4Array = findRowSeq(departmentsDocPart, 1);
assertFieldWithValueExists(departmentsDocPart, row4Array, "dept", FieldType.CHILD, IS_ARRAY);
DocPartData deptDocPart = findDocPart(collectionData, "departments.dept");
DocPartRow rowDocument = findRowSeq(deptDocPart, row4Document.getRid(), NO_SEQ);
assertFieldWithValueExists(deptDocPart, rowDocument, "name", FieldType.STRING, "dept1");
DocPartRow firstRowArray = findRowSeq(deptDocPart, row4Array.getRid(), 0);
assertFieldWithValueExists(deptDocPart, firstRowArray, "name", FieldType.STRING, "dept2");
DocPartRow secondRowArray = findRowSeq(deptDocPart, row4Array.getRid(), 1);
assertFieldWithValueExists(deptDocPart, secondRowArray, "name", FieldType.STRING, "dept3");
}
@Test
public void arrayInArrayMapsToNewTable() {
CollectionData collectionData = parseDocument("MultiArray.json");
DocPartData rootDocPart = findRootDocPart(collectionData);
DocPartRow firstRow = rootDocPart.iterator().next();
assertFieldWithValueExists(rootDocPart, firstRow, "months", FieldType.CHILD, IS_ARRAY);
DocPartData monthsDocPart = findDocPart(collectionData, "months");
DocPartRow firstRowMonths = monthsDocPart.iterator().next();
assertScalarWithValueExists(monthsDocPart, firstRowMonths, FieldType.CHILD, IS_ARRAY);
DocPartData subArrayDocPart = findDocPart(collectionData, "months.$2");
assertNotNull(subArrayDocPart);
DocPartRow firstRowSubArray = findRowSeq(subArrayDocPart, firstRow.getRid(), 0);
assertNotNull(firstRowSubArray);
int fieldSubArray = findScalarPosition(subArrayDocPart, FieldType.INTEGER);
assertTrue(fieldSubArray >= 0);
assertExistScalarValueInPosition(firstRowSubArray, fieldSubArray, 1);
}
@Test
public void emptyArrayInArrayMapsToATable() {
CollectionData collectionData = parseDocument("MultiArrayEmpty.json");
DocPartData monthsDocPart = findDocPart(collectionData, "months");
DocPartRow firstRow = monthsDocPart.iterator().next();
assertScalarWithValueExists(monthsDocPart, firstRow, FieldType.CHILD, IS_ARRAY);
DocPartData subArrayDocPart = findDocPart(collectionData, "months.$2");
assertNotNull(subArrayDocPart);
}
@Test
public void mapFieldTypes() {
CollectionData collectionData = parseDocument("FieldTypes.json");
DocPartData rootDocPart = findRootDocPart(collectionData);
DocPartRow firstRow = rootDocPart.iterator().next();
assertFieldWithValueExists(rootDocPart, firstRow, "_id", FieldType.MONGO_OBJECT_ID, HexUtils
.hex2Bytes("5298a5a03b3f4220588fe57c"));
assertFieldWithValueExists(rootDocPart, firstRow, "null", FieldType.NULL, null);
assertFieldWithValueExists(rootDocPart, firstRow, "boolean", FieldType.BOOLEAN, true);
assertFieldWithValueExists(rootDocPart, firstRow, "integer", FieldType.INTEGER, 10);
assertFieldWithValueExists(rootDocPart, firstRow, "double", FieldType.DOUBLE, 10.2);
assertFieldWithValueExists(rootDocPart, firstRow, "string", FieldType.STRING, "john");
assertFieldWithValueExists(rootDocPart, firstRow, "long", FieldType.LONG, 10020202020L);
DateTimeFormatter sdf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX");
assertFieldWithValueExists(rootDocPart, firstRow, "date", FieldType.INSTANT, Instant.from(sdf
.parse("2015-06-18T16:43:58.967Z")));
}
@Test
public void nodesWithDifferentFieldsHasSameNumberOfFieldsInDocPartRow() {
CollectionData collectionData = parseDocument("NodesWithDifferentFields.json");
DocPartData docPart = findDocPart(collectionData, "department");
int fieldsNumber = (int) docPart.getMetaDocPart().streamFields().count();
Iterator<DocPartRow> it = docPart.iterator();
DocPartRow first = it.next();
DocPartRow second = it.next();
assertEquals(fieldsNumber, countFields(second));
assertEquals(countFields(first), countFields(second));
}
@Test
public void collectionDataIsSortedByParentRelationship() {
CollectionData collectionData = parseDocument("DocPartLevelSorted1.json",
"DocPartLevelSorted2.json");
Set<TableRef> parsed = new HashSet<>();
for (DocPartData docPartData : collectionData.orderedDocPartData()) {
TableRef tableRef = docPartData.getMetaDocPart().getTableRef();
if (!tableRef.isRoot()) {
TableRef parent = tableRef.getParent().get();
assertTrue(parsed.contains(parent));
}
parsed.add(tableRef);
}
}
private int countFields(DocPartRow row) {
Iterator<KvValue<?>> it = row.getFieldValues().iterator();
int cont = 0;
while (it.hasNext()) {
cont++;
it.next();
}
return cont;
}
private DocPartData findRootDocPart(CollectionData collectionData) {
return findDocPart(collectionData, ROOT_DOC_NAME);
}
private DocPartData findDocPart(CollectionData collectionData, String path) {
ArrayList<String> pathList = new ArrayList<>(Arrays.asList(path.split("\\.")));
Collections.reverse(pathList);
pathList.add(ROOT_DOC_NAME);
String name = pathList.get(0);
for (DocPartData docPartData : collectionData.orderedDocPartData()) {
MetaDocPart metaDocPart = docPartData.getMetaDocPart();
if (name.equals(metaDocPart.getTableRef().getName())) {
if (isSamePath(pathList, metaDocPart.getTableRef())) {
return docPartData;
}
}
}
return null;
}
private void assertFieldWithValueExists(DocPartData rootDocPart, DocPartRow firstRow,
String fieldName, FieldType fieldType, Object fieldValue) {
int fieldOrder = findFieldPosition(rootDocPart, fieldName, fieldType);
assertTrue(fieldOrder >= 0);
assertExistFieldValueInPosition(firstRow, fieldOrder, fieldValue);
}
private void assertScalarWithValueExists(DocPartData rootDocPart, DocPartRow firstRow,
FieldType fieldType, Object fieldValue) {
int scalarOrder = findScalarPosition(rootDocPart, fieldType);
assertTrue(scalarOrder >= 0);
assertExistScalarValueInPosition(firstRow, scalarOrder, fieldValue);
}
private boolean isSamePath(ArrayList<String> pathList, TableRef tableRef) {
int idx = 0;
Optional<TableRef> table = Optional.of(tableRef);
while (table.isPresent()) {
if (!pathList.get(idx).equals(table.get().getName())) {
return false;
}
idx++;
table = table.get().getParent();
}
return true;
}
private DocPartRow findRowSeq(DocPartData docPartData, Integer seq) {
for (DocPartRow row : docPartData) {
if (row.getSeq() == seq) {
return row;
}
}
return null;
}
private DocPartRow findRowSeq(DocPartData docPartData, int parentId, Integer seq) {
for (DocPartRow row : docPartData) {
if (row.getPid() == parentId && row.getSeq() == seq) {
return row;
}
}
return null;
}
private int findFieldPosition(DocPartData docPartData, String name, FieldType type) {
int idx = 0;
Iterator<? extends MetaField> iterator = docPartData.orderedMetaFieldIterator();
while (iterator.hasNext()) {
MetaField field = iterator.next();
if (field.getName().equals(name) && field.getType() == type) {
return idx;
}
idx++;
}
return -1;
}
private int findScalarPosition(DocPartData docPartData, FieldType type) {
int idx = 0;
Iterator<? extends MetaScalar> iterator = docPartData.orderedMetaScalarIterator();
while (iterator.hasNext()) {
MetaScalar scalar = iterator.next();
if (scalar.getType() == type) {
return idx;
}
idx++;
}
return -1;
}
private boolean assertExistFieldValueInPosition(DocPartRow row, int order, Object value) {
return assertExistValueInPosition(row.getFieldValues(), order, value);
}
private boolean assertExistScalarValueInPosition(DocPartRow row, int order, Object value) {
return assertExistValueInPosition(row.getScalarValues(), order, value);
}
private boolean assertExistValueInPosition(Iterable<KvValue<?>> values, int order, Object value) {
Iterator<KvValue<?>> iterator = values.iterator();
KvValue<?> kv = null;
for (int i = 0; i <= order; i++) {
kv = iterator.next();
}
if (kv.getType() == NullType.INSTANCE) {
assertEquals(value, null);
} else if (kv.getType() == MongoObjectIdType.INSTANCE) {
assertArrayEquals((byte[]) value, ((KvMongoObjectId) kv).getArrayValue());
} else {
assertEquals(value, kv.getValue());
}
return true;
}
private CollectionData parseDocument(String... docNames) {
MockRidGenerator ridGenerator = new MockRidGenerator();
IdentifierFactory identifierFactory =
new DefaultIdentifierFactory(new MockIdentifierInterface());
MutableMetaDatabase db = mutableSnapshot.getMetaDatabaseByName(DB1);
D2RTranslator translator = new D2RTranslatorStack(tableRefFactory, identifierFactory,
ridGenerator, db, db.getMetaCollectionByName(COLLA));
for (String doc : docNames) {
KvDocument document = parser.createFromResource("docs/" + doc);
translator.translate(document);
}
return translator.getCollectionDataAccumulator();
}
}