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
10 changes: 6 additions & 4 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ env:
WEAVIATE_129: 1.29.9
WEAVIATE_130: 1.30.12
WEAVIATE_131: 1.31.5
WEAVIATE_132: 1.32.4-cdf9a3b
WEAVIATE_132: 1.32.5
WEAVIATE_133: 1.33.0-rc.1

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand Down Expand Up @@ -45,9 +46,10 @@ jobs:
{ node: "22.x", weaviate: $WEAVIATE_129},
{ node: "22.x", weaviate: $WEAVIATE_130},
{ node: "22.x", weaviate: $WEAVIATE_131},
{ node: "18.x", weaviate: $WEAVIATE_132},
{ node: "20.x", weaviate: $WEAVIATE_132},
{ node: "22.x", weaviate: $WEAVIATE_132}
{ node: "22.x", weaviate: $WEAVIATE_132},
{ node: "18.x", weaviate: $WEAVIATE_133},
{ node: "20.x", weaviate: $WEAVIATE_133},
{ node: "22.x", weaviate: $WEAVIATE_133},
]
steps:
- uses: actions/checkout@v3
Expand Down
24 changes: 22 additions & 2 deletions src/collections/filters/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class Filters {
static and(...filters: FilterValue[]): FilterValue<null> {
return {
operator: 'And',
filters: filters,
filters,
value: null,
};
}
Expand All @@ -55,7 +55,19 @@ export class Filters {
static or(...filters: FilterValue[]): FilterValue<null> {
return {
operator: 'Or',
filters: filters,
filters,
value: null,
};
}
/**
* Negate a filter using the logical NOT operator.
*
* @param {FilterValue} filter The filter to negate.
*/
static not(filter: FilterValue): FilterValue<null> {
return {
operator: 'Not',
filters: [filter],
value: null,
};
}
Expand Down Expand Up @@ -140,6 +152,14 @@ export class FilterProperty<V> extends FilterBase implements FilterByProperty<V>
};
}

public containsNone<U extends ContainsValue<V>>(value: U[]): FilterValue<U[]> {
return {
operator: 'ContainsNone',
target: this.targetPath(),
value: value,
};
}

public containsAll<U extends ContainsValue<V>>(value: U[]): FilterValue<U[]> {
return {
operator: 'ContainsAll',
Expand Down
44 changes: 37 additions & 7 deletions src/collections/filters/integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
import { requireAtLeast } from '../../../test/version.js';
import weaviate, { WeaviateClient } from '../../index.js';
import { Collection } from '../collection/index.js';
import { CrossReference, Reference } from '../references/index.js';
Expand Down Expand Up @@ -120,6 +121,34 @@ describe('Testing of the filter class with a simple collection', () => {
expect(obj.uuid).toEqual(ids[1]);
});

it('should filter a fetch objects query with a contains-all filter', async () => {
const res = await collection.query.fetchObjects({
filters: collection.filter.byProperty('text').containsAll(['two']),
});
expect(res.objects.length).toEqual(1);
const obj = res.objects[0];
expect(obj.properties.text).toEqual('two');
});

it('should filter a fetch objects query with a contains-any filter', async () => {
const res = await collection.query.fetchObjects({
filters: collection.filter.byProperty('text').containsAny(['two', 'three']),
});
expect(res.objects.length).toEqual(2);
const texts = res.objects.map((o) => o.properties.text);
expect(texts).toContain('two');
expect(texts).toContain('three');
});

requireAtLeast(1, 33, 0).it('should filter a fetch objects query with a contains-none filter', async () => {
const res = await collection.query.fetchObjects({
filters: collection.filter.byProperty('text').containsNone(['one', 'three']),
});
expect(res.objects.length).toEqual(1);
const obj = res.objects[0];
expect(obj.properties.text).toEqual('two');
});

it('should filter a fetch objects query with an AND filter', async () => {
const res = await collection.query.fetchObjects({
filters: Filters.and(
Expand Down Expand Up @@ -147,15 +176,16 @@ describe('Testing of the filter class with a simple collection', () => {
// Return of fetch not necessarily in order due to filter
expect(res.objects.map((o) => o.properties.text)).toContain('two');
expect(res.objects.map((o) => o.properties.text)).toContain('three');
});

expect(res.objects.map((o) => o.properties.int)).toContain(2);
expect(res.objects.map((o) => o.properties.int)).toContain(3);

expect(res.objects.map((o) => o.properties.float)).toContain(2.2);
expect(res.objects.map((o) => o.properties.float)).toContain(3.3);
requireAtLeast(1, 33, 0).it('should filter a fetch objects query with a NOT filter', async () => {
const res = await collection.query.fetchObjects({
filters: Filters.not(collection.filter.byProperty('text').equal('one')),
});
expect(res.objects.length).toEqual(2);

expect(res.objects.map((o) => o.uuid)).toContain(ids[1]);
expect(res.objects.map((o) => o.uuid)).toContain(ids[2]);
expect(res.objects.map((o) => o.properties.text)).toContain('two');
expect(res.objects.map((o) => o.properties.text)).toContain('three');
});

it('should filter a fetch objects query with a reference filter', async () => {
Expand Down
11 changes: 10 additions & 1 deletion src/collections/filters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ export type Operator =
| 'WithinGeoRange'
| 'ContainsAny'
| 'ContainsAll'
| 'ContainsNone'
| 'And'
| 'Or';
| 'Or'
| 'Not';

export type FilterValue<V = any> = {
filters?: FilterValue[];
Expand Down Expand Up @@ -133,6 +135,13 @@ export interface FilterByProperty<T> {
* @returns {FilterValue<U[]>} The filter value.
*/
containsAll: <U extends ContainsValue<T>>(value: U[]) => FilterValue<U[]>;
/**
* Filter on whether the property contains none of the given values.
*
* @param {U[]} value The values to filter on.
* @returns {FilterValue<U[]>} The filter value.
*/
containsNone: <U extends ContainsValue<T>>(value: U[]) => FilterValue<U[]>;
/**
* Filter on whether the property is equal to the given value.
*
Expand Down
30 changes: 28 additions & 2 deletions src/collections/filters/unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('Unit testing of filters', () => {
});
});

it('should create a contains all filter with a primitive type', () => {
it('should create a contains all filter with an array type', () => {
const f = filter.byProperty('name').containsAll(['John', 'Doe']);
expect(f).toEqual<FilterValue<string[]>>({
operator: 'ContainsAll',
Expand All @@ -47,7 +47,7 @@ describe('Unit testing of filters', () => {
});
});

it('should create a contains any filter with a primitive type', () => {
it('should create a contains any filter with an array type', () => {
const f = filter.byProperty('name').containsAny(['John', 'Doe']);
expect(f).toEqual<FilterValue<string[]>>({
operator: 'ContainsAny',
Expand All @@ -69,6 +69,17 @@ describe('Unit testing of filters', () => {
});
});

it('should create a contains none filter with an array type', () => {
const f = filter.byProperty('friends').containsNone(['John', 'Doe']);
expect(f).toEqual<FilterValue<string[]>>({
operator: 'ContainsNone',
target: {
property: 'friends',
},
value: ['John', 'Doe'],
});
});

it('should create an equal filter', () => {
const f = filter.byProperty('name').equal('John');
expect(f).toEqual<FilterValue<string>>({
Expand Down Expand Up @@ -893,5 +904,20 @@ describe('Unit testing of filters', () => {
],
});
});

it('should map a NOT filter', () => {
const f = Filters.not(filter.byProperty('name').equal('John'));
const s = Serialize.filtersREST(f);
expect(s).toEqual<WhereFilter>({
operator: 'Not',
operands: [
{
operator: 'Equal',
path: ['name'],
valueText: 'John',
},
],
});
});
});
});
3 changes: 3 additions & 0 deletions src/collections/generate/mock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ class GenerateMock {
},
batchDelete: jest.fn(),
batchObjects: jest.fn(),
batchReferences: jest.fn(),
batchSend: jest.fn(),
batchStream: jest.fn(),
};
grpc.add(WeaviateDefinition, weaviateMockImpl);

Expand Down
9 changes: 8 additions & 1 deletion src/collections/serialize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,11 @@ export class Serialize {
operator: Filters_Operator.OPERATOR_OR,
filters: resolveFilters(filters),
});
case 'Not':
return FiltersGRPC.fromPartial({
operator: Filters_Operator.OPERATOR_NOT,
filters: resolveFilters(filters),
});
default:
return FiltersGRPC.fromPartial({
operator: Serialize.operator(filters.operator),
Expand Down Expand Up @@ -1568,7 +1573,7 @@ export class Serialize {

public static filtersREST = (filters: FilterValue): WhereFilter => {
const { value } = filters;
if (filters.operator === 'And' || filters.operator === 'Or') {
if (filters.operator === 'And' || filters.operator === 'Or' || filters.operator === 'Not') {
return {
operator: filters.operator,
operands: filters.filters?.map(Serialize.filtersREST),
Expand Down Expand Up @@ -1660,6 +1665,8 @@ export class Serialize {
return Filters_Operator.OPERATOR_CONTAINS_ANY;
case 'ContainsAll':
return Filters_Operator.OPERATOR_CONTAINS_ALL;
case 'ContainsNone':
return Filters_Operator.OPERATOR_CONTAINS_NONE;
case 'GreaterThan':
return Filters_Operator.OPERATOR_GREATER_THAN;
case 'GreaterThanEqual':
Expand Down
3 changes: 3 additions & 0 deletions src/collections/tenants/unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ const makeGrpcApp = () => {
search: jest.fn(),
batchDelete: jest.fn(),
batchObjects: jest.fn(),
batchReferences: jest.fn(),
batchSend: jest.fn(),
batchStream: jest.fn(),
};
const healthMockImpl: HealthServiceImplementation = {
check: (request: HealthCheckRequest): Promise<HealthCheckResponse> =>
Expand Down
3 changes: 3 additions & 0 deletions src/connection/unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,9 @@ const makeGrpcApp = () => {
errors: [],
};
}),
batchReferences: jest.fn(),
batchSend: jest.fn(),
batchStream: jest.fn(),
};
const healthMockImpl: HealthServiceImplementation = {
check: (request: HealthCheckRequest): Promise<HealthCheckResponse> =>
Expand Down
102 changes: 51 additions & 51 deletions src/data/journey.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,57 +357,57 @@ describe('data', () => {
.catch((e) => fail('it should not have errord: ' + e));
});

it('gets all things with all optional _additional params', () => {
return client.data
.getter()
.withAdditional('classification')
.withAdditional('interpretation')
.withAdditional('nearestNeighbors')
.withAdditional('featureProjection')
.withVector()
.withLimit(2)
.do()
.then((res: WeaviateObjectsList) => {
if (!res.objects) {
throw new Error(`response should have objects: ${JSON.stringify(res)}`);
}
expect(res.objects).toHaveLength(2);
expect(res.objects[0].vector?.length).toBeGreaterThan(10);
expect(res.objects[0].additional?.interpretation).toBeDefined();
expect(res.objects[0].additional?.featureProjection).toBeDefined();
expect(res.objects[0].additional?.nearestNeighbors).toBeDefined();
// not testing for classification as that's only set if the object was
// actually classified, this one wasn't
})
.catch((e: WeaviateError) => {
throw new Error('it should not have errord: ' + e);
});
});

it('gets all classes objects with all optional _additional params', () => {
return client.data
.getter()
.withClassName(thingClassName)
.withAdditional('classification')
.withAdditional('interpretation')
.withAdditional('nearestNeighbors')
.withAdditional('featureProjection')
.withVector()
.do()
.then((res: WeaviateObjectsList) => {
if (!res.objects) {
throw new Error(`response should have objects: ${JSON.stringify(res)}`);
}
expect(res.objects).toHaveLength(2);
expect(res.objects[0].vector?.length).toBeGreaterThan(10);
expect(res.objects[0].additional?.interpretation).toBeDefined();
expect(res.objects[0].additional?.featureProjection).toBeDefined();
expect(res.objects[0].additional?.nearestNeighbors).toBeDefined();
})
.catch((e: WeaviateError) => {
throw new Error('it should not have errord: ' + e);
});
});
// it('gets all things with all optional _additional params', () => {
// return client.data
// .getter()
// .withAdditional('classification')
// .withAdditional('interpretation')
// .withAdditional('nearestNeighbors')
// .withAdditional('featureProjection')
// .withVector()
// .withLimit(2)
// .do()
// .then((res: WeaviateObjectsList) => {
// if (!res.objects) {
// throw new Error(`response should have objects: ${JSON.stringify(res)}`);
// }
// expect(res.objects).toHaveLength(2);
// expect(res.objects[0].vector?.length).toBeGreaterThan(10);
// expect(res.objects[0].additional?.interpretation).toBeDefined();
// expect(res.objects[0].additional?.featureProjection).toBeDefined();
// expect(res.objects[0].additional?.nearestNeighbors).toBeDefined();
// // not testing for classification as that's only set if the object was
// // actually classified, this one wasn't
// })
// .catch((e: WeaviateError) => {
// throw new Error('it should not have errord: ' + e);
// });
// });

// it('gets all classes objects with all optional _additional params', () => {
// return client.data
// .getter()
// .withClassName(thingClassName)
// .withAdditional('classification')
// .withAdditional('interpretation')
// .withAdditional('nearestNeighbors')
// .withAdditional('featureProjection')
// .withVector()
// .do()
// .then((res: WeaviateObjectsList) => {
// if (!res.objects) {
// throw new Error(`response should have objects: ${JSON.stringify(res)}`);
// }
// expect(res.objects).toHaveLength(2);
// expect(res.objects[0].vector?.length).toBeGreaterThan(10);
// expect(res.objects[0].additional?.interpretation).toBeDefined();
// expect(res.objects[0].additional?.featureProjection).toBeDefined();
// expect(res.objects[0].additional?.nearestNeighbors).toBeDefined();
// })
// .catch((e: WeaviateError) => {
// throw new Error('it should not have errord: ' + e);
// });
// });

it('gets one thing by id only', () => {
return client.data
Expand Down
Loading