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

Add where to SubjectEntity's query() #560

Merged
merged 2 commits into from
Feb 6, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ This project _loosely_ adheres to [Semantic Versioning](https://semver.org/spec/
- Add `author` and `timestamp` to SubjectEntity objects [PR#557](https://github.com/coasys/ad4m/pull/557)
- Make `SubjectEntity.#perspective` protected to enable subclasses to implement complex fields and methods [PR#557](https://github.com/coasys/ad4m/pull/557)
- Add AI client to PerspectiveProxy to enable SubjectEntity sub-classes (subject classes) to use AI processes without having to rely on ad4m-connect or similar to access the AI client. [PR#558](https://github.com/coasys/ad4m/pull/558)
- SubjectEntity.query() with `where: { propertyName: "value" }` and `where: { condition: 'triple(Base, _, "...")'} [PR#560](https://github.com/coasys/ad4m/pull/560)

### Changed
- Partially migrated the Runtime service to Rust. (DM language installation for agents is pending.) [PR#466](https://github.com/coasys/ad4m/pull/466)
Expand Down
31 changes: 22 additions & 9 deletions core/src/subject/SubjectEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,13 +264,27 @@ export class SubjectEntity {
let subjectClass = await perspective.stringOrTemplateObjectToSubjectClass(this)

let res = [];
let instanceConditions = `subject_class("${subjectClass}", C), instance(C, Base), link("${source}", Predicate, Base, Timestamp, Author)`

if (query) {
if(query.where) {
if(query.where["condition"]) {
instanceConditions += ", " + query.where["condition"]
} else {
const pairs = Object.entries(query.where);
for(let p of pairs) {
const propertyName = p[0];
const propertyValue = p[1];
instanceConditions += `, property_getter(C, Base, "${propertyName}", "${propertyValue}")`
}
}
}

if (query) {
try {
const queryResponse = (await perspective.infer(`findall([Timestamp, Base], (subject_class("${subjectClass}", C), instance(C, Base), link("${source}", Predicate, Base, Timestamp, Author)), AllData), sort(AllData, SortedData), length(SortedData, DataLength).`))[0]
const queryResponse = (await perspective.infer(`findall([Timestamp, Base], (${instanceConditions}), AllData), sort(AllData, SortedData), length(SortedData, DataLength).`))[0]

if (queryResponse.DataLength >= query.size) {
const mainQuery = `findall([Timestamp, Base], (subject_class("${subjectClass}", C), instance(C, Base), link("${source}", Predicate, Base, Timestamp, Author)), AllData), sort(AllData, SortedData), reverse(SortedData, ReverseSortedData), paginate(ReverseSortedData, ${query.page}, ${query.size}, PageData).`
const mainQuery = `findall([Timestamp, Base], (${instanceConditions}), AllData), sort(AllData, SortedData), reverse(SortedData, ReverseSortedData), paginate(ReverseSortedData, ${query.page}, ${query.size}, PageData).`

res = await perspective.infer(mainQuery);

Expand All @@ -279,17 +293,13 @@ export class SubjectEntity {
Timestamp: r[0]
}))
} else {
res = await perspective.infer(
`subject_class("${subjectClass}", C), instance(C, Base), triple("${source}", Predicate, Base).`
);
res = await perspective.infer(instanceConditions);
}
} catch (e) {
console.log("Query failed", e);
}
} else {
res = await perspective.infer(
`subject_class("${subjectClass}", C), instance(C, Base), triple("${source}", Predicate, Base).`
);
res = await perspective.infer(instanceConditions);
}

if (!res) return [];
Expand Down Expand Up @@ -320,4 +330,7 @@ export type SubjectEntityQueryParam = {

// The page of the query.
page?: number;

// conditions on properties
where?: { condition?: string } | object;
}
25 changes: 22 additions & 3 deletions tests/js/tests/prolog-and-literals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,24 +694,43 @@ describe("Prolog + Literals", () => {

it("query()", async () => {
let recipes = await Recipe.query(perspective!, { page: 1, size: 2 });

expect(recipes.length).to.equal(2)

recipes = await Recipe.query(perspective!, { page: 2, size: 1 });
expect(recipes.length).to.equal(1)

const testName = "recipe://where_test"

recipes = await Recipe.query(perspective!, { where: { name: testName }})
expect(recipes.length).to.equal(0)

let root = Literal.from("Where test").toUrl()
const whereTestRecipe = new Recipe(perspective!, root)
whereTestRecipe.name = testName
await whereTestRecipe.save()

recipes = await Recipe.query(perspective!, { where: { name: testName }})
expect(recipes.length).to.equal(1)

recipes = await Recipe.query(perspective!, { where: { condition: `triple(Base, _, "ad4m://test_self")` }})
expect(recipes.length).to.equal(0)

await perspective?.add({source: root, target: "ad4m://test_self"})
recipes = await Recipe.query(perspective!, { where: { condition: `triple(Base, _, "ad4m://test_self")` }})
expect(recipes.length).to.equal(1)

})

it("delete()", async () => {
const recipe2 = await Recipe.all(perspective!);

expect(recipe2.length).to.equal(3)
expect(recipe2.length).to.equal(4)

await recipe2[0].delete();

const recipe3 = await Recipe.all(perspective!);

expect(recipe3.length).to.equal(2)
expect(recipe3.length).to.equal(3)
})

it("can constrain collection entries through 'where' clause with prolog condition", async () => {
Expand Down