/* * 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.torod.impl.sql; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.torodb.core.TableRef; import com.torodb.core.TableRefFactory; import com.torodb.core.backend.BackendCursor; import com.torodb.core.cursors.Cursor; import com.torodb.core.cursors.EmptyCursor; import com.torodb.core.d2r.R2DTranslator; import com.torodb.core.exceptions.user.CollectionNotFoundException; import com.torodb.core.exceptions.user.IndexNotFoundException; import com.torodb.core.language.AttributeReference; import com.torodb.core.language.AttributeReference.Key; import com.torodb.core.language.AttributeReference.ObjectKey; import com.torodb.core.transaction.InternalTransaction; import com.torodb.core.transaction.metainf.FieldType; import com.torodb.core.transaction.metainf.MetaCollection; import com.torodb.core.transaction.metainf.MetaDatabase; import com.torodb.core.transaction.metainf.MetaDocPart; import com.torodb.core.transaction.metainf.MetaField; import com.torodb.core.transaction.metainf.MetaIndex; import com.torodb.kvdocument.values.KvValue; import com.torodb.torod.CollectionInfo; import com.torodb.torod.IndexInfo; import com.torodb.torod.TorodTransaction; import com.torodb.torod.cursors.EmptyTorodCursor; import com.torodb.torod.cursors.TorodCursor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jooq.lambda.tuple.Tuple2; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.json.Json; /** * */ public abstract class SqlTorodTransaction<T extends InternalTransaction> implements TorodTransaction { private static final Logger LOGGER = LogManager.getLogger(SqlTorodTransaction.class); private boolean closed = false; private final SqlTorodConnection connection; private final T internalTransaction; public SqlTorodTransaction(SqlTorodConnection connection) { this.connection = connection; this.internalTransaction = createInternalTransaction(connection); } protected abstract T createInternalTransaction(SqlTorodConnection connection); @Override public boolean isClosed() { return closed; } @Override public final SqlTorodConnection getConnection() { return connection; } protected T getInternalTransaction() { return internalTransaction; } @Override public boolean existsDatabase(String dbName) { MetaDatabase metaDb = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); return metaDb != null; } @Override public boolean existsCollection(String dbName, String colName) { MetaDatabase metaDb = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); return metaDb != null && metaDb.getMetaCollectionByName(colName) != null; } @Override public List<String> getDatabases() { return getInternalTransaction().getMetaSnapshot().streamMetaDatabases() .map(metaDb -> metaDb.getName()).collect(Collectors.toList()); } @Override public long getDatabaseSize(String dbName) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { return 0L; } return getInternalTransaction().getBackendTransaction().getDatabaseSize(db); } @Override public long countAll(String dbName, String colName) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { return 0; } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { return 0; } return getInternalTransaction().getBackendTransaction().countAll(db, col); } @Override public long getCollectionSize(String dbName, String colName) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { return 0; } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { return 0; } return getInternalTransaction().getBackendTransaction().getCollectionSize(db, col); } @Override public long getDocumentsSize(String dbName, String colName) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { return 0; } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { return 0; } return getInternalTransaction().getBackendTransaction().getDocumentsSize(db, col); } @Override public TorodCursor findAll(String dbName, String colName) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { LOGGER.trace("Db with name " + dbName + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { LOGGER.trace("Collection " + dbName + '.' + colName + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } return toToroCursor(getInternalTransaction() .getBackendTransaction() .findAll(db, col) ); } @Override public TorodCursor findByAttRef(String dbName, String colName, AttributeReference attRef, KvValue<?> value) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { LOGGER.trace("Db with name " + dbName + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { LOGGER.trace("Collection " + dbName + '.' + colName + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } TableRef ref = extractTableRef(attRef); String lastKey = extractKeyName(attRef.getKeys().get(attRef.getKeys().size() - 1)); MetaDocPart docPart = col.getMetaDocPartByTableRef(ref); if (docPart == null) { LOGGER.trace("DocPart " + dbName + '.' + colName + '.' + ref + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } MetaField field = docPart.getMetaFieldByNameAndType(lastKey, FieldType.from(value.getType())); if (field == null) { LOGGER.trace("Field " + dbName + '.' + colName + '.' + ref + '.' + lastKey + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } return toToroCursor(getInternalTransaction() .getBackendTransaction() .findByField(db, col, docPart, field, value) ); } @Override public TorodCursor findByAttRefIn(String dbName, String colName, AttributeReference attRef, Collection<KvValue<?>> values) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { LOGGER.trace("Db with name " + dbName + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { LOGGER.trace("Collection " + dbName + '.' + colName + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } if (values.isEmpty()) { LOGGER.trace( "An empty list of values have been given as in condition. An empty cursor is returned"); return new EmptyTorodCursor(); } TableRef ref = extractTableRef(attRef); String lastKey = extractKeyName(attRef.getKeys().get(attRef.getKeys().size() - 1)); MetaDocPart docPart = col.getMetaDocPartByTableRef(ref); if (docPart == null) { LOGGER.trace("DocPart " + dbName + '.' + colName + '.' + ref + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } Multimap<MetaField, KvValue<?>> valuesMap = ArrayListMultimap.create(); for (KvValue<?> value : values) { MetaField field = docPart.getMetaFieldByNameAndType(lastKey, FieldType.from(value.getType())); if (field != null) { valuesMap.put(field, value); } } return toToroCursor(getInternalTransaction() .getBackendTransaction() .findByFieldIn(db, col, docPart, valuesMap) ); } @Override public Cursor<Tuple2<Integer, KvValue<?>>> findByAttRefInProjection(String dbName, String colName, AttributeReference attRef, Collection<KvValue<?>> values) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { LOGGER.trace("Db with name " + dbName + " does not exist. An empty cursor is returned"); return new EmptyCursor<>(); } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { LOGGER.trace("Collection " + dbName + '.' + colName + " does not exist. An empty cursor is returned"); return new EmptyCursor<>(); } if (values.isEmpty()) { LOGGER.trace( "An empty list of values have been given as in condition. An empty cursor is returned"); return new EmptyCursor<>(); } TableRef ref = extractTableRef(attRef); String lastKey = extractKeyName(attRef.getKeys().get(attRef.getKeys().size() - 1)); MetaDocPart docPart = col.getMetaDocPartByTableRef(ref); if (docPart == null) { LOGGER.trace("DocPart " + dbName + '.' + colName + '.' + ref + " does not exist. An empty cursor is returned"); return new EmptyCursor<>(); } Multimap<MetaField, KvValue<?>> valuesMap = ArrayListMultimap.create(); for (KvValue<?> value : values) { MetaField field = docPart.getMetaFieldByNameAndType(lastKey, FieldType.from(value.getType())); if (field != null) { valuesMap.put(field, value); } } return getInternalTransaction().getBackendTransaction() .findByFieldInProjection(db, col, docPart, valuesMap); } @Override public TorodCursor fetch(String dbName, String colName, Cursor<Integer> didCursor) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { LOGGER.trace("Db with name " + dbName + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { LOGGER.trace("Collection " + dbName + '.' + colName + " does not exist. An empty cursor is returned"); return new EmptyTorodCursor(); } return toToroCursor(getInternalTransaction() .getBackendTransaction() .fetch(db, col, didCursor) ); } private TorodCursor toToroCursor(BackendCursor backendCursor) { R2DTranslator r2dTrans = getConnection().getServer().getR2DTranslator(); return new LazyTorodCursor(r2dTrans, backendCursor); } @Override public Stream<CollectionInfo> getCollectionsInfo(String dbName) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { return Stream.empty(); } return db.streamMetaCollections() .map(metaCol -> new CollectionInfo(metaCol.getName(), Json.createObjectBuilder().build())); } @Override public CollectionInfo getCollectionInfo(String dbName, String colName) throws CollectionNotFoundException { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { throw new CollectionNotFoundException(dbName, colName); } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { throw new CollectionNotFoundException(dbName, colName); } return new CollectionInfo(db.getMetaCollectionByName(colName).getName(), Json .createObjectBuilder().build()); } @Override public Stream<IndexInfo> getIndexesInfo(String dbName, String colName) { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { return Stream.empty(); } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { return Stream.empty(); } return col.streamContainedMetaIndexes() .map(metaIdx -> createIndexInfo(metaIdx)); } @Override public IndexInfo getIndexInfo(String dbName, String colName, String idxName) throws IndexNotFoundException { MetaDatabase db = getInternalTransaction().getMetaSnapshot().getMetaDatabaseByName(dbName); if (db == null) { throw new IndexNotFoundException(dbName, colName, idxName); } MetaCollection col = db.getMetaCollectionByName(colName); if (col == null) { throw new IndexNotFoundException(dbName, colName, idxName); } MetaIndex idx = col.getMetaIndexByName(idxName); if (idx == null) { throw new IndexNotFoundException(dbName, colName, idxName); } return createIndexInfo(idx); } protected IndexInfo createIndexInfo(MetaIndex metaIndex) { IndexInfo.Builder indexInfoBuilder = new IndexInfo.Builder(metaIndex.getName(), metaIndex .isUnique()); metaIndex.iteratorFields() .forEachRemaining(metaIndexField -> indexInfoBuilder.addField( getAttrivuteReference(metaIndexField.getTableRef(), metaIndexField.getName()), metaIndexField.getOrdering().isAscending())); return indexInfoBuilder.build(); } protected AttributeReference getAttrivuteReference(TableRef tableRef, String name) { AttributeReference.Builder attributeReferenceBuilder = new AttributeReference.Builder(); while (!tableRef.isRoot()) { attributeReferenceBuilder.addObjectKeyAsFirst(tableRef.getName()); tableRef = tableRef.getParent().get(); } attributeReferenceBuilder.addObjectKey(name); return attributeReferenceBuilder.build(); } protected TableRef extractTableRef(AttributeReference attRef) { TableRefFactory tableRefFactory = getConnection().getServer().getTableRefFactory(); TableRef ref = tableRefFactory.createRoot(); if (attRef.getKeys().isEmpty()) { throw new IllegalArgumentException("The empty attribute reference is not valid"); } if (attRef.getKeys().size() > 1) { List<Key<?>> keys = attRef.getKeys(); List<Key<?>> tableKeys = keys.subList(0, keys.size() - 1); for (Key<?> key : tableKeys) { ref = tableRefFactory.createChild(ref, extractKeyName(key)); } } return ref; } protected String extractKeyName(Key<?> key) { if (key instanceof ObjectKey) { return ((ObjectKey) key).getKey(); } else { throw new IllegalArgumentException("Keys whose type is not object are not valid"); } } @Override public void rollback() { getInternalTransaction().rollback(); } @Override public void close() { if (!closed) { closed = true; getInternalTransaction().close(); connection.onTransactionClosed(this); } } }