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

io.permazen.core.Schema Maven / Gradle / Ivy

Go to download

Permazen core API classes which provide objects, fields, indexes, queries, and schema management on top of a key/value store.

The newest version!

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.core;

import com.google.common.base.Preconditions;

import io.permazen.encoding.EncodingRegistry;
import io.permazen.kv.KeyRange;
import io.permazen.kv.KeyRanges;
import io.permazen.schema.SchemaId;
import io.permazen.schema.SchemaModel;
import io.permazen.schema.SchemaObjectType;

import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;

/**
 * Reflects a {@link SchemaModel} as recorded in a {@link Database} as seen by a particular {@link Transaction}.
 *
 * 

* Instances are immutable and thread safe. */ public class Schema { private final int schemaIndex; // zero only if schema is empty private final SchemaModel schemaModel; private final SchemaBundle schemaBundle; private final TreeMap objTypesByName = new TreeMap<>(); private final HashMap objTypesByStorageId = new HashMap<>(); // Maps each DeleteAction to a map from reference field to key ranges of object types that have the field with that action private final EnumMap> deleteActionKeyRanges = new EnumMap<>(DeleteAction.class); // Constructor // Create a non-empty schema Schema(SchemaBundle schemaBundle, int schemaIndex, SchemaModel schemaModel) { // Sanity check Preconditions.checkArgument(schemaBundle != null, "null schemaBundle"); Preconditions.checkArgument(schemaModel != null, "null schemaModel"); Preconditions.checkArgument(schemaIndex > 0, "non-positive schemaIndex"); Preconditions.checkArgument(!schemaModel.isEmpty(), "empty schema model"); Preconditions.checkArgument(schemaModel.isLockedDown(true), "schemaModel not locked down"); schemaModel.validate(); // this should already be done so this should be quick // Initialize fields this.schemaIndex = schemaIndex; this.schemaModel = schemaModel; this.schemaBundle = schemaBundle; // Create object types but don't initialize them yet for (SchemaObjectType schemaType : this.schemaModel.getSchemaObjectTypes().values()) { final ObjType objType = new ObjType(this, schemaType); assert !this.objTypesByName.containsKey(objType.getName()); assert !this.objTypesByStorageId.containsKey(objType.getStorageId()); this.objTypesByName.put(objType.getName(), objType); this.objTypesByStorageId.put(objType.getStorageId(), objType); } } // Create an empty schema Schema(SchemaBundle schemaBundle) { Preconditions.checkArgument(schemaBundle != null, "null schemaBundle"); this.schemaIndex = 0; this.schemaBundle = schemaBundle; this.schemaModel = new SchemaModel(); this.schemaModel.lockDown(true); } // Initialization void initialize(EncodingRegistry encodingRegistry) { // Sanity check Preconditions.checkArgument(encodingRegistry != null, "null encodingRegistry"); // Initialize object types for (SchemaObjectType schemaType : this.schemaModel.getSchemaObjectTypes().values()) { final ObjType objType = this.objTypesByName.get(schemaType.getName()); objType.initialize(schemaType, encodingRegistry); } // This is an optimization for handling DeleteAction. Because the same reference field can be configured with // a different DeleteAction in different object types, which can come from the same or different schemas, // when an object is deleted, we have to be careful to only apply DeleteAction's matching the schema and object // type of the referring fields. What follows builds a data structure to optimize finding them at runtime. // First calculate, for each reference field, KeyRanges covering all object types that contain the field final HashMap allObjTypesKeyRangesMap = new HashMap<>(); this.objTypesByStorageId.forEach((storageId, objType) -> { final KeyRange objTypeKeyRange = ObjId.getKeyRange((int)storageId); for (ReferenceField field : objType.referenceFieldsAndSubFields.values()) allObjTypesKeyRangesMap.computeIfAbsent(field, f -> KeyRanges.empty()).add(objTypeKeyRange); }); // Now do the same thing again, but broken out by the reference fields' configured DeleteAction for (DeleteAction deleteAction : DeleteAction.values()) { // Find reference fields matching the DeleteAction final HashMap fieldKeyRanges = new HashMap<>(); this.objTypesByStorageId.forEach((storageId, objType) -> { final KeyRange objTypeKeyRange = ObjId.getKeyRange((int)storageId); for (ReferenceField field : objType.referenceFieldsAndSubFields.values()) { if (field.inverseDelete.equals(deleteAction)) fieldKeyRanges.computeIfAbsent(field, i -> KeyRanges.empty()).add(objTypeKeyRange); } }); // If for any field, the field is configured the same way in every object type, then no need to restrict by object type fieldKeyRanges.entrySet().forEach(entry -> { if (entry.getValue().equals(allObjTypesKeyRangesMap.get(entry.getKey()))) entry.setValue(null); }); // Done this.deleteActionKeyRanges.put(deleteAction, fieldKeyRanges); } } // Public Methods /** * Determine whether this schema is empty, i.e., contains zero object types. * *

* Empty schemas are not recorded in the database. * * @return true if empty, otherwise false */ public boolean isEmpty() { return this.schemaIndex == 0; } /** * Get the schema bundle that this instance is a member of. * * @return the containing schema bundle */ public SchemaBundle getSchemaBundle() { return this.schemaBundle; } /** * Get the schema index associated with this instance. * * @return schema index, or zero if this is the empty schema */ public int getSchemaIndex() { return this.schemaIndex; } /** * Get the schema ID associated with this instance. * * @return schema ID */ public SchemaId getSchemaId() { return this.schemaModel.getSchemaId(); } /** * Get the original {@link SchemaModel} on which this instance is based as it is recorded in the database. * *

* Equivalent to: {@link getSchemaModel(boolean)}{@code (false)}. * * @return schema model */ public SchemaModel getSchemaModel() { return this.getSchemaModel(false); } /** * Get the {@link SchemaModel} on which this instance is based, optionally including explicit storage ID assignments. * *

* If {@code withStorageIds} is true, then all of the schema items in the returned model will * have non-zero storage ID's reflecting their storage ID assignments in the database. * * @param withStorageIds true to include all storage ID assignments, false for the original model * @return schema model with storage ID assignments */ public SchemaModel getSchemaModel(boolean withStorageIds) { if (!withStorageIds) return this.schemaModel; final SchemaModel model = this.schemaModel.clone(); model.lockDown(false); model.visitSchemaItems(item -> item.setStorageId(this.schemaBundle.getStorageId(item.getSchemaId()))); model.lockDown(true); return model; } /** * Get all of the {@link ObjType}s that constitute this schema, indexed by type name. * * @return unmodifiable mapping from {@linkplain ObjType#getName type name} to {@link ObjType} */ public NavigableMap getObjTypes() { return Collections.unmodifiableNavigableMap(this.objTypesByName); } /** * Get the {@link ObjType} in this schema with the given name. * * @param typeName object type name * @return the corresponding {@link ObjType} * @throws UnknownTypeException if no such {@link ObjType} exists * @throws IllegalArgumentException if {@code typeName} is null */ public ObjType getObjType(String typeName) { Preconditions.checkArgument(typeName != null, "null typeName"); final ObjType objType = this.objTypesByName.get(typeName); if (objType == null) throw new UnknownTypeException(typeName, this); return objType; } /** * Get the {@link ObjType} in this schema with the given storage ID. * * @param storageId object type storage ID * @return the corresponding {@link ObjType} * @throws UnknownTypeException if no such {@link ObjType} exists * @throws IllegalArgumentException if {@code storageId} is invalid */ public ObjType getObjType(int storageId) { Preconditions.checkArgument(storageId > 0, "invalid storageId"); final ObjType objType = this.objTypesByStorageId.get(storageId); if (objType == null) throw new UnknownTypeException(String.format("#%d", storageId), this); return objType; } // Object @Override public String toString() { return String.format("%s@%d", this.schemaModel.getSchemaId(), this.schemaIndex); } // Package Methods EnumMap> getDeleteActionKeyRanges() { return this.deleteActionKeyRanges; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy