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

Located Triples #1351

Draft
wants to merge 111 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
84a4bdf
Code for locating triples in an existing index
Jun 9, 2023
d8781a4
Add test for the `locatedTriple` method + address some of the comment…
Jun 10, 2023
258231d
Improve the `locateTriple` test (three relations instead of just one)
Jun 10, 2023
34647fa
Merge current master and get everything to run again
Feb 10, 2024
59aae8e
A few minor improvements
Feb 13, 2024
0754933
Merge branch 'master' into located-triples
Qup42 Apr 29, 2024
a37f5c3
Delete some comment reformatting
Qup42 Apr 29, 2024
b394d89
Initial adaption of LocatedTriples
Qup42 Apr 29, 2024
524cc22
Update comments
Qup42 Apr 30, 2024
295961d
Use proper objects in tests
Qup42 Apr 30, 2024
70dc617
Simply triple location
Qup42 Apr 30, 2024
702fdc7
Upgrade LocatedTriples::numTriples to ScanSpecification
Qup42 May 13, 2024
8d11581
Upgrade LocatedTriples::mergeTriples to ScanSpecification
Qup42 May 13, 2024
dd32606
Some cleanup
Qup42 May 13, 2024
fd28fb1
Update docstrings for ScanSpecification
Qup42 May 13, 2024
f975039
Merge branch 'refs/heads/master' into located-triples-3
Qup42 May 17, 2024
f07251e
Extract SparqlTripleSimple to separate file
Qup42 May 18, 2024
61b01ed
Parse Update Queries provisionally into ParsedQuery
Qup42 May 18, 2024
2527cfe
Calculate triple sets for update queries
Qup42 May 18, 2024
f738b40
Start locating triples
Qup42 May 19, 2024
cfc581e
Add benchmarking code
Qup42 May 20, 2024
b0bcae7
format
Qup42 May 20, 2024
3f177ea
Merge branch 'master' into located-triples-3
Qup42 May 27, 2024
33a5f3e
Adapt for merged changes
Qup42 May 27, 2024
25656db
Merge branch 'master' into located-triples-3
Qup42 Jun 5, 2024
aa6e8a5
Update SparqlAntlrParser Tests
Qup42 Jun 5, 2024
5275304
Provide runtime information for updates
Qup42 Jun 6, 2024
b09872c
Remove obsolete cmake add_library entry
Qup42 Jun 6, 2024
80aa974
Add multiple LocatedTriples to LocatedTriplesPerBlock
Qup42 Jun 6, 2024
49b9226
Set ParsedQuery::_originalString on Updates
Qup42 Jun 6, 2024
ea328bf
First update POC
Qup42 Jun 10, 2024
68972ef
Merge branch 'master' into located-triples-3
Qup42 Jun 10, 2024
3aec517
Work
Qup42 Jun 11, 2024
21af4c2
Some more cleanup and fixes
Qup42 Jun 11, 2024
aa42c68
More simplification and cleanup
Qup42 Jun 12, 2024
252a627
Drop `ScanSpec` from `LocatedTriples::numTriples` and update tests
Qup42 Jun 12, 2024
69a9173
Use `LocalVocab` from `DeltaTriples`
Qup42 Jun 12, 2024
cc9ee22
Use `IdTriple` in `LocatedTriple`
Qup42 Jun 12, 2024
267eb87
Cleanup and a tiny bit refactoring
Qup42 Jun 12, 2024
1cdca25
Improve
Qup42 Jun 12, 2024
4d1b7b0
Improve tests
Qup42 Jun 12, 2024
55eab54
Merge all da triples
Qup42 Jun 12, 2024
75fce36
Move adding Update Triples out of `decompressBlock`
Qup42 Jun 13, 2024
28e62ea
Add Test that ensure that already existing triples are not added
Qup42 Jun 13, 2024
60e7ed2
Iron out another edge-case
Qup42 Jun 13, 2024
a3007c6
Move adding Update Triples out of `decompressBlockToExistingIdTable`
Qup42 Jun 13, 2024
e71d315
Use correct iterator
Qup42 Jun 13, 2024
c98c828
Add switch to disable/enable use of update triples
Qup42 Jun 13, 2024
cd9a969
Update comments
Qup42 Jun 14, 2024
ddcf2b3
Merge branch 'master' into located-triples-3
Qup42 Jun 14, 2024
17a4ced
Delete dead code
Qup42 Jun 14, 2024
86e46a7
Fix spelling errors
Qup42 Jun 14, 2024
1cd0c47
Remove endl from toString of PermutedTriple
Qup42 Jun 14, 2024
d04e6b6
Refactor blockBeginOffset
Qup42 Jun 14, 2024
a6f858d
Remove debug prints
Qup42 Jun 14, 2024
09125fe
Move printing function declarations to better place
Qup42 Jun 14, 2024
d6675ee
Mute warning in test
Qup42 Jun 14, 2024
ef7373b
Fix
Qup42 Jun 14, 2024
93f3284
Delete dead code from tests
Qup42 Jun 14, 2024
9a903db
Add all printing functions used in tests
Qup42 Jun 14, 2024
8a5176f
Improve server code
Qup42 Jun 14, 2024
ebb15b0
Fix some code style issues
Qup42 Jun 15, 2024
af18d38
Defeat index building for testing
Qup42 Jun 16, 2024
9048ed0
Little cleanup
Qup42 Jun 16, 2024
57df48d
Revert usage of std::format
Qup42 Jun 16, 2024
6292f07
Add missing explicit instantiations
Qup42 Jun 16, 2024
a4d4281
Add support for testing triple location in all permutations
Qup42 Jun 16, 2024
bea748d
sonarcloud
Qup42 Jun 16, 2024
900d9a2
Mute aggressive warnings
Qup42 Jun 17, 2024
9a7abc6
Convert some `LocatedTriples` tests to Matchers
Qup42 Jun 17, 2024
b2841c9
Fix a bug in triple counting of `LocatedTriplesPerBlock`
Qup42 Jun 17, 2024
84518fd
Extend `LocatedTriplesTest::numTriplesInBlock`
Qup42 Jun 17, 2024
e5f1f84
Extend `LocatedTriplesTest::mergeTriples`
Qup42 Jun 17, 2024
9641e15
Add field with all `Permutation::Enum` values to `Permutation`
Qup42 Jun 18, 2024
2060709
Work on DeltaTriples
Qup42 Jun 18, 2024
f4abf7f
Use the cancellationHandle more
Qup42 Jun 18, 2024
ff36919
Update copyright headers
Qup42 Jun 18, 2024
b39d3da
Rename `LocatedTripleHandles` fields to convention
Qup42 Jun 18, 2024
fbcb040
Work on LocatedTriples
Qup42 Jun 18, 2024
6dd7e19
Fix bug in LocatedTriples
Qup42 Jun 19, 2024
75b5cd6
Add explicit type for std::ranges::count_if result
Qup42 Jun 19, 2024
e9a4f7a
Change pretty printing functions to hidden friend
Qup42 Jun 19, 2024
acd0e07
Pass numberIndexColumns into merge triples
Qup42 Jun 19, 2024
57364ec
Negative tests
Qup42 Jun 19, 2024
1d4f50b
CodeReview
Qup42 Jun 20, 2024
fcdeb8c
Have printing function as class members in the header
Qup42 Jun 21, 2024
72a930b
Adapt to CodeReview changes
Qup42 Jun 21, 2024
5a5304d
Make `IdTriple` its own type
Qup42 Jun 21, 2024
940f52a
Remove no longer used pretty printer
Qup42 Jun 21, 2024
8e86de6
DeltaTriples
Qup42 Jun 21, 2024
10aacef
More concrete print function
Qup42 Jun 21, 2024
25166af
Simply tripleLocation test
Qup42 Jun 21, 2024
318b6af
Commit
Qup42 Jun 21, 2024
7b0ed64
Fix segfault
Qup42 Jun 21, 2024
0b5d903
Fix
Qup42 Jun 21, 2024
f7c3ca4
Code Review
Qup42 Jun 21, 2024
19a0266
Code Review
Qup42 Jun 24, 2024
9eb603b
Code Review
Qup42 Jun 24, 2024
6816deb
Template `numIndex` in `mergeTriples`
Qup42 Jun 24, 2024
237e7f3
Use implicit copy constructor
Qup42 Jun 24, 2024
fc7b8c4
Mark print function's purpose
Qup42 Jun 24, 2024
e7a1543
Improvements
Qup42 Jun 25, 2024
f466785
Improvements
Qup42 Jun 26, 2024
73da7ed
Fix bug in `getDistinctColIdsAndCountsImpl`
Qup42 Jun 26, 2024
8ef8a0d
Complete index columns when reading blocks to enable updates
Qup42 Jun 26, 2024
abb9941
Consider updated block borders
Qup42 Jun 27, 2024
3bcd238
Fix updated block borders
Qup42 Jun 29, 2024
3f7073a
Add sentinel block after last real block for updates
Qup42 Jun 29, 2024
dfae87b
Remove obsolete code
Qup42 Jun 29, 2024
a7a74df
Add some debug prints
Qup42 Jun 29, 2024
8202044
Eliminate duplicate triples
Qup42 Jun 29, 2024
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
145 changes: 145 additions & 0 deletions src/engine/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
#include "engine/ExportQueryExecutionTrees.h"
#include "engine/QueryPlanner.h"
#include "global/RuntimeParameters.h"
#include "index/IndexImpl.h"
#include "index/LocatedTriples.h"
#include "util/AsioHelpers.h"
#include "util/CancellationHandle.h"
#include "util/MemorySize/MemorySize.h"
#include "util/OnDestructionDontThrowDuringStackUnwinding.h"
#include "util/ParseableDuration.h"
Expand Down Expand Up @@ -582,6 +585,141 @@ Awaitable<void> Server::sendStreamableResponse(
}
}

// _____________________________________________________________________________
// TODO: move somewhere else?
nlohmann::json Server::executeUpdateQuery(
const ParsedQuery& query, const QueryExecutionTree& qet,
const ad_utility::Timer& requestTimer,
SharedCancellationHandle cancellationHandle, Index& index) {
AD_CONTRACT_CHECK(query.hasUpdateClause());
parsedQuery::UpdateClause update = query.updateClause();

ad_utility::Timer step{ad_utility::Timer::Started};

std::shared_ptr<const Result> res = qet.getResult();
auto timeQueryBodyExecution = step.msecs();
step.start();

auto& vocab = qet.getQec()->getIndex().getVocab();
LocalVocab& localVocab = index.deltaTriples().localVocab();
using IdOrVariable = std::variant<Id, Variable>;

auto transformSparqlTripleComponent =
[&vocab, &localVocab](TripleComponent component) -> IdOrVariable {
if (component.isVariable()) {
return std::move(component.getVariable());
} else {
return std::move(component).toValueId(vocab, localVocab);
}
};
auto transformSparqlTripleSimple =
[&transformSparqlTripleComponent](SparqlTripleSimple triple) {
return std::array<IdOrVariable, 3>{
transformSparqlTripleComponent(std::move(triple.s_)),
transformSparqlTripleComponent(std::move(triple.p_)),
transformSparqlTripleComponent(std::move(triple.o_))};
};
std::vector<std::array<IdOrVariable, 3>> toInsertTemplates =
ad_utility::transform(std::move(update.toInsert_),
transformSparqlTripleSimple);
std::vector<std::array<IdOrVariable, 3>> toDeleteTemplates =
ad_utility::transform(std::move(update.toDelete_),
transformSparqlTripleSimple);
auto timeTemplatePrepration = step.msecs();
step.start();

auto resolveVariable = [](const ConstructQueryExportContext& context,
IdOrVariable idOrVar) -> std::optional<Id> {
if (std::holds_alternative<Variable>(idOrVar)) {
auto var = std::get<Variable>(std::move(idOrVar));
if (!context._variableColumns.contains(var)) {
return std::nullopt;
} else {
return context._res.idTable().operator()(
context._row, context._variableColumns.at(var).columnIndex_);
}
} else if (std::holds_alternative<Id>(idOrVar)) {
return std::get<Id>(idOrVar);
} else {
AD_FAIL();
}
};

std::vector<IdTriple<0>> toInsert;
std::vector<IdTriple<0>> toDelete;
toInsert.reserve(res->idTable().size() * toInsertTemplates.size());
toDelete.reserve(res->idTable().size() * toDeleteTemplates.size());
// Result size is size(query result) x num template rows
// TODO: use ExportQueryExecutionTrees::getRowIndices as iterator
for (size_t i : std::views::iota((size_t)0, res->idTable().size())) {
ConstructQueryExportContext context{i, *res, qet.getVariableColumns(),
qet.getQec()->getIndex()};
for (const auto& [s, p, o] : toInsertTemplates) {
auto subject = resolveVariable(context, s);
auto predicate = resolveVariable(context, p);
auto object = resolveVariable(context, o);
if (!subject.has_value() || !predicate.has_value() ||
!object.has_value()) {
continue;
}

toInsert.emplace_back(std::array<Id, 3>{
subject.value(), predicate.value(), object.value()});
cancellationHandle->throwIfCancelled();
}

for (const auto& [s, p, o] : toDeleteTemplates) {
auto subject = resolveVariable(context, s);
auto predicate = resolveVariable(context, p);
auto object = resolveVariable(context, o);
if (!subject.has_value() || !predicate.has_value() ||
!object.has_value()) {
continue;
}

toDelete.emplace_back(std::array<Id, 3>{
subject.value(), predicate.value(), object.value()});
cancellationHandle->throwIfCancelled();
}
}

auto timeMaterializeUpdateTriples = step.msecs();
step.start();

index.deltaTriples().insertTriples(cancellationHandle, std::move(toInsert));
index.deltaTriples().deleteTriples(cancellationHandle, std::move(toDelete));
auto timeDeltaTriples = step.msecs();
step.start();

nlohmann::json j;
j["query"] = query._originalString;
j["status"] = "ERROR";
j["exception"] =
"Not supported: SPARQL 1.1 Update currently not supported by QLever.";
j["warnings"] = qet.collectWarnings();

j["runtimeInformation"]["meta"] = nlohmann::ordered_json(
qet.getRootOperation()->getRuntimeInfoWholeQuery());
RuntimeInformation runtimeInformation = qet.getRootOperation()->runtimeInfo();
runtimeInformation.addLimitOffsetRow(
query._limitOffset, std::chrono::milliseconds::zero(), false);
runtimeInformation.addDetail("executed-implicitly-during-query-export", true);
j["runtimeInformation"]["query_execution_tree"] =
nlohmann::ordered_json(runtimeInformation);

j["time"]["total"] = std::to_string(requestTimer.msecs().count()) + "ms";
j["time"]["queryBody"] =
std::to_string(timeQueryBodyExecution.count()) + "ms";
j["time"]["computeResult"] = j["time"]["total"];
j["time"]["templatePreparation"] =
std::to_string(timeTemplatePrepration.count()) + "ms";
j["time"]["updateTripleMaterialization"] =
std::to_string(timeMaterializeUpdateTriples.count()) + "ms";
j["time"]["deltaTriples"] = std::to_string(timeDeltaTriples.count()) + "ms";

return j;
}

// ____________________________________________________________________________
boost::asio::awaitable<void> Server::processQuery(
const ParamValueMap& params, ad_utility::Timer& requestTimer,
Expand Down Expand Up @@ -718,6 +856,13 @@ boost::asio::awaitable<void> Server::processQuery(
plannedQuery.value().parsedQuery_._limitOffset._offset -=
qet.getRootOperation()->getLimit()._offset;

if (plannedQuery.value().parsedQuery_.hasUpdateClause()) {
nlohmann::basic_json resp = Server::executeUpdateQuery(
plannedQuery.value().parsedQuery_, qet, requestTimer,
std::move(cancellationHandle), index_);
co_return co_await sendJson(std::move(resp), responseStatus);
}

// This actually processes the query and sends the result in the requested
// format.
switch (mediaType.value()) {
Expand Down
6 changes: 6 additions & 0 deletions src/engine/Server.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,10 @@ class Server {
ad_utility::MediaType mediaType, const PlannedQuery& plannedQuery,
const QueryExecutionTree& qet,
SharedCancellationHandle cancellationHandle) const;

nlohmann::json executeUpdateQuery(const ParsedQuery& query,
const QueryExecutionTree& qet,
const ad_utility::Timer& requestTimer,
SharedCancellationHandle cancellationHandle,
Index& index);
};
11 changes: 11 additions & 0 deletions src/engine/idTable/IdTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,17 @@ class IdTableStatic
*(static_cast<Base*>(this)) = std::move(b);
return *this;
}

// This operator is only for debugging and testing. It returns a
// human-readable representation.
friend std::ostream& operator<<(std::ostream& os,
const IdTableStatic& idTable) {
os << "{ ";
std::ranges::copy(
idTable, std::ostream_iterator<columnBasedIdTable::Row<Id>>(os, " "));
os << "}";
return os;
}
};

// This was previously implemented as an alias (`using IdTable =
Expand Down
12 changes: 12 additions & 0 deletions src/engine/idTable/IdTableRow.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <variant>
#include <vector>

#include "global/Id.h"
#include "util/Enums.h"
#include "util/Exception.h"
#include "util/Forward.h"
Expand Down Expand Up @@ -93,6 +94,17 @@ class Row {
std::ranges::copy(*this, result.begin());
return result;
}

// This operator is only for debugging and testing. It returns a
// human-readable representation.
friend std::ostream& operator<<(std::ostream& os, const Row& idTableRow)
requires(std::is_same_v<T, Id>) {
os << "(";
for (size_t i = 0; i < idTableRow.numColumns(); ++i) {
os << idTableRow[i] << (i < idTableRow.numColumns() - 1 ? " " : ")");
}
return os;
}
};

// The following two classes store a reference to a row in the underlying
Expand Down
3 changes: 3 additions & 0 deletions src/engine/sparqlExpressions/SparqlExpressionValueGetters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ NumericValue NumericValueGetter::operator()(
// functions.
return static_cast<int64_t>(id.getBool());
case Datatype::Undefined:
case Datatype::Sentinel:
case Datatype::VocabIndex:
case Datatype::LocalVocabIndex:
case Datatype::TextRecordIndex:
Expand All @@ -49,6 +50,7 @@ auto EffectiveBooleanValueGetter::operator()(
case Datatype::Bool:
return id.getBool() ? True : False;
case Datatype::Undefined:
case Datatype::Sentinel:
case Datatype::BlankNodeIndex:
return Undef;
case Datatype::VocabIndex: {
Expand Down Expand Up @@ -142,6 +144,7 @@ IntDoubleStr ToNumericValueGetter::operator()(
ValueId id, [[maybe_unused]] const EvaluationContext* context) const {
switch (id.getDatatype()) {
case Datatype::Undefined:
case Datatype::Sentinel:
return std::monostate{};
case Datatype::Int:
return id.getInt();
Expand Down
64 changes: 61 additions & 3 deletions src/global/IdTriple.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,70 @@
// Copyright 2024, University of Freiburg
// Chair of Algorithms and Data Structures
// Authors: Hannah Bast <bast@cs.uni-freiburg.de>
// Authors:
// 2023 Hannah Bast <bast@cs.uni-freiburg.de>
// 2024 Julian Mundhahs <mundhahj@tf.uni-freiburg.de>

#pragma once

#include <array>
#include <ostream>

#include "global/Id.h"
#include "index/CompressedRelation.h"

// Should we have an own class for this? We need this at several places.
using IdTriple = std::array<Id, 3>;
// TODO<qup42> some matching comparison operators between
// idtriple/permutedtriple would be nice
template <size_t N = 0>
struct IdTriple {
// The three IDs that define the triple.
std::array<Id, 3> ids_;
// Some additional payload of the triple, e.g. which graph it belongs to.
std::array<Id, N> payload_;

explicit IdTriple() = default;
explicit IdTriple(
const CompressedBlockMetadata::PermutedTriple& permutedTriple)
: ids_({permutedTriple.col0Id_, permutedTriple.col1Id_,
permutedTriple.col2Id_}),
payload_(){};

explicit IdTriple(const std::array<Id, 3>& ids) requires(N == 0)
: ids_(ids), payload_(){};

explicit IdTriple(const std::array<Id, 3>& ids,
const std::array<Id, N>& payload) requires(N != 0)
: ids_(ids), payload_(payload){};

friend std::ostream& operator<<(std::ostream& os, const IdTriple& triple) {
os << "IdTriple(";
std::ranges::copy(triple.ids_, std::ostream_iterator<Id>(os, ", "));
std::ranges::copy(triple.payload_, std::ostream_iterator<Id>(os, ", "));
os << ")";
return os;
}

auto operator<=>(const IdTriple&) const = default;

template <typename H>
friend H AbslHashValue(H h, const IdTriple& c) {
return H::combine(std::move(h), c.ids_);
}

// TODO<qup42>: should this be a `PermutedTriple`?
// Permutes the ID of this triple according to the given permutation given by
// its keyOrder.
IdTriple<N> permute(const std::array<size_t, 3>& keyOrder) const {
std::array<Id, 3> newIds{ids_[keyOrder[0]], ids_[keyOrder[1]],
ids_[keyOrder[2]]};
if constexpr (N == 0) {
return IdTriple<N>(newIds);
} else {
return IdTriple<N>(newIds, payload_);
}
}

CompressedBlockMetadata::PermutedTriple toPermutedTriple() const
requires(N == 0) {
return {ids_[0], ids_[1], ids_[2]};
}
};
23 changes: 21 additions & 2 deletions src/global/ValueId.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ enum struct Datatype {
Date,
WordVocabIndex,
BlankNodeIndex,
MaxValue = BlankNodeIndex
Sentinel,
MaxValue = Sentinel,
// Note: Unfortunately we cannot easily get the size of an enum.
// If members are added to this enum, then the `MaxValue`
// alias must always be equal to the last member,
Expand Down Expand Up @@ -61,6 +62,8 @@ constexpr std::string_view toString(Datatype type) {
return "Date";
case Datatype::BlankNodeIndex:
return "BlankNodeIndex";
case Datatype::Sentinel:
return "Sentinel";
}
// This line is reachable if we cast an arbitrary invalid int to this enum
AD_FAIL();
Expand Down Expand Up @@ -112,6 +115,10 @@ class ValueId {
/// generic code like in the `visit` method.
struct UndefinedType {};

// A struct that represent the single sentinel value. See above for why this
// is required.
struct SentinelType {};

private:
// The actual bits.
T _bits;
Expand Down Expand Up @@ -174,6 +181,13 @@ class ValueId {
[[nodiscard]] UndefinedType getUndefined() const noexcept { return {}; }
bool isUndefined() const noexcept { return *this == makeUndefined(); }

constexpr static ValueId makeSentinel() noexcept {
return addDatatypeBits(std::bit_cast<uint64_t>(0UL), Datatype::Sentinel);
}

[[nodiscard]] SentinelType getSentinel() const noexcept { return {}; }
bool isSentinel() const noexcept { return *this == makeSentinel(); }

/// Create a `ValueId` for a double value. The conversion will reduce the
/// precision of the mantissa of an IEEE double precision floating point
/// number from 53 to 49 significant bits.
Expand Down Expand Up @@ -319,6 +333,8 @@ class ValueId {
return std::invoke(visitor, getDate());
case Datatype::BlankNodeIndex:
return std::invoke(visitor, getBlankNodeIndex());
case Datatype::Sentinel:
return std::invoke(visitor, getSentinel());
}
AD_FAIL();
}
Expand All @@ -329,10 +345,13 @@ class ValueId {
ostr << toString(id.getDatatype())[0] << ':';
if (id.getDatatype() == Datatype::Undefined) {
return ostr << id.getBits();
} else if (id.getDatatype() == Datatype::Sentinel) {
return ostr << "Sentinel";
}

auto visitor = [&ostr]<typename T>(T&& value) {
if constexpr (ad_utility::isSimilar<T, ValueId::UndefinedType>) {
if constexpr (ad_utility::isSimilar<T, ValueId::UndefinedType> ||
ad_utility::isSimilar<T, ValueId::SentinelType>) {
// already handled above
AD_FAIL();
} else if constexpr (ad_utility::isSimilar<T, double> ||
Expand Down
Loading
Loading