All Downloads are FREE. Search and download functionalities are using the official Maven repository.

prompto.store.datomic.BaseDatomicStore Maven / Gradle / Ivy

There is a newer version: 0.0.136
Show newest version
package prompto.store.datomic;

import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import datomic.Connection;
import datomic.Database;
import datomic.Datom;
import datomic.Entity;
import datomic.Peer;
import datomic.QueryRequest;
import datomic.query.EntityMap;
import prompto.error.InternalError;
import prompto.error.PromptoError;
import prompto.intrinsic.PromptoBinary;
import prompto.store.AttributeInfo;
import prompto.store.IQuery;
import prompto.store.IQueryBuilder;
import prompto.store.IStorable;
import prompto.store.IStorable.IDbIdFactory;
import prompto.store.IStore;
import prompto.store.IStored;
import prompto.store.IStoredIterable;
import prompto.store.datomic.Constants.Db;
import prompto.store.datomic.Constants.DbCardinality;

public abstract class BaseDatomicStore implements IStore {

	String uri;
	Connection cnx;
	Map attributesByDbId = new HashMap<>();
	Map attributesByName = new HashMap<>();
	
	public BaseDatomicStore(String uri, boolean create) {
		this.uri = uri;
		if(create && !Peer.createDatabase(uri))
			throw new RuntimeException("Unable to create db at: " + uri);
	}
	
	public void connect() {
		cnx = Peer.connect(this.uri);
	}

	@Override
	public boolean checkConnection() {
		return cnx!=null;
	}

	@Override
	public Class getDbIdClass() {
		return Object.class;
	}

	@Override
	public Object newDbId() {
		throw new UnsupportedOperationException();
	}
	
	@Override
	public Object convertToDbId(Object dbId) {
		throw new UnsupportedOperationException();
	}

	@Override
	public AttributeInfo getAttributeInfo(String name) throws PromptoError {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void createOrUpdateAttributes(Collection attributes) throws PromptoError {
		Stream> builtins = collectBuiltinAttributesFacts();
		Stream> custom = attributes.stream()
				.map(this::collectCustomAttributeFacts);
		List data = Stream.concat(builtins,  custom)
				.filter(Objects::nonNull)
				.flatMap(List::stream)
				.collect(Collectors.toList());
		if(!data.isEmpty()) try {
			cnx.transact(data).get();
		} catch(Exception e) {
			throw new InternalError(e);
		}
	}
	
	private Stream> collectBuiltinAttributesFacts() {
		List> builtins = new ArrayList<>();
		builtins.add(getCategoryAttributes());
		return builtins.stream();
	}

	private List getCategoryAttributes() {
		Map unordered = new HashMap<>();
		unordered.put(Db.IDENT.dbName(), ":category");
		unordered.put(Db.VALUETYPE.dbName(), ":db.type/string");
		unordered.put(Db.CARDINALITY.dbName(), DbCardinality.MANY.dbName());
		Map ordered = new HashMap<>();
		ordered.put(Db.IDENT.dbName(), ":category/ordered");
		ordered.put(Db.VALUETYPE.dbName(), ":db.type/string");
		ordered.put(Db.CARDINALITY.dbName(), DbCardinality.MANY.dbName());
		return Arrays.asList(unordered, ordered);
	}

	public void dumpFacts(PrintStream output) {
		Iterable data = cnx.db().datoms(Database.EAVT);
		Iterator iter = data.iterator();
		while(iter.hasNext()) {
			Datom d = iter.next();
			output.println(d.e().toString() + " " + d.a().toString() + " " + d.v().toString());
		}
	}

	public List collectCustomAttributeFacts(AttributeInfo attribute) {
		AttributeInfo info = getAttributeInfo(attribute.getName());
		if(attribute.equals(info))
			return Collections.emptyList();
		FamilyHelper helper = FamilyHelper.HELPERS.get(attribute.getFamily());
		return helper.collectAttributeFacts(attribute);
	}

	@Override
	public IStorable newStorable(String[] categories, IDbIdFactory factory) {
		return new StorableDocument(categories, factory);
	}

	@Override
	public void store(Collection deletables, Collection storables) throws PromptoError {
		Stream retractions = null;
		Stream additions = null;
		if(deletables!=null)
			retractions = deletables.stream()
				.map(d->Arrays.asList(":db.fn/retractEntity", d));
		if(storables!=null)
			additions = storables.stream()
				.map((s)->((StorableDocument)s).getAddedFacts())
				.flatMap(Function.identity());
		if(retractions==null && additions==null)
			return;
		Stream all;
		if(retractions==null)
			all = additions;
		else if(additions==null)
			all = retractions;
		else
			all = Stream.of(retractions, additions)
					.flatMap(Function.identity());
		List list = all.collect(Collectors.toList());
		if(!list.isEmpty()) try {
			@SuppressWarnings("rawtypes")
			Map result = cnx.transact(list).get();
			if(storables!=null) {
				Database dbAfter = (Database)result.get(Connection.DB_AFTER);
				Object tempids = result.get(Connection.TEMPIDS);
				for(IStorable storable : storables)
					storable.setDbId(Peer.resolveTempid(dbAfter, tempids, storable.getOrCreateDbId()));
			}
		} catch(Exception e) {
			throw new InternalError(e);
		}
	}
	
	public void storeFacts(DatomicFacts ... storables) throws PromptoError {
		List list = Stream.of(storables)
				.map(DatomicFacts::getAddedFacts)
				.flatMap(Function.identity())
				.collect(Collectors.toList());
		try {
			@SuppressWarnings("rawtypes")
			Map result = cnx.transact(list).get();
			Database dbAfter = (Database)result.get(Connection.DB_AFTER);
			Object tempids = result.get(Connection.TEMPIDS);
			for(DatomicFacts storable : storables)
				storable.setDbId(Peer.resolveTempid(dbAfter, tempids, storable.getDbId()));
		} catch(Exception e) {
			throw new InternalError(e);
		}
	}

	@Override
	public void deleteAll() throws PromptoError {
		throw new UnsupportedOperationException();
	}

	@Override
	public PromptoBinary fetchBinary(Object dbId, String attr)
			throws PromptoError {
		throw new UnsupportedOperationException();
	}

	@Override
	public IStored fetchUnique(Object dbId) throws PromptoError {
		if(dbId instanceof EntityMap)
			dbId = ((EntityMap)dbId).valAt(Constants.Db.ID.dbName());
		Entity entity = cnx.db().entity(dbId);
		return new StoredDocument(entity);
	}

	@Override
	public IQueryBuilder newQueryBuilder() {
		return new DatomicQueryBuilder();
	}

	@Override
	public IStored fetchOne(IQuery query) throws PromptoError {
		Collection> all = fetch(query);
		Iterator> iter = all.iterator();
		if(!iter.hasNext())
			return null;
		Collection one = iter.next();
		Object dbId = one.iterator().next();
		Entity entity = cnx.db().entity(dbId);
		return new StoredDocument(entity);
	}

	@Override
	public IStoredIterable fetchMany(IQuery query) throws PromptoError {
		return new StoredIterable((DatomicQuery)query);
	}
	
	private Collection> fetch(IQuery query) {
		if(query==null)
			return Peer.query("[:find ?e :in $ :where [?e :category _]]", cnx.db());
		else {
			DatomicQuery d = (DatomicQuery)query;
			Object q = d.getQuery();
			List inputs = d.getInputs();
			inputs.set(0, cnx.db());
			QueryRequest r = QueryRequest.create(q, inputs.toArray());
			return Peer.query(r);
		}
	}


	@Override
	public void flush() throws PromptoError {
		// no action required
	}
	
	@Override
	public void close() throws IOException {
		cnx.release();
		cnx = null;
	}

	class StoredIterable implements IStoredIterable {

		DatomicQuery query;
		Collection> entities;
		Long totalCount = null;
		
		StoredIterable(DatomicQuery query) {
			this.query = query;
			this.entities = fetch(query);
		}
		
		@Override
		public Iterator iterator() {
			Iterator> iter = entities.iterator();

			return new Iterator() {
				@Override
				public boolean hasNext() {
					return iter.hasNext();
				}
				
				@Override
				public IStored next() {
					Collection one = iter.next();
					Object dbId = one.iterator().next();
					Entity entity = cnx.db().entity(dbId);
					return new StoredDocument(entity);
				}
			};
		}
		
		@Override
		public long totalCount() {
			/* if(totalCount==null) {
				if(query==null || query.predicate==null)
					totalCount = collection.count();
				else
					totalCount = collection.count(query.predicate);
			} */
			return totalCount;
		}
		
		@Override
		public long count() {
			return entities.size();
		}
	};


}