Skip to content
Merged
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
12 changes: 12 additions & 0 deletions query/common/CardinalityEstimation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ size_t CardinalityEstimation::estimateEdgeCount() const {
return reader.getEdgeCount();
}

size_t CardinalityEstimation::estimateOutEdgeCount(const LabelSet& labelset) const {
GraphReader reader(_graphView);

return reader.getOutEdgeCountMatchingLabelset(labelset.handle());
}

size_t CardinalityEstimation::estimateInEdgeCount(const LabelSet& labelset) const {
GraphReader reader(_graphView);

return reader.getInEdgeCountMatchingLabelset(labelset.handle());
}

bool CardinalityEstimation::shouldPreferCartesian(const LabelSet& left, const LabelSet& right, size_t queryLimit) const {
const size_t leftCount = estimateNodeCount(left);
const size_t rightCount = estimateNodeCount(right);
Expand Down
5 changes: 5 additions & 0 deletions query/common/CardinalityEstimation.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ class CardinalityEstimation {
// count is what a by-type scan is read at, an upper bound on its rows.
size_t estimateEdgeCount() const;

// The edges a scan of the out-edge (in-edge) index walks under a label set: those of the
// nodes matching it, deleted ones included.
size_t estimateOutEdgeCount(const LabelSet& labelset) const;
size_t estimateInEdgeCount(const LabelSet& labelset) const;

// Returns true when cartesian product + filter is likely cheaper
// than value hash join, either because the product is small or
// because a small LIMIT makes early-termination effective.
Expand Down
2 changes: 1 addition & 1 deletion query/ir/codegen/DBPassPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const std::array<DBPassFactory, DB_PASS_COUNT> dbPassPipeline = {
[](const mlir::db::DBPassContext*) { return mlir::db::createNarrowEdgeTypeReads(); },
[](const mlir::db::DBPassContext*) { return mlir::db::createFuseScanOutEdgesByLabel(); },
[](const mlir::db::DBPassContext*) { return mlir::db::createFuseScanInEdgesByLabel(); },
[](const mlir::db::DBPassContext*) { return mlir::db::createFuseScanEdgesByEndpointLabel(); },
[](const mlir::db::DBPassContext* context) { return mlir::db::createFuseScanEdgesByEndpointLabel(context); },
[](const mlir::db::DBPassContext*) { return mlir::db::createFuseEdgesByEndpointLabel(); },
[](const mlir::db::DBPassContext*) { return mlir::db::createRemoveRedundantLabelChecks(); },
[](const mlir::db::DBPassContext*) { return mlir::db::createFuseExploreEndConstraint(); },
Expand Down
28 changes: 28 additions & 0 deletions query/ir/dialect/db/DBOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,18 @@ void ScanInEdgesByLabelSrc::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
}
}

void ScanOutEdgesByLabelSrcTgt::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
for (Value result : getResults()) {
setNameFn(result, "");
}
}

void ScanInEdgesByLabelSrcTgt::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
for (Value result : getResults()) {
setNameFn(result, "");
}
}

// Ensures each variable has a numeric name
void GetOutEdges::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
for (Value result : getResults()) {
Expand Down Expand Up @@ -1360,6 +1372,22 @@ LogicalResult ScanInEdgesByLabelSrc::verify() {
return success();
}

LogicalResult ScanOutEdgesByLabelSrcTgt::verify() {
if (getSrcLabels().empty() || getTgtLabels().empty()) {
return emitOpError("requires at least one label on each end");
}

return success();
}

LogicalResult ScanInEdgesByLabelSrcTgt::verify() {
if (getSrcLabels().empty() || getTgtLabels().empty()) {
return emitOpError("requires at least one label on each end");
}

return success();
}

LogicalResult GetOutEdgesByLabel::verify() {
if (getLabels().empty()) {
return emitOpError("requires at least one label");
Expand Down
74 changes: 74 additions & 0 deletions query/ir/dialect/db/DBOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,80 @@ def ScanInEdgesByLabelSrc : TuringOp<"scan_in_edges_by_label_src", [Pure, Declar
}];
}

/**
* ScanOutEdgesByLabelSrcTgt
*/
def ScanOutEdgesByLabelSrcTgt : TuringOp<"scan_out_edges_by_label_src_tgt", [Pure, DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>]> {
let summary = "Produce every edge whose source and target each carry every given label of their end as four row-aligned columns";

let description = [{
The both-ends sibling of db.scan_out_edges_by_label_src: it produces the edges
whose source's label set is a superset of `src_labels` and whose target's is a
superset of `tgt_labels`, in the same four row-aligned columns:

%srcs, %eids, %etypes, %tgts = db.scan_out_edges_by_label_src_tgt(["Person"], ["Interest"]) : !db.column<!storage.node_id>, !db.column<!storage.edge_id>, !db.column<!storage.edge_type_id>, !db.column<!storage.node_id>

It is `MATCH (a:Person)-->(b:Interest)`: the edges db.scan_out_edges_by_label_src
keeps once a db.check_label_constraint over its target column has dropped the
rest. It reads the index keyed by the source's label set, as that scan does, and
checks each edge's target against `tgt_labels`. db.scan_in_edges_by_label_src_tgt
produces the same rows off the index keyed by the target's label set.

Neither label list may be empty: a scan labelled on one end only is
db.scan_out_edges_by_label_src or db.scan_in_edges_by_label_tgt.
}];

let arguments = (ins LabelRefArrayAttr:$src_labels, LabelRefArrayAttr:$tgt_labels);

let results = (outs ColumnNodeIDs:$srcids,
ColumnEdgeIDs:$eids,
ColumnEdgeTypes:$etypes,
ColumnNodeIDs:$tgtids);
let hasVerifier = 1;

let assemblyFormat = [{
`(` $src_labels `,` $tgt_labels `)` attr-dict `:` qualified(type($srcids)) `,` qualified(type($eids)) `,`
qualified(type($etypes)) `,` qualified(type($tgtids))
}];
}

/**
* ScanInEdgesByLabelSrcTgt
*/
def ScanInEdgesByLabelSrcTgt : TuringOp<"scan_in_edges_by_label_src_tgt", [Pure, DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>]> {
let summary = "Produce every edge whose target and source each carry every given label of their end, read off the target's index";

let description = [{
The both-ends sibling of db.scan_in_edges_by_label_tgt: it produces the edges
whose source's label set is a superset of `src_labels` and whose target's is a
superset of `tgt_labels`:

%srcs, %eids, %etypes, %tgts = db.scan_in_edges_by_label_src_tgt(["Interest"], ["Person"]) : !db.column<!storage.node_id>, !db.column<!storage.edge_id>, !db.column<!storage.edge_type_id>, !db.column<!storage.node_id>

It is `MATCH (a:Person)<--(b:Interest)`: the edges db.scan_in_edges_by_label_tgt
keeps once a db.check_label_constraint over its source column has dropped the
rest. It reads the index keyed by the target's label set, as that scan does, and
checks each edge's source against `src_labels`. The rows are those of
db.scan_out_edges_by_label_src_tgt; the two differ in the index they read.

Neither label list may be empty: a scan labelled on one end only is
db.scan_in_edges_by_label_tgt or db.scan_out_edges_by_label_src.
}];

let arguments = (ins LabelRefArrayAttr:$src_labels, LabelRefArrayAttr:$tgt_labels);

let results = (outs ColumnNodeIDs:$srcids,
ColumnEdgeIDs:$eids,
ColumnEdgeTypes:$etypes,
ColumnNodeIDs:$tgtids);
let hasVerifier = 1;

let assemblyFormat = [{
`(` $src_labels `,` $tgt_labels `)` attr-dict `:` qualified(type($srcids)) `,` qualified(type($eids)) `,`
qualified(type($etypes)) `,` qualified(type($tgtids))
}];
}

/**
* GetOutEdges
*/
Expand Down
Loading
Loading