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
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: '3.4'
services:
weaviate:
image: semitechnologies/weaviate:1.13.0
image: semitechnologies/weaviate:1.13.2
restart: on-failure:0
ports:
- "8080:8080"
Expand Down
155 changes: 148 additions & 7 deletions graphql/getter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,76 @@ describe("nearText searchers", () => {
expect(mockClient.query).toHaveBeenCalledWith(expectedQuery);
});

test("with moveTo with objects parameter", () => {
const mockClient = {
query: jest.fn(),
};

const expectedQuery =
`{Get{Person` +
`(nearText:{concepts:["foo","bar"],certainty:0.7,moveTo:{objects:[{id:"uuid"},{beacon:"beacon"}],force:0.7}})` +
`{name}}}`;

new Getter(mockClient)
.withClassName("Person")
.withFields("name")
.withNearText({
concepts: ["foo", "bar"],
certainty: 0.7,
moveTo: { force: 0.7, objects: [{ id: "uuid" }, {beacon: "beacon"}] },
})
.do();

expect(mockClient.query).toHaveBeenCalledWith(expectedQuery);
});

test("with moveAwayFrom with objects parameter", () => {
const mockClient = {
query: jest.fn(),
};

const expectedQuery =
`{Get{Person` +
`(nearText:{concepts:["foo","bar"],certainty:0.7,moveAwayFrom:{objects:[{id:"uuid"},{beacon:"beacon"}],force:0.7}})` +
`{name}}}`;

new Getter(mockClient)
.withClassName("Person")
.withFields("name")
.withNearText({
concepts: ["foo", "bar"],
certainty: 0.7,
moveAwayFrom: { force: 0.7, objects: [{ id: "uuid" }, {beacon: "beacon"}] },
})
.do();

expect(mockClient.query).toHaveBeenCalledWith(expectedQuery);
});

test("with moveTo and moveAway with objects parameter", () => {
const mockClient = {
query: jest.fn(),
};

const expectedQuery =
`{Get{Person` +
`(nearText:{concepts:["foo","bar"],certainty:0.7,moveTo:{objects:[{id:"uuid"}],force:0.7},moveAwayFrom:{objects:[{beacon:"beacon"}],force:0.5}})` +
`{name}}}`;

new Getter(mockClient)
.withClassName("Person")
.withFields("name")
.withNearText({
concepts: ["foo", "bar"],
certainty: 0.7,
moveTo: { force: 0.7, objects: [{ id: "uuid" }] },
moveAwayFrom: { force: 0.5, objects: [{ beacon: "beacon" }] },
})
.do();

expect(mockClient.query).toHaveBeenCalledWith(expectedQuery);
});

describe("queries with invalid nearText searchers", () => {
const mockClient = {
query: jest.fn(),
Expand All @@ -308,31 +378,102 @@ describe("nearText searchers", () => {
msg: "nearText filter: certainty must be a number",
},
{
title: "moveTo without concepts",
title: "moveTo empty object",
nearText: { concepts: ["foo"], moveTo: {} },
msg: "nearText filter: moveTo.concepts must be an array",
msg: "nearText filter: moveTo.concepts or moveTo.objects must be present",
},
{
title: "moveTo without concepts",
title: "moveTo without force with concepts",
nearText: { concepts: ["foo"], moveTo: { concepts: ["foo"] } },
msg: "nearText filter: moveTo must have fields 'concepts' and 'force'",
msg: "nearText filter: moveTo must have fields 'concepts' or 'objects' and 'force'",
},
{
title: "moveTo without force with objects",
nearText: { concepts: ["foo"], moveTo: { objects: [{beacon: "beacon"}] } },
msg: "nearText filter: moveTo must have fields 'concepts' or 'objects' and 'force'",
},
{
title: "moveAwayFrom without concepts",
nearText: { concepts: ["foo"], moveAwayFrom: {} },
msg: "nearText filter: moveAwayFrom.concepts must be an array",
msg: "nearText filter: moveAwayFrom.concepts or moveAwayFrom.objects must be present",
},
{
title: "moveAwayFrom without concepts",
title: "moveAwayFrom without force with concepts",
nearText: { concepts: ["foo"], moveAwayFrom: { concepts: ["foo"] } },
msg:
"nearText filter: moveAwayFrom must have fields 'concepts' and 'force'",
"nearText filter: moveAwayFrom must have fields 'concepts' or 'objects' and 'force'",
},
{
title: "moveAwayFrom without force with objects",
nearText: { concepts: ["foo"], moveAwayFrom: { objects: [{id: "uuid"}] } },
msg:
"nearText filter: moveAwayFrom must have fields 'concepts' or 'objects' and 'force'",
},
{
title: "autocorrect of wrong type",
nearText: { concepts: ["foo"], autocorrect: "foo" },
msg: "nearText filter: autocorrect must be a boolean",
},
{
title: "moveTo with empty objects",
nearText: { concepts: ["foo"], moveTo: { force: 0.8, objects: {} } },
msg:
"nearText filter: moveTo.objects must be an array",
},
{
title: "moveTo with empty object in objects",
nearText: { concepts: ["foo"], moveTo: { force: 0.8, objects: [{}] } },
msg:
"nearText filter: moveTo.objects[0].id or moveTo.objects[0].beacon must be present",
},
{
title: "moveTo with objects[0].id not of string type",
nearText: { concepts: ["foo"], moveTo: { force: 0.8, objects: [{id: 0.8}] } },
msg:
"nearText filter: moveTo.objects[0].id must be string",
},
{
title: "moveTo with objects[0].beacon not of string type",
nearText: { concepts: ["foo"], moveTo: { force: 0.8, objects: [{beacon: 0.8}] } },
msg:
"nearText filter: moveTo.objects[0].beacon must be string",
},
{
title: "moveTo with objects[0].id not of string type and objects[1].beacon not of string type",
nearText: { concepts: ["foo"], moveTo: { force: 0.8, objects: [{id: 0.8},{beacon: 0.8}] } },
msg:
"nearText filter: moveTo.objects[0].id must be string, moveTo.objects[1].beacon must be string",
},
{
title: "moveAwayFrom with empty objects",
nearText: { concepts: ["foo"], moveAwayFrom: { force: 0.8, objects: {} } },
msg:
"nearText filter: moveAwayFrom.objects must be an array",
},
{
title: "moveAwayFrom with empty object in objects",
nearText: { concepts: ["foo"], moveAwayFrom: { force: 0.8, objects: [{}] } },
msg:
"nearText filter: moveAwayFrom.objects[0].id or moveAwayFrom.objects[0].beacon must be present",
},
{
title: "moveAwayFrom with objects[0].id not of string type",
nearText: { concepts: ["foo"], moveAwayFrom: { force: 0.8, objects: [{id: 0.8}] } },
msg:
"nearText filter: moveAwayFrom.objects[0].id must be string",
},
{
title: "moveAwayFrom with objects[0].beacon not of string type",
nearText: { concepts: ["foo"], moveAwayFrom: { force: 0.8, objects: [{beacon: 0.8}] } },
msg:
"nearText filter: moveAwayFrom.objects[0].beacon must be string",
},
{
title: "moveAwayFrom with objects[0].id not of string type and objects[1].beacon not of string type",
nearText: { concepts: ["foo"], moveAwayFrom: { force: 0.8, objects: [{id: 0.8},{beacon: 0.8}] } },
msg:
"nearText filter: moveAwayFrom.objects[0].id must be string, moveAwayFrom.objects[1].beacon must be string",
},
];

tests.forEach((t) => {
Expand Down
19 changes: 19 additions & 0 deletions graphql/journey.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ describe("the graphql journey", () => {
.catch((e) => fail("it should not have error'd" + e));
});

test("graphql get with nearText with moveTo and moveAwayFrom", () => {
return client.graphql
.get()
.withClassName("Article")
.withNearText({
concepts: ["Article"], certainty: 0.7,
moveTo: { objects:[{ id: "abefd256-8574-442b-9293-9205193737e2" }], force: 0.7 },
moveAwayFrom: { objects:[{ id: "abefd256-8574-442b-9293-9205193737e1" }], force: 0.5 },
})
.withFields("_additional { id }")
.do()
.then((res) => {
expect(res.data.Get.Article.length).toBe(3);
})
.catch((e) => fail("it should not have error'd" + e));
});

test("graphql get expected failure - multiple nearMedia filters", () => {
return expect(() => {
client.graphql
Expand Down Expand Up @@ -509,6 +526,7 @@ const setup = async (client) => {
},
},
{
id: "abefd256-8574-442b-9293-9205193737e1",
class: "Article",
properties: {
wordCount: 40,
Expand All @@ -517,6 +535,7 @@ const setup = async (client) => {
},
},
{
id: "abefd256-8574-442b-9293-9205193737e2",
class: "Article",
properties: {
wordCount: 600,
Expand Down
79 changes: 69 additions & 10 deletions graphql/nearText.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,27 @@ export default class GraphQLNearText {
}

if (this.moveTo) {
let moveToArgs = [`concepts:${JSON.stringify(this.moveToConcepts)}`];
let moveToArgs = []
if (this.moveToConcepts) {
moveToArgs = [...moveToArgs, `concepts:${JSON.stringify(this.moveToConcepts)}`];
}
if (this.moveToObjects) {
moveToArgs = [...moveToArgs, `objects:${this.moveToObjects}`];
}
if (this.moveToForce) {
moveToArgs = [...moveToArgs, `force:${this.moveToForce}`];
}
args = [...args, `moveTo:{${moveToArgs.join(",")}}`];
}

if (this.moveAwayFrom) {
let moveAwayFromArgs = [
`concepts:${JSON.stringify(this.moveAwayFromConcepts)}`,
];
let moveAwayFromArgs = [];
if (this.moveAwayFromConcepts) {
moveAwayFromArgs = [...moveAwayFromArgs, `concepts:${JSON.stringify(this.moveAwayFromConcepts)}`];
}
if (this.moveAwayFromObjects) {
moveAwayFromArgs = [...moveAwayFromArgs, `objects:${this.moveAwayFromObjects}`];
}
if (this.moveAwayFromForce) {
moveAwayFromArgs = [
...moveAwayFromArgs,
Expand All @@ -50,17 +60,17 @@ export default class GraphQLNearText {
}

if (this.moveTo) {
if (!this.moveToForce || !this.moveToConcepts) {
if (!this.moveToForce || (!this.moveToConcepts && !this.moveToObjects)) {
throw new Error(
"nearText filter: moveTo must have fields 'concepts' and 'force'"
"nearText filter: moveTo must have fields 'concepts' or 'objects' and 'force'"
);
}
}

if (this.moveAwayFrom) {
if (!this.moveAwayFromForce || !this.moveAwayFromConcepts) {
if (!this.moveAwayFromForce || (!this.moveAwayFromConcepts && !this.moveAwayFromObjects)) {
throw new Error(
"nearText filter: moveAwayFrom must have fields 'concepts' and 'force'"
"nearText filter: moveAwayFrom must have fields 'concepts' or 'objects' and 'force'"
);
}
}
Expand Down Expand Up @@ -111,37 +121,59 @@ export default class GraphQLNearText {
throw new Error("nearText filter: moveTo must be object");
}

if (!Array.isArray(target.concepts)) {
if (!target.concepts && !target.objects) {
throw new Error("nearText filter: moveTo.concepts or moveTo.objects must be present");
}

if (target.concepts && !Array.isArray(target.concepts)) {
throw new Error("nearText filter: moveTo.concepts must be an array");
}

if (target.objects && !Array.isArray(target.objects)) {
throw new Error("nearText filter: moveTo.objects must be an array");
}

if (target.force && typeof target.force != "number") {
throw new Error("nearText filter: moveTo.force must be a number");
}

this.moveTo = true;
this.moveToConcepts = target.concepts;
this.moveToForce = target.force;
if (target.objects) {
this.moveToObjects = this.parseMoveObjects("moveTo", target.objects);
}
}

parseMoveAwayFrom(target) {
if (typeof target !== "object") {
throw new Error("nearText filter: moveAwayFrom must be object");
}

if (!Array.isArray(target.concepts)) {
if (!target.concepts && !target.objects) {
throw new Error("nearText filter: moveAwayFrom.concepts or moveAwayFrom.objects must be present");
}

if (target.concepts && !Array.isArray(target.concepts)) {
throw new Error(
"nearText filter: moveAwayFrom.concepts must be an array"
);
}

if (target.objects && !Array.isArray(target.objects)) {
throw new Error("nearText filter: moveAwayFrom.objects must be an array");
}

if (target.force && typeof target.force != "number") {
throw new Error("nearText filter: moveAwayFrom.force must be a number");
}

this.moveAwayFrom = true;
this.moveAwayFromConcepts = target.concepts;
this.moveAwayFromForce = target.force;
if (target.objects) {
this.moveAwayFromObjects = this.parseMoveObjects("moveAwayFrom", target.objects);
}
}

parseAutocorrect(autocorrect) {
Expand All @@ -151,4 +183,31 @@ export default class GraphQLNearText {

this.autocorrect = autocorrect;
}

parseMoveObjects(move, objects) {
let moveObjects = [];
let errors = [];
for (var i in objects) {
if (!objects[i].id && !objects[i].beacon) {
errors.push(`${move}.objects[${i}].id or ${move}.objects[${i}].beacon must be present`)
} else if (objects[i].id && typeof objects[i].id !== "string") {
errors.push(`${move}.objects[${i}].id must be string`)
} else if (objects[i].beacon && typeof objects[i].beacon !== "string") {
errors.push(`${move}.objects[${i}].beacon must be string`)
} else {
var objs = []
if (objects[i].id) {
objs.push(`id:"${objects[i].id}"`);
}
if (objects[i].beacon) {
objs.push(`beacon:"${objects[i].beacon}"`);
}
moveObjects.push(`{${objs.join(",")}}`)
}
}
if (errors.length > 0) {
throw new Error(`nearText filter: ${errors.join(", ")}`);
}
return `[${moveObjects.join(",")}]`
}
}
2 changes: 2 additions & 0 deletions schema/journey.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ describe("schema", () => {
vectorizer: "text2vec-contextionary",
vectorIndexConfig: {
cleanupIntervalSeconds: 300,
distance: "cosine",
dynamicEfFactor: 8,
dynamicEfMax: 500,
dynamicEfMin: 100,
Expand Down Expand Up @@ -381,6 +382,7 @@ function newClassObject(className) {
vectorizer: 'text2vec-contextionary',
vectorIndexConfig: {
cleanupIntervalSeconds: 300,
distance: "cosine",
dynamicEfFactor: 8,
dynamicEfMax: 500,
dynamicEfMin: 100,
Expand Down