Skip to content

Commit

Permalink
repo-sqlbase: NestedContainerUpdateContext now used for deltas
Browse files Browse the repository at this point in the history
Previously the same context took care of updates in nested/embedded
containers, which seemed OK (similar ot query resolution), but using
new context has also the benefit that 1 context = 1 mapping.
  • Loading branch information
virgo47 committed Apr 27, 2021
1 parent 9c1cf06 commit 16d7c66
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 92 deletions.
Expand Up @@ -25,33 +25,29 @@
* If the modification has multi-part name then it resolves it to the last component first.
*
* This component is for delta processing what {@link ValueFilterProcessor} is for filters.
* Notable difference is that context and mapping are always related here, even for nested
* mappings on the same table we create new context which contains the right mapping, so we
* just track context changes here.
*/
public class DelegatingItemDeltaProcessor implements ItemDeltaProcessor {

/** Query context and mapping is not final as it can change during complex path resolution. */
/** Query context is not final as it can change during complex path resolution. */
private SqaleUpdateContext<?, ?, ?> context;
private QueryModelMapping<?, ?, ?> mapping;

public DelegatingItemDeltaProcessor(
SqaleUpdateContext<?, ?, ?> context, QueryModelMapping<?, ?, ?> mapping) {
public DelegatingItemDeltaProcessor(SqaleUpdateContext<?, ?, ?> context) {
this.context = context;
this.mapping = mapping;
}

@Override
public void process(ItemDelta<?, ?> modification) throws RepositoryException {
// TODO will we need various types of SqaleUpdateContext too?
// E.g. AccessCertificationWorkItemType is container inside container and to add/delete
// it we need to anchor the context in its parent, not in absolute root of update context.
// Similar situation is adding multi-value references to containers like assignments.

QName itemName = resolvePath(modification.getPath());
if (itemName == null) {
// This may indicate forgotten mapping, but normally it means that the item is simply
// not externalized and there is nothing to, update is only in fullObject.
return;
}

QueryModelMapping<?, ?, ?> mapping = context.mapping();
ItemSqlMapper<?, ?, ?> itemSqlMapper = mapping.getItemMapper(itemName);
if (itemSqlMapper instanceof SqaleItemSqlMapper) {
((SqaleItemSqlMapper<?, ?, ?>) itemSqlMapper)
Expand All @@ -72,6 +68,7 @@ private QName resolvePath(ItemPath path) {
ItemName firstName = path.firstName();
path = path.rest();

QueryModelMapping<?, ?, ?> mapping = context.mapping();
ItemRelationResolver<?, ?> relationResolver = mapping.getRelationResolver(firstName);
if (relationResolver == null) {
return null; // unmapped, not persisted, nothing to do
Expand All @@ -84,11 +81,8 @@ private QName resolvePath(ItemPath path) {
}

// we know nothing about context and resolver types, so we have to ignore it
@SuppressWarnings({ "rawtypes", "unchecked" })
SqaleItemRelationResolver.UpdateResolutionResult resolution =
((SqaleItemRelationResolver) relationResolver).resolve(context);
context = resolution.context;
mapping = resolution.mapping;
//noinspection unchecked,rawtypes
context = ((SqaleItemRelationResolver) relationResolver).resolve(context);
}
return path.asSingleName();
}
Expand Down
Expand Up @@ -25,6 +25,10 @@
* The class also declares more specific methods for applying values (add, replace, delete),
* because in some scenarios we work with items and not with item delta modifications anymore.
*
* Implementations populate updates contained in the {@link #context} using
* {@link SqaleUpdateContext#set} method (these will be executed later) or issue insert/delete
* statements which are executed immediately which is responsibility of the processor.
*
* @param <T> expected type of the real value for the modification (after optional conversion)
*/
public abstract class ItemDeltaValueProcessor<T> implements ItemDeltaProcessor {
Expand All @@ -43,13 +47,13 @@ public void process(ItemDelta<?, ?> modification) throws RepositoryException {
return;
}

// if it was replace, we don't get here, but add+delete can be used together
if (modification.isAdd()) {
addRealValues(modification.getRealValuesToAdd());
}
// if it was replace, we don't get here, but delete+add can be used together
if (modification.isDelete()) {
deleteRealValues(modification.getRealValuesToDelete());
}
if (modification.isAdd()) {
addRealValues(modification.getRealValuesToAdd());
}
}

/** Default conversion for one value is a mere type cast, override as necessary. */
Expand Down
Expand Up @@ -6,23 +6,26 @@
*/
package com.evolveum.midpoint.repo.sqale.mapping;

import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.repo.sqale.qmodel.SqaleNestedMapping;
import com.evolveum.midpoint.repo.sqale.update.NestedContainerUpdateContext;
import com.evolveum.midpoint.repo.sqale.update.SqaleUpdateContext;
import com.evolveum.midpoint.repo.sqlbase.SqlQueryContext;
import com.evolveum.midpoint.repo.sqlbase.mapping.QueryModelMapping;
import com.evolveum.midpoint.repo.sqlbase.querydsl.FlexibleRelationalPathBase;

/**
* Resolver that maps the nested items (next component of the path) to the same query type columns.
*
* @param <Q> query type of source entity (where the mapping is declared)
* @param <S> schema type for the nested container
* @param <Q> query type of entity where the mapping nested and declared
* @param <R> row type of {@link Q}
*/
public class NestedMappingResolver<Q extends FlexibleRelationalPathBase<R>, R>
public class NestedMappingResolver<S extends Containerable, Q extends FlexibleRelationalPathBase<R>, R>
implements SqaleItemRelationResolver<Q, R> {

private final QueryModelMapping<?, Q, R> mapping;
private final SqaleNestedMapping<S, Q, R> mapping;

public NestedMappingResolver(QueryModelMapping<?, Q, R> mapping) {
public NestedMappingResolver(SqaleNestedMapping<S, Q, R> mapping) {
this.mapping = mapping;
}

Expand All @@ -33,10 +36,7 @@ public ResolutionResult resolve(SqlQueryContext<?, Q, R> context) {
}

@Override
public UpdateResolutionResult resolve(SqaleUpdateContext<?, Q, R> context) {
// TODO this is OK unless we need to capture new schema object, e.g. nested metadata,
// in the context (SqaleUpdateContext#object). Row stays still the same, update clause
// doesn't change either, that's OK. Currently we're just losing type information.
return new UpdateResolutionResult(context, mapping);
public NestedContainerUpdateContext<S, Q, R> resolve(SqaleUpdateContext<?, Q, R> context) {
return new NestedContainerUpdateContext<>(context, mapping);
}
}
Expand Up @@ -8,7 +8,6 @@

import com.evolveum.midpoint.repo.sqale.update.SqaleUpdateContext;
import com.evolveum.midpoint.repo.sqlbase.mapping.ItemRelationResolver;
import com.evolveum.midpoint.repo.sqlbase.mapping.QueryModelMapping;
import com.evolveum.midpoint.repo.sqlbase.querydsl.FlexibleRelationalPathBase;

/**
Expand All @@ -20,20 +19,9 @@ public interface SqaleItemRelationResolver<Q extends FlexibleRelationalPathBase<
extends ItemRelationResolver<Q, R> {

/**
* Resolves current query context to {@link ResolutionResult} with new context and mapping.
* Resolves current query context to a new context (mapping is always part of context).
* The information about the resolved item is captured in the instance resolver already
* in a manner that is specific for various types of resolution (JOIN or nested mapping).
*/
UpdateResolutionResult resolve(SqaleUpdateContext<?, Q, R> context);

class UpdateResolutionResult {
public final SqaleUpdateContext<?, ?, ?> context;
public final QueryModelMapping<?, ?, ?> mapping;

public UpdateResolutionResult(
SqaleUpdateContext<?, ?, ?> context, QueryModelMapping<?, ?, ?> mapping) {
this.context = context;
this.mapping = mapping;
}
}
SqaleUpdateContext<?, ?, ?> resolve(SqaleUpdateContext<?, Q, R> context);
}
Expand Up @@ -61,13 +61,12 @@ public ResolutionResult resolve(SqlQueryContext<?, Q, R> context) {
}

@Override
public UpdateResolutionResult resolve(SqaleUpdateContext<?, Q, R> context) {
public SqaleUpdateContext<TS, TQ, TR> resolve(SqaleUpdateContext<?, Q, R> context) {
// TODO for query above we can hop to another table with join, still using SqlQueryContext
// (just a new instance), but right now SqaleUpdateContext is not built for that.
// Options - superclass? Common interface? Parametrized to Flexible... instead of QObject?

// return null; // TODO: now fails outside with NPE
return null; // TODO: now fails outside with NPE
// new ContainerTableDeltaProcessor<>
return new UpdateResolutionResult(context, targetMapping);
}
}
Expand Up @@ -11,20 +11,19 @@
import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.repo.sqale.qmodel.ref.QObjectReferenceMapping;
import com.evolveum.midpoint.repo.sqale.qmodel.ref.QReferenceMapping;
import com.evolveum.midpoint.repo.sqlbase.mapping.ItemSqlMapper;
import com.evolveum.midpoint.repo.sqlbase.mapping.QueryModelMapping;
import com.evolveum.midpoint.repo.sqlbase.querydsl.FlexibleRelationalPathBase;

/**
* Sqale implementation for nested mapping with support for sqale specific types.
* This allows for fluent calls of methods like {@link #addRefMapping(QName, QObjectReferenceMapping)}
* This allows for fluent calls of methods like {@link #addRefMapping}
* which depend on sqale-specific types like {@link QReferenceMapping} in this example.
* This extends from sqlbase {@link QueryModelMapping} because this is NOT whole table mapping
* so it can't extend from {@link SqaleTableMapping}.
*
* @param <S> schema type
* @param <S> schema type for the nested container
* @param <Q> type of entity path
* @param <R> row type related to the {@link Q}
*/
Expand Down
Expand Up @@ -13,6 +13,7 @@
import com.evolveum.midpoint.repo.sqale.qmodel.common.MContainer;
import com.evolveum.midpoint.repo.sqale.qmodel.common.QContainer;
import com.evolveum.midpoint.repo.sqlbase.JdbcSession;
import com.evolveum.midpoint.repo.sqlbase.mapping.QueryModelMapping;

/**
* Update context for owned containers stored in tables.
Expand All @@ -24,20 +25,19 @@
* @param <S> schema type of the object stored in the owned (child) table
* @param <Q> type of entity path for the owned (child) table
* @param <R> row type related to the {@link Q}
* TODO: other params
* @param <OR> owner row type
*/
// TODO rename to ContainerTableUpdateContext
public class ContainerUpdateContext<S extends Containerable, Q extends QContainer<R, OR>, R extends MContainer, OR>
public class ContainerTableUpdateContext<S extends Containerable, Q extends QContainer<R, OR>, R extends MContainer, OR>
extends SqaleUpdateContext<S, Q, R> {

private final Q path;
private final SQLUpdateClause update;

public ContainerUpdateContext(SqaleUpdateContext<?, ?, OR> parentContext,
JdbcSession jdbcSession, S object, R row) {
super(parentContext, object, row);
public ContainerTableUpdateContext(SqaleUpdateContext<?, ?, OR> parentContext,
JdbcSession jdbcSession, R row) {
super(parentContext, row);

path = mapping.defaultAlias();
path = null; // TODO mapping.defaultAlias(); mapping missing
// we create the update, but only use it if set methods are used
update = jdbcSession.newUpdate(path)
.where(path.isOwnedBy(parentContext.row()));
Expand All @@ -47,12 +47,9 @@ public Q path() {
return path;
}

/** Executes updates if applicable, nothing is done if set methods were not used. */
// TODO mechanism to root context to call this recursively for the whole tree of contexts
public void execute() {
if (!update.isEmpty()) {
update.execute();
}
@Override
public QueryModelMapping<S, Q, R> mapping() {
return null; // TODO
}

public SQLUpdateClause update() {
Expand All @@ -62,4 +59,13 @@ public SQLUpdateClause update() {
public <P extends Path<T>, T> void set(P path, T value) {
update.set(path, value);
}

/** Executes updates if applicable, nothing is done if set methods were not used. */
@Override
protected void finishExecutionOwn() {
System.out.println("ContainerTableUpdateContext EXECUTE");
if (!update.isEmpty()) {
update.execute();
}
}
}
Expand Up @@ -9,6 +9,8 @@
import com.querydsl.core.types.Path;

import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.repo.sqale.qmodel.SqaleNestedMapping;
import com.evolveum.midpoint.repo.sqlbase.mapping.QueryModelMapping;
import com.evolveum.midpoint.repo.sqlbase.querydsl.FlexibleRelationalPathBase;

/**
Expand All @@ -21,20 +23,35 @@
public class NestedContainerUpdateContext<S extends Containerable, Q extends FlexibleRelationalPathBase<R>, R>
extends SqaleUpdateContext<S, Q, R> {

private final SqaleNestedMapping<S, Q, R> mapping;

public NestedContainerUpdateContext(
SqaleUpdateContext<?, Q, R> parentContext, S object, R rootRow) {
super(parentContext, object, rootRow);
SqaleUpdateContext<?, Q, R> parentContext,
SqaleNestedMapping<S, Q, R> mapping) {
super(parentContext, parentContext.row);

// TODO
this.mapping = mapping;
}

@Override
public Q path() {
return null; // TODO
//noinspection unchecked
return (Q) parentContext.path();
}

@Override
public QueryModelMapping<S, Q, R> mapping() {
return mapping;
}

@Override
public <P extends Path<T>, T> void set(P path, T value) {
// TODO delegate to parent
parentContext.set(path, value);
}

@Override
protected void finishExecutionOwn() {
// nothing to do, parent context has all the updates
System.out.println("NestedContainerUpdateContext EXECUTE");
}
}
Expand Up @@ -26,6 +26,7 @@
import com.evolveum.midpoint.repo.sqale.qmodel.object.QObject;
import com.evolveum.midpoint.repo.sqlbase.JdbcSession;
import com.evolveum.midpoint.repo.sqlbase.RepositoryException;
import com.evolveum.midpoint.repo.sqlbase.mapping.QueryTableMapping;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
Expand All @@ -41,6 +42,8 @@
public class RootUpdateContext<S extends ObjectType, Q extends QObject<R>, R extends MObject>
extends SqaleUpdateContext<S, Q, R> {

private final S object;
protected final QueryTableMapping<S, Q, R> mapping;
private final Q rootPath;
private final SQLUpdateClause update;
private final int objectVersion;
Expand All @@ -49,11 +52,11 @@ public class RootUpdateContext<S extends ObjectType, Q extends QObject<R>, R ext

public RootUpdateContext(SqaleTransformerSupport transformerSupport,
JdbcSession jdbcSession, S object, R rootRow) {
super(transformerSupport,
transformerSupport.sqlRepoContext()
.getMappingBySchemaType(SqaleUtils.getClass(object)),
jdbcSession, object, rootRow);
super(transformerSupport, jdbcSession, rootRow);

this.object = object;
mapping = transformerSupport.sqlRepoContext()
.getMappingBySchemaType(SqaleUtils.getClass(object));
rootPath = mapping.defaultAlias();
objectVersion = objectVersionAsInt(object);
// root context always updates, at least version and full object, so we can create it early
Expand All @@ -66,6 +69,11 @@ public Q path() {
return rootPath;
}

@Override
public QueryTableMapping<S, Q, R> mapping() {
return mapping;
}

/** Applies modifications, executes necessary updates and returns narrowed modifications. */
public Collection<? extends ItemDelta<?, ?>> execute(
Collection<? extends ItemDelta<?, ?>> modifications)
Expand Down Expand Up @@ -105,16 +113,15 @@ private void processModification(ItemDelta<?, ?> modification)
cidGenerator.processModification(modification);
modification.applyTo(getPrismObject());

new DelegatingItemDeltaProcessor(this, mapping)
.process(modification);
new DelegatingItemDeltaProcessor(this).process(modification);
}

/**
* Executes all necessary SQL updates (including sub-entity inserts/deletes)
* for the enclosed {@link #object}.
* This also increments the version information and serializes `fullObject`.
*/
public void finishExecution() throws SchemaException, RepositoryException {
protected void finishExecutionOwn() throws SchemaException, RepositoryException {
int newVersion = objectVersionAsInt(object) + 1;
object.setVersion(String.valueOf(newVersion));
update.set(rootPath.version, newVersion);
Expand Down

0 comments on commit 16d7c66

Please sign in to comment.