/*
* 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 com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Table;
import com.torodb.core.TableRef;
import com.torodb.core.d2r.DocPartResult;
import com.torodb.core.d2r.DocPartResultRow;
import com.torodb.core.d2r.InternalFields;
import com.torodb.core.d2r.R2DTranslator;
import com.torodb.core.document.ToroDocument;
import com.torodb.core.transaction.metainf.FieldType;
import com.torodb.core.transaction.metainf.MetaDocPart;
import com.torodb.core.transaction.metainf.MetaField;
import com.torodb.core.transaction.metainf.MetaScalar;
import com.torodb.kvdocument.values.KvBoolean;
import com.torodb.kvdocument.values.KvDocument;
import com.torodb.kvdocument.values.KvValue;
import com.torodb.kvdocument.values.heap.ListKvArray;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class R2DTranslatorImpl implements R2DTranslator {
@Override
public List<ToroDocument> translate(Iterator<DocPartResult> docPartResultIt) {
ImmutableList.Builder<ToroDocument> readedDocuments = ImmutableList.builder();
Table<TableRef, Integer, Map<String, List<KvValue<?>>>> currentFieldDocPartTable =
HashBasedTable.<TableRef, Integer, Map<String, List<KvValue<?>>>>create();
Table<TableRef, Integer, Map<String, List<KvValue<?>>>> childFieldDocPartTable =
HashBasedTable.<TableRef, Integer, Map<String, List<KvValue<?>>>>create();
int previousDepth = -1;
while (docPartResultIt.hasNext()) {
DocPartResult docPartResult = docPartResultIt.next();
MetaDocPart metaDocPart = docPartResult.getMetaDocPart();
TableRef tableRef = metaDocPart.getTableRef();
if (previousDepth != -1 && previousDepth != tableRef.getDepth()) {
Table<TableRef, Integer, Map<String, List<KvValue<?>>>> previousFieldChildDocPartTable =
childFieldDocPartTable;
childFieldDocPartTable = currentFieldDocPartTable;
currentFieldDocPartTable = previousFieldChildDocPartTable;
if (!tableRef.isRoot()) {
currentFieldDocPartTable.clear();
}
}
previousDepth = tableRef.getDepth();
Map<Integer, Map<String, List<KvValue<?>>>> childFieldDocPartRow =
childFieldDocPartTable.row(tableRef);
Map<Integer, Map<String, List<KvValue<?>>>> currentFieldDocPartRow;
if (tableRef.isRoot()) {
currentFieldDocPartRow = null;
} else {
currentFieldDocPartRow = currentFieldDocPartTable.row(tableRef.getParent().get());
}
readResult(metaDocPart, tableRef, docPartResult,
currentFieldDocPartRow, childFieldDocPartRow, readedDocuments);
}
return readedDocuments.build();
}
private void readResult(MetaDocPart metaDocPart, TableRef tableRef, DocPartResult docPartResult,
Map<Integer, Map<String, List<KvValue<?>>>> currentFieldDocPartRow,
Map<Integer, Map<String, List<KvValue<?>>>> childFieldDocPartRow,
ImmutableList.Builder<ToroDocument> readedDocuments) {
while (docPartResult.hasNext()) {
KvDocument.Builder documentBuilder = new KvDocument.Builder();
DocPartResultRow row = docPartResult.next();
Integer did = row.getDid();
Integer rid = row.getRid();
Integer pid = row.getPid();
Integer seq = row.getSeq();
Map<String, List<KvValue<?>>> childFieldDocPartCell = childFieldDocPartRow.get(rid);
//TODO: ensure MetaField order using ResultSet meta data
Iterator<? extends MetaScalar> metaScalarIterator = metaDocPart
.streamScalars().iterator();
boolean wasScalar = false;
int fieldIndex = 0;
while (metaScalarIterator.hasNext() && !wasScalar) {
assert seq != null : "found scalar value outside of an array";
MetaScalar metaScalar = metaScalarIterator.next();
KvValue<?> value = row.getUserValue(fieldIndex, metaScalar.getType());
fieldIndex++;
if (value != null) {
if (metaScalar.getType() == FieldType.CHILD) {
value = getChildValue(value, getDocPartCellName(tableRef), childFieldDocPartCell);
}
addValueToDocPartRow(currentFieldDocPartRow, tableRef, pid, seq, value);
wasScalar = true;
}
}
if (wasScalar) {
continue;
}
Iterator<? extends MetaField> metaFieldIterator = metaDocPart
.streamFields().iterator();
while (metaFieldIterator.hasNext()) {
MetaField metaField = metaFieldIterator.next();
KvValue<?> value = row.getUserValue(fieldIndex, metaField.getType());
fieldIndex++;
if (value != null) {
if (metaField.getType() == FieldType.CHILD) {
value = getChildValue(value, metaField.getName(), childFieldDocPartCell);
}
documentBuilder.putValue(metaField.getName(), value);
}
}
if (tableRef.isRoot()) {
readedDocuments.add(new ToroDocument(did, documentBuilder.build()));
} else {
addValueToDocPartRow(currentFieldDocPartRow, tableRef, pid, seq, documentBuilder.build());
}
}
}
private KvValue<?> getChildValue(KvValue<?> value, String key,
Map<String, List<KvValue<?>>> childDocPartCell) {
KvBoolean child = (KvBoolean) value;
if (child.getValue() == InternalFields.CHILD_ARRAY_VALUE) {
List<KvValue<?>> elements;
if (childDocPartCell == null || (elements = childDocPartCell.get(key)) == null) {
value = new ListKvArray(ImmutableList.of());
} else {
value = new ListKvArray(elements);
}
} else {
value = childDocPartCell.get(key).get(0);
}
return value;
}
private void addValueToDocPartRow(Map<Integer, Map<String, List<KvValue<?>>>> currentDocPartRow,
TableRef tableRef,
Integer pid, Integer seq, KvValue<?> value) {
if (seq == null) {
setDocPartRowValue(currentDocPartRow, tableRef, pid, null, ImmutableList.of(value));
} else {
addToDocPartRow(currentDocPartRow, tableRef, pid, seq, value);
}
}
private void setDocPartRowValue(
Map<Integer, Map<String, List<KvValue<?>>>> docPartRow, TableRef tableRef, Integer pid,
Integer seq, ImmutableList<KvValue<?>> elements) {
Map<String, List<KvValue<?>>> docPartCell = getDocPartCell(docPartRow, pid);
String name = getDocPartCellName(tableRef);
docPartCell.put(name, elements);
}
private void addToDocPartRow(
Map<Integer, Map<String, List<KvValue<?>>>> docPartRow, TableRef tableRef, Integer pid,
Integer seq, KvValue<?> value) {
String name = getDocPartCellName(tableRef);
Map<String, List<KvValue<?>>> docPartCell = getDocPartCell(docPartRow, pid);
List<KvValue<?>> elements = docPartCell
.computeIfAbsent(name, n -> new ArrayList<KvValue<?>>());
final int size = elements.size();
if (seq < size) {
elements.set(seq, value);
} else {
for (int i = elements.size(); i < seq; i++) {
elements.add(null);
}
elements.add(value);
}
}
private String getDocPartCellName(TableRef tableRef) {
while (tableRef.isInArray()) {
tableRef = tableRef.getParent().get();
}
return tableRef.getName();
}
private Map<String, List<KvValue<?>>> getDocPartCell(
Map<Integer, Map<String, List<KvValue<?>>>> docPartRow,
Integer pid) {
return docPartRow.computeIfAbsent(pid, p -> new HashMap<String, List<KvValue<?>>>());
}
}