Skip to content

Commit 20199dc

Browse files
authored
ENG-1012 Update Query Builder Relation Conditions to Use Reified Relation Triples (#538)
* eng-1012 update query builder to use reified relations
1 parent 48de469 commit 20199dc

File tree

5 files changed

+636
-215
lines changed

5 files changed

+636
-215
lines changed

apps/roam/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"react-draggable": "4.4.5",
6969
"react-in-viewport": "1.0.0-alpha.20",
7070
"react-vertical-timeline-component": "3.5.2",
71-
"roamjs-components": "0.85.4",
71+
"roamjs-components": "0.85.6",
7272
"tldraw": "2.3.0",
7373
"use-sync-external-store": "1.5.0",
7474
"xregexp": "^5.0.0",

apps/roam/src/utils/createReifiedBlock.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@ import { getSetting } from "~/utils/extensionSettings";
55

66
export const DISCOURSE_GRAPH_PROP_NAME = "discourse-graph";
77

8+
const SANE_ROLE_NAME_RE = new RegExp(/^[\w\-]*$/);
9+
810
const strictQueryForReifiedBlocks = async (
911
parameterUids: Record<string, string>,
1012
): Promise<string | null> => {
1113
const paramsAsSeq = Object.entries(parameterUids);
14+
// validate parameter names
15+
if (
16+
Object.keys(parameterUids).filter((k) => !k.match(SANE_ROLE_NAME_RE)).length
17+
)
18+
throw new Error(
19+
`invalid parameter names in ${Object.keys(parameterUids).join(", ")}`,
20+
);
1221
const query = `[:find ?u ?d
1322
:in $ ${paramsAsSeq.map(([k]) => "?" + k).join(" ")}
1423
:where [?s :block/uid ?u] [?s :block/props ?p] [(get ?p :${DISCOURSE_GRAPH_PROP_NAME}) ?d]
@@ -72,7 +81,7 @@ const createReifiedBlock = async ({
7281
const RELATION_PAGE_TITLE = "roam/js/discourse-graph/relations";
7382
let relationPageUid: string | undefined = undefined;
7483

75-
const getRelationPageUid = async (): Promise<string> => {
84+
const getOrCreateRelationPageUid = async (): Promise<string> => {
7685
if (relationPageUid === undefined) {
7786
relationPageUid = getPageUidByPageTitle(RELATION_PAGE_TITLE);
7887
if (relationPageUid === "") {
@@ -82,8 +91,16 @@ const getRelationPageUid = async (): Promise<string> => {
8291
return relationPageUid;
8392
};
8493

94+
export const getExistingRelationPageUid = (): string | undefined => {
95+
if (relationPageUid === undefined) {
96+
const uid = getPageUidByPageTitle(RELATION_PAGE_TITLE);
97+
if (uid !== "") relationPageUid = uid;
98+
}
99+
return relationPageUid;
100+
};
101+
85102
export const countReifiedRelations = async (): Promise<number> => {
86-
const pageUid = await getRelationPageUid();
103+
const pageUid = getExistingRelationPageUid();
87104
if (pageUid === undefined) return 0;
88105
const r = await window.roamAlphaAPI.data.async.q(
89106
`[:find (count ?c) :where [?p :block/children ?c] [?p :block/uid "${pageUid}"]]`,
@@ -103,7 +120,7 @@ export const createReifiedRelation = async ({
103120
const authorized = getSetting("use-reified-relations");
104121
if (authorized) {
105122
return await createReifiedBlock({
106-
destinationBlockUid: await getRelationPageUid(),
123+
destinationBlockUid: await getOrCreateRelationPageUid(),
107124
schemaUid: relationBlockUid,
108125
parameterUids: {
109126
sourceUid,

apps/roam/src/utils/fireQuery.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import conditionToDatalog from "./conditionToDatalog";
22
import type {
33
PullBlock,
4-
DatalogAndClause,
54
DatalogClause,
5+
DatalogAndClause,
66
} from "roamjs-components/types";
77
import compileDatalog from "./compileDatalog";
88
import { getNodeEnv } from "roamjs-components/util/env";
@@ -65,9 +65,9 @@ const firstVariable = (
6565
};
6666

6767
const optimizeQuery = (
68-
clauses: (DatalogClause | DatalogAndClause)[],
68+
clauses: DatalogClause[],
6969
capturedVariables: Set<string>,
70-
): (DatalogClause | DatalogAndClause)[] => {
70+
): DatalogClause[] => {
7171
const marked = clauses.map(() => false);
7272
const orderedClauses: (DatalogClause | DatalogAndClause)[] = [];
7373
const variablesByIndex: Record<number, Set<string>> = {};
@@ -107,7 +107,8 @@ const optimizeQuery = (
107107
if (Array.from(allVars).every((v) => capturedVariables.has(v))) {
108108
score = 10;
109109
} else {
110-
score = 100002;
110+
// downgrade disjunction and negation
111+
score = c.type === "and-clause" ? 100002 : 100006;
111112
}
112113
} else if (c.type === "not-join-clause" || c.type === "or-join-clause") {
113114
if (c.variables.every((v) => capturedVariables.has(v.value))) {
@@ -125,7 +126,8 @@ const optimizeQuery = (
125126
(a) => a.type !== "variable" || capturedVariables.has(a.value),
126127
)
127128
) {
128-
score = 1000;
129+
// equality is almost as good as a binding
130+
c.type == "pred-expr" && c.pred == "=" ? (score = 5) : (score = 1000);
129131
} else {
130132
score = 100004;
131133
}
@@ -155,6 +157,16 @@ const optimizeQuery = (
155157
bestClause.arguments
156158
.filter((v) => v.type === "variable")
157159
.forEach((v) => capturedVariables.add(v.value));
160+
} else if (bestClause.type === "fn-expr") {
161+
// A function expression acts as biding a variable to a unique function value
162+
if (
163+
bestClause.arguments.filter(
164+
(a) => a.type === "variable" && !capturedVariables.has(a.value),
165+
).length === 0 &&
166+
bestClause.binding.type === "bind-scalar" &&
167+
bestClause.binding.variable.type === "variable"
168+
)
169+
capturedVariables.add(bestClause.binding.variable.value);
158170
}
159171
}
160172
return orderedClauses;
@@ -197,7 +209,7 @@ export const getDatalogQuery = ({
197209
const whereClauses = optimizeQuery(
198210
getWhereClauses({ conditions, returnNode }),
199211
new Set([]),
200-
) as DatalogClause[];
212+
);
201213

202214
const defaultSelections: {
203215
mapper: PredefinedSelection["mapper"];

0 commit comments

Comments
 (0)