blob: 8004f987c488a6922dfaaee2efcda1b02d52d5eb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.backend.store;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.hugegraph.HugeException;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.iterator.ExtendableIterator;
import org.apache.hugegraph.perf.PerfUtil.Watched;
import org.apache.hugegraph.type.HugeType;
import org.apache.hugegraph.type.define.Action;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.InsertionOrderUtil;
public class BackendMutation {
private final MutationTable updates;
public BackendMutation() {
this.updates = new MutationTable();
}
public BackendMutation(int initialCapacity) {
this.updates = new MutationTable(initialCapacity);
}
/**
* Add data entry with an action to collection `updates`
* @param entry the backend entry
* @param action operate action on the entry
*/
@Watched(prefix = "mutation")
public void add(BackendEntry entry, Action action) {
Id id = entry.id();
assert id != null;
if (this.updates.containsKey(entry.type(), id)) {
this.optimizeUpdates(entry, action);
} else {
// If there is no entity with this id, add it
this.updates.put(entry.type(), id, BackendAction.of(action, entry));
}
}
/**
* Put directly without checking and merging
*/
public void put(BackendEntry entry, Action action) {
Id id = entry.id();
this.updates.put(entry.type(), id, BackendAction.of(action, entry));
}
/**
* The optimized scenes include but are not limited to:
* 1.If you want to delete an entry, the other mutations previously
* can be ignored.
* 2.As similar to the No.1 item, If you want to insert an entry,
* the other mutations previously also can be ignored.
* 3.If you append an entry and then eliminate it, the new action
* can override the old one.
*/
@Watched(prefix = "mutation")
private void optimizeUpdates(BackendEntry entry, Action action) {
final Id id = entry.id();
assert id != null;
final List<BackendAction> items = this.updates.get(entry.type(), id);
assert items != null;
boolean ignoreCurrent = false;
for (Iterator<BackendAction> iter = items.iterator(); iter.hasNext();) {
BackendAction originItem = iter.next();
Action originAction = originItem.action();
switch (action) {
case INSERT:
iter.remove();
break;
case DELETE:
if (originAction == Action.INSERT) {
throw incompatibleActionException(action, originAction);
} else if (originAction == Action.DELETE) {
ignoreCurrent = true;
} else {
iter.remove();
}
break;
case APPEND:
if (entry.type().isUniqueIndex() &&
originAction == Action.APPEND) {
throw new IllegalArgumentException(String.format(
"Unique constraint conflict is found in" +
" transaction between %s and %s",
entry, originItem.entry()));
}
if (originAction == Action.INSERT ||
originAction == Action.DELETE) {
throw incompatibleActionException(action, originAction);
} else {
Id subId = entry.subId();
Id originSubId = originItem.entry().subId();
assert subId != null;
if (subId == originSubId || subId.equals(originSubId)) {
iter.remove();
}
}
break;
case ELIMINATE:
if (originAction == Action.INSERT ||
originAction == Action.DELETE) {
throw incompatibleActionException(action, originAction);
} else {
Id subId = entry.subId();
Id originSubId = originItem.entry().subId();
assert subId != null;
if (subId == originSubId || subId.equals(originSubId)) {
iter.remove();
}
}
break;
default:
throw new AssertionError(String.format(
"Unknown mutate action: %s", action));
}
}
if (!ignoreCurrent) {
items.add(BackendAction.of(action, entry));
}
}
private static HugeException incompatibleActionException(
Action newAction,
Action originAction) {
return new HugeException("The action '%s' is incompatible with " +
"action '%s'", newAction, originAction);
}
/**
* Merges another mutation into this mutation. Ensures that all additions
* and deletions are added to this mutation. Does not remove duplicates
* if such exist - this needs to be ensured by the caller.
* @param mutation another mutation to be merged
*/
public void merge(BackendMutation mutation) {
E.checkNotNull(mutation, "mutation");
for (Iterator<BackendAction> it = mutation.mutation(); it.hasNext();) {
BackendAction item = it.next();
this.add(item.entry(), item.action());
}
}
public Set<HugeType> types() {
return this.updates.keys();
}
/**
* Get all mutations
* @return mutations
*/
public Iterator<BackendAction> mutation() {
return this.updates.values();
}
/**
* Get mutations by type
* @param type entry type
* @return mutations
*/
public Iterator<BackendAction> mutation(HugeType type) {
return this.updates.get(type);
}
/**
* Get mutations by type and id
* @param type entry type
* @param id entry id
* @return mutations
*/
public List<BackendAction> mutation(HugeType type, Id id) {
return this.updates.get(type, id);
}
/**
* Whether mutation contains entry and action
* @param entry entry
* @param action action
* @return true if exist, otherwise false
*/
public boolean contains(BackendEntry entry, Action action) {
List<BackendAction> items = this.updates.get(entry.type(), entry.id());
if (items == null || items.isEmpty()) {
return false;
}
for (BackendAction item : items) {
if (item.action().equals(action)) {
return true;
}
}
return false;
}
/**
* Whether mutation contains type and action
* @param type type
* @param action action
* @return true if exist, otherwise false
*/
public boolean contains(HugeType type, Action action) {
for (Iterator<BackendAction> i = this.updates.get(type); i.hasNext();) {
BackendAction entry = i.next();
if (entry.action() == action) {
return true;
}
}
return false;
}
/**
* Whether this mutation is empty
* @return true if empty, otherwise false
*/
public boolean isEmpty() {
return this.updates.size() == 0;
}
/**
* Get size of mutations
* @return size
*/
public int size() {
return this.updates.size();
}
public void clear() {
this.updates.clear();
}
@Override
public String toString() {
return String.format("BackendMutation{mutations=%s}", this.updates);
}
private static class MutationTable {
// Mapping type => id => mutations
private final Map<HugeType, Map<Id, List<BackendAction>>> mutations;
public MutationTable() {
// NOTE: ensure insert order
this.mutations = InsertionOrderUtil.newMap();
}
public MutationTable(int initialCapacity) {
// NOTE: ensure insert order
this.mutations = InsertionOrderUtil.newMap(initialCapacity);
}
public void put(HugeType type, Id id, BackendAction mutation) {
Map<Id, List<BackendAction>> table = this.mutations.get(type);
if (table == null) {
table = InsertionOrderUtil.newMap();
this.mutations.put(type, table);
}
List<BackendAction> items = table.get(id);
if (items == null) {
items = new ArrayList<>();
table.put(id, items);
}
items.add(mutation);
}
public boolean containsKey(HugeType type, Id id) {
Map<Id, List<BackendAction>> table = this.mutations.get(type);
return table != null && table.containsKey(id);
}
public List<BackendAction> get(HugeType type, Id id) {
Map<Id, List<BackendAction>> table = this.mutations.get(type);
if (table == null) {
return null;
}
return table.get(id);
}
public Iterator<BackendAction> get(HugeType type) {
ExtendableIterator<BackendAction> rs = new ExtendableIterator<>();
Map<Id, List<BackendAction>> table = this.mutations.get(type);
if (table != null) {
for (List<BackendAction> items : table.values()) {
rs.extend(items.iterator());
}
}
return rs;
}
public Set<HugeType> keys() {
return this.mutations.keySet();
}
public Iterator<BackendAction> values() {
ExtendableIterator<BackendAction> rs = new ExtendableIterator<>();
for (Map<Id, List<BackendAction>> table : this.mutations.values()) {
for (List<BackendAction> items : table.values()) {
rs.extend(items.iterator());
}
}
return rs;
}
public int size() {
int size = 0;
for (Map<Id, List<BackendAction>> m : this.mutations.values()) {
// NOTE: Index entry has same id with different subIds
for (List<BackendAction> actions : m.values()) {
size += actions.size();
}
}
return size;
}
public void clear() {
this.mutations.clear();
}
}
}