Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAUSEWAY-3779 Bulk fetch Entities by ID (experimental branch) #2507

Draft
wants to merge 1 commit into
base: v3
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.function.Predicate;

import org.apache.causeway.applib.services.repository.RepositoryService;
import org.apache.causeway.commons.collections.Can;

import lombok.NonNull;

Expand Down Expand Up @@ -69,15 +70,15 @@ public interface Query<T> extends Serializable {

Query<T> withRange(@NonNull QueryRange range);

default Query<T> withRange(long ...range) {
default Query<T> withRange(final long ...range) {
return withRange(QueryRange.of(range));
}

default Query<T> withStart(long start) {
default Query<T> withStart(final long start) {
return withRange(start);
}

default Query<T> withLimit(long limit) {
default Query<T> withLimit(final long limit) {
return withRange(0L, limit);
}

Expand All @@ -94,4 +95,16 @@ static <T> NamedQuery<T> named(
return new _NamedQueryDefault<>(resultType, queryName, QueryRange.unconstrained(), null);
}

static <T> SelectByIdQuery<T> selectByIdQuery(
final @NonNull Class<T> resultType,
final @NonNull String ... idsStringified) {
return selectByIdQuery(resultType, Can.ofArray(idsStringified));
}

static <T> SelectByIdQuery<T> selectByIdQuery(
final @NonNull Class<T> resultType,
final @NonNull Can<String> idsStringified) {
return new _SelectByIdQueryDefault<>(resultType, idsStringified, QueryRange.unconstrained());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.causeway.applib.query;

import org.apache.causeway.commons.collections.Can;

/**
* @since 2.1, 3.1 {@index}
*/
public interface SelectByIdQuery<T> extends Query<T> {

Can<String> getIdsStringified();

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@

import lombok.NonNull;

final class _AllInstancesQueryDefault<T>
extends _QueryAbstract<T>
final class _AllInstancesQueryDefault<T>
extends _QueryAbstract<T>
implements AllInstancesQuery<T> {

private static final long serialVersionUID = 1L;

protected _AllInstancesQueryDefault(
final @NonNull Class<T> type,
final @NonNull Class<T> type,
final @NonNull QueryRange range) {
super(type, range);
}
Expand All @@ -38,11 +38,10 @@ public String getDescription() {
}

// -- WITHERS

@Override
public _AllInstancesQueryDefault<T> withRange(final @NonNull QueryRange range) {
return new _AllInstancesQueryDefault<>(getResultType(), range);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.causeway.applib.query;

import org.apache.causeway.commons.collections.Can;

import lombok.Getter;
import lombok.NonNull;

final class _SelectByIdQueryDefault<T>
extends _QueryAbstract<T>
implements SelectByIdQuery<T> {

private static final long serialVersionUID = 1L;

@Getter
private final @NonNull Can<String> idsStringified;

_SelectByIdQueryDefault(
final @NonNull Class<T> type,
final @NonNull Can<String> idsStringified,
final @NonNull QueryRange range) {
super(type, range);
this.idsStringified = idsStringified;
}

@Override
public String getDescription() {
return getResultType().getName() + " (select by id)";
}

// -- WITHERS

@Override
public _SelectByIdQueryDefault<T> withRange(final @NonNull QueryRange range) {
return new _SelectByIdQueryDefault<>(getResultType(), idsStringified, range);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,21 @@ public static StopWatch run(final Runnable runnable) {
return watch.stop();
}

public static void runVerbose(final String label, final Runnable runnable) {
System.out.println(String.format(Locale.US, "Entering call '%s'", label));
final StopWatch watch = run(runnable);
System.out.println(String.format(Locale.US, "Running '%s' took %d ms", label, watch.getMillis()));
}

public static <T> T callVerbose(final String label, final Supplier<T> callable) {
System.out.println(String.format(Locale.US, "Entering call '%s'", label));
final StopWatch watch = now();
T result = callable.get();
watch.stop();
System.out.println(String.format(Locale.US, "Calling '%s' took %d ms", label, watch.getMillis()));
return result;
}

public static void runVerbose(final Logger log, final String label, final Runnable runnable) {
final StopWatch watch = run(runnable);
log.info(String.format(Locale.US, "Running '%s' took %d ms", label, watch.getMillis()));
Expand All @@ -147,7 +162,4 @@ public static <T> T callVerbose(final Logger log, final String label, final Supp
return result;
}




}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.io.Serializable;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
Expand All @@ -35,6 +36,7 @@
import org.apache.causeway.commons.binding.Bindable;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.functional.IndexedFunction;
import org.apache.causeway.commons.internal.base._Timing;
import org.apache.causeway.commons.internal.binding._BindableAbstract;
import org.apache.causeway.commons.internal.binding._Bindables;
import org.apache.causeway.commons.internal.binding._Observables;
Expand Down Expand Up @@ -146,22 +148,22 @@ private DataTableInteractive(
//.filter(this::ignoreHidden) // I believe is redundant, has major performance impact
);

this.dataRows = _Observables.lazy(()->
this.dataRows = _Observables.lazy(()->_Timing.callVerbose("dataRows.get()", ()->
dataElements.getValue().stream()
.map(IndexedFunction.zeroBased((rowIndex, element)->new DataRow(rowIndex, this, element, tokens(element))))
.collect(Can.toCan()));
.collect(Can.toCan())));

this.dataRowsFilteredAndSorted = _Observables.lazy(()->
this.dataRowsFilteredAndSorted = _Observables.lazy(()->_Timing.callVerbose("dataRowsFilteredAndSorted.get()", ()->
dataRows.getValue().stream()
.filter(adaptSearchPredicate())
.sorted(sortingComparator()
.orElseGet(()->(a, b)->0)) // else don't sort (no-op comparator for streams)
.collect(Can.toCan()));
.collect(Can.toCan())));

this.dataRowsSelected = _Observables.lazy(()->
this.dataRowsSelected = _Observables.lazy(()->_Timing.callVerbose("dataRowsSelected.get()", ()->
dataRows.getValue().stream()
.filter(dataRow->dataRow.getSelectToggle().getValue().booleanValue())
.collect(Can.toCan()));
.collect(Can.toCan())));

this.selectionChanges = _Bindables.forValue(Boolean.FALSE);
this.selectAllToggle = _Bindables.forValue(Boolean.FALSE);
Expand Down Expand Up @@ -374,6 +376,16 @@ public DataTable export() {
.collect(Can.toCan()));
}

@NonNull
public Iterator<DataRow> getDataRowsFilteredAndSorted(final long skip, final long limit) {
var stopWatch = _Timing.now();
var iterator = dataRowsFilteredAndSorted.getValue()
.iterator(Math.toIntExact(skip), Math.toIntExact(limit));
System.err.printf("get iterator took %s%n", stopWatch);
stopWatch.stop();
return iterator;
}

// used internally for serialization
private DataTable exportAll() {
return new DataTable(
Expand Down Expand Up @@ -406,12 +418,19 @@ public static class Memento implements Serializable {
static Memento create(
final @NonNull DataTableInteractive tableInteractive) {

return new Memento(
var stopWatch = _Timing.now();

var memento = new Memento(
tableInteractive.managedMember.getIdentifier(),
tableInteractive.where,
tableInteractive.exportAll(),
tableInteractive.searchArgument.getValue(),
tableInteractive.getSelectedRowIndexes());

stopWatch.stop();
System.err.printf("table memento created, took %s%n", stopWatch);

return memento;
}

private final @NonNull Identifier featureId;
Expand All @@ -432,6 +451,8 @@ public DataTableInteractive getDataTableModel(final ManagedObject owner) {
throw _Exceptions.illegalArgument("cannot recreate from memento for deleted object");
}

var stopWatch = _Timing.now();

val memberId = featureId.getMemberLogicalName();

final ManagedMember managedMember = featureId.getType().isPropertyOrCollection()
Expand All @@ -456,6 +477,10 @@ public DataTableInteractive getDataTableModel(final ManagedObject owner) {
.filter(dataRow->selectedRowIndexes.contains(dataRow.getRowIndex()))
.forEach(dataRow->dataRow.getSelectToggle().setValue(true));
});

stopWatch.stop();
System.err.printf("table restored from memento, took %s%n", stopWatch);

return dataTableInteractive;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.apache.causeway.core.metamodel.context.MetaModelContext;
import org.apache.causeway.core.metamodel.object.ManagedObject;
import org.apache.causeway.core.metamodel.objectmanager.ObjectBulkLoader;
import org.apache.causeway.core.metamodel.objectmanager.ObjectBulkLoader.Request;
import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
import org.apache.causeway.core.metamodel.spec.feature.MixedIn;
Expand Down Expand Up @@ -348,9 +349,20 @@ private Object readResolve() {
var elementType = MetaModelContext.instanceElseFail().specForTypeElseFail(elementTypeClass);
var dataTable = new DataTable(elementType, columnIds
.map(columnId->elementType.getAssociationElseFail(columnId, MixedIn.INCLUDED)));
var rowElements = rowElementBookmarks.map(objectManager::loadObjectElseFail);
dataTable.setDataElements(rowElements);
dataTable.tableFriendlyName = tableFriendlyName;

if(elementType.isEntity()) {
//TODO[CAUSEWAY-3779] instead of using the common element type we need to use to actual types
//(use a multivalued map by type)
final Can<String> idsStringified = rowElementBookmarks.map(Bookmark::getIdentifier);
var rowElements = objectManager.getObjectBulkLoader()
.loadObject(Request.of(elementType,
Query.selectByIdQuery(elementType.getCorrespondingClass(), idsStringified)));
dataTable.setDataElements(rowElements);
} else {
var rowElements = rowElementBookmarks.map(objectManager::loadObjectElseFail);
dataTable.setDataElements(rowElements);
}
dataTable.tableFriendlyName = this.tableFriendlyName;
return dataTable;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.apache.causeway.applib.query.AllInstancesQuery;
import org.apache.causeway.applib.query.NamedQuery;
import org.apache.causeway.applib.query.Query;
import org.apache.causeway.applib.query.SelectByIdQuery;
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.services.exceprecog.Category;
import org.apache.causeway.applib.services.exceprecog.ExceptionRecognizerService;
Expand Down Expand Up @@ -250,6 +251,24 @@ public Can<ManagedObject> fetchByQuery(final Query<?> query) {

return resultList;

} else if(query instanceof SelectByIdQuery) {

//TODO[CAUSEWAY-3779] implement select by

val queryById = (SelectByIdQuery<?>) query;
val queryEntityType = queryById.getResultType();
val idsStringified = queryById.getIdsStringified();

// guard against misuse
_Assert.assertTypeIsInstanceOf(queryEntityType, entityClass);

val pm = getPersistenceManager();

val q = pm.newQuery(queryEntityType, ":p.contains(id)");

val resultList = fetchWithinTransaction(()->(List)q.execute(idsStringified.toList()));
return resultList;

} else if(query instanceof NamedQuery) {

val applibNamedQuery = (NamedQuery<?>) query;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.causeway.applib.query.AllInstancesQuery;
import org.apache.causeway.applib.query.NamedQuery;
import org.apache.causeway.applib.query.Query;
import org.apache.causeway.applib.query.SelectByIdQuery;
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.services.repository.EntityState;
import org.apache.causeway.commons.collections.Can;
Expand Down Expand Up @@ -154,7 +155,12 @@ public Can<ManagedObject> fetchByQuery(final Query<?> query) {
typedQuery.getResultStream()
.map(entity -> ManagedObject.adaptSingular(entitySpec, entity)));

} else if (query instanceof NamedQuery) {
} else if(query instanceof SelectByIdQuery) {

//TODO[CAUSEWAY-3779] implement select by

} else if(query instanceof NamedQuery) {


val applibNamedQuery = (NamedQuery<?>) query;
val queryResultType = applibNamedQuery.getResultType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ public Iterator<DataRow> iterator(final long skip, final long limit) {
.iterator(Math.toIntExact(skip), Math.toIntExact(limit));
}

//TODO[CAUSEWAY-3779] idea
// @Override
// public Iterator<DataRow> iterator(final long skip, final long limit) {
// var dataTable = getDataTableModel();
// // honor (single) column sort (if any)
// dataTable.getColumnSort().setValue(columnSort().orElse(null));
// return dataTable.getDataRowsFilteredAndSorted(Math.toIntExact(skip), Math.toIntExact(limit));
// }

// -- HELPER

private Optional<DataTableInteractive.ColumnSort> columnSort() {
Expand Down