Skip to content

Commit

Permalink
Merge d0c1c17 into 29486fc
Browse files Browse the repository at this point in the history
  • Loading branch information
papafe committed May 24, 2023
2 parents 29486fc + d0c1c17 commit 49a5351
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* Added support for a sync configuration option to provide an `SSLConfiguration` with a custom function for validating the server's SSL certificate. ([#5485](https://github.com/realm/realm-js/issues/5485))
* Improve performance of equality queries on a non-indexed mixed property by about 30%. ([realm/realm-core#6506](https://github.com/realm/realm-core/pull/6506))
* Improve performance of rolling back write transactions after making changes. ([realm/realm-core#6513](https://github.com/realm/realm-core/pull/6513))
* Extended `PropertySchema.indexed` with the `full-text` option, that allows to create an index for full-text search queries. ([#5755](https://github.com/realm/realm-js/issues/5755))

### Fixed
* Fix a stack overflow crash when using the query parser with long chains of AND/OR conditions. ([realm/realm-core#6428](https://github.com/realm/realm-core/pull/6428), since v10.11.0)
Expand Down
90 changes: 89 additions & 1 deletion integration-tests/tests/src/tests/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
import Realm, { BSON } from "realm";
import Realm, { BSON, ObjectSchema } from "realm";
import { expect } from "chai";
import { openRealmBeforeEach } from "../hooks";
import { IPerson, PersonSchema } from "../schemas/person-and-dogs";
Expand Down Expand Up @@ -63,6 +63,25 @@ class NullableTypesObject extends Realm.Object implements INullableTypesObject {
};
}

interface IStory {
title?: string;
content?: string;
}

class Story extends Realm.Object<Story> implements IStory {
title?: string;
content?: string;

static schema: ObjectSchema = {
name: "Story",
properties: {
title: { type: "string" },
content: { type: "string", indexed: "full-text" },
},
primaryKey: "title",
};
}

type QueryLengthPair = [ExpectedLength: number, Query: string, ...QueryArgs: Array<any>];
type QueryExceptionPair = [ExpectedException: string, Query: string, ...QueryArgs: Array<any>];
type QueryResultsPair = [ExpectedResults: any[], Query: string, ...QueryArgs: Array<any>];
Expand Down Expand Up @@ -131,6 +150,75 @@ const expectQueryResultValues = (
};

describe("Queries", () => {
describe("Full text search", () => {
openRealmBeforeEach({ schema: [Story] });

const story1: IStory = {
title: "Dogs and cats",
content: "A short story about a dog running after two cats",
};

const story2: IStory = {
title: "Adventure",
content: "A novel about two friends looking for a treasure",
};

const story3: IStory = {
title: "A friend",
content: "A short poem about friendship",
};

const story4: IStory = {
title: "Lord of the rings",
content: "A long story about the quest for a ring",
};

beforeEach(function (this: RealmContext) {
this.realm.write(() => {
this.realm.create("Story", story1);
this.realm.create("Story", story2);
this.realm.create("Story", story3);
this.realm.create("Story", story4);
});
});

it("single term", function (this: RealmContext) {
expectQueryResultValues(this.realm, Story, "title", [[[story1.title], "content TEXT 'cats'"]]);
expectQueryResultValues(this.realm, Story, "title", [[[story1.title, story4.title], "content TEXT 'story'"]]);
});

it("multiple terms", function (this: RealmContext) {
expectQueryResultValues(this.realm, Story, "title", [[[story1.title], "content TEXT 'two dog'"]]);
expectQueryResultValues(this.realm, Story, "title", [[[story3.title], "content TEXT 'poem short friendship'"]]);
expectQueryResultValues(this.realm, Story, "title", [
[[story1.title, story2.title, story3.title, story4.title], "content TEXT 'about a'"],
]);
});

it("exclude term", function (this: RealmContext) {
expectQueryResultValues(this.realm, Story, "title", [[[story4.title], "content TEXT 'story -cats'"]]);
});

it("empty results", function (this: RealmContext) {
expectQueryResultValues(this.realm, Story, "title", [[[], "content TEXT 'two dog friends'"]]);
expectQueryResultValues(this.realm, Story, "title", [[[], "content TEXT 'amazing'"]]);
});

it("query parameters", function (this: RealmContext) {
expectQueryResultValues(this.realm, Story, "title", [[[story1.title], "content TEXT $0", "cats"]]);
expectQueryResultValues(this.realm, Story, "title", [[[story1.title], "content TEXT $0", "two dog"]]);
expectQueryResultValues(this.realm, Story, "title", [[[story4.title], "content TEXT $0", "story -cats"]]);

expectQueryResultValues(this.realm, Story, "title", [[[story1.title], "content TEXT $0", "'cats'"]]);
expectQueryResultValues(this.realm, Story, "title", [[[story1.title], "content TEXT $0", "'two dog'"]]);
expectQueryResultValues(this.realm, Story, "title", [[[story4.title], "content TEXT $0", "'story -cats'"]]);
});

it("throws on column with no index", function (this: RealmContext) {
expectQueryException(this.realm, Story, [["Column has no fulltext index", "title TEXT 'cats'"]]);
});
});

describe("Basic types", () => {
openRealmBeforeEach({ schema: [NullableTypesObject] });

Expand Down
52 changes: 52 additions & 0 deletions integration-tests/tests/src/tests/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,56 @@ describe("Realm schema", () => {
expect(obj.simpleValue).to.equal(123);
});
});

describe("Schema validation", () => {
it("throws on invalid indexed type", () => {
expect(() => {
new Realm({
schema: [
{
name: "testProp",
properties: {
content: { type: "string", indexed: 22 },
},
},
],
});
}).throws(
"Invalid type declaration for property 'content' on 'testProp': Expected 'content.indexed' on 'testProp' to be a boolean or 'full-text'",
);
});
});

it("throws when declaring full-text index on non string property", () => {
expect(() => {
new Realm({
schema: [
{
name: "testProp",
properties: {
num: { type: "int", indexed: "full-text" },
},
},
],
});
}).throws("Index not supported for this property: num");
});

it("throws when declaring full-text index on primary key", () => {
expect(() => {
new Realm({
schema: [
{
name: "testProp",
properties: {
myString: { type: "string", indexed: "full-text" },
},
primaryKey: "myString",
},
],
});
}).throws(
"Invalid type declaration for property 'myString' on 'testProp': 'myString on 'testProp' cannot be both a primary key and have a full-text index",
);
});
});
2 changes: 1 addition & 1 deletion packages/realm/bindgen/vendor/realm-core
Submodule realm-core updated 1 files
+3 −0 bindgen/spec.yml
1 change: 1 addition & 0 deletions packages/realm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export {
ProgressRealmPromise,
PropertiesTypes,
PropertySchema,
PropertySchemaCommon,
PropertySchemaParseError,
PropertySchemaShorthand,
PropertySchemaStrict,
Expand Down
4 changes: 2 additions & 2 deletions packages/realm/src/schema/from-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ export function fromBindingObjectSchema({
* @internal
*/
export function fromBindingPropertySchema(propertySchema: BindingProperty): CanonicalPropertySchema {
const { name, isIndexed, publicName } = propertySchema;
const { name, isIndexed, isFulltextIndexed, publicName } = propertySchema;
const result: CanonicalPropertySchema = {
name,
indexed: isIndexed,
indexed: isFulltextIndexed ? "full-text" : isIndexed,
mapTo: name,
...fromBindingPropertyTypeName(propertySchema),
};
Expand Down
3 changes: 2 additions & 1 deletion packages/realm/src/schema/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,10 @@ function normalizePropertySchemaObject(info: PropertyInfoUsingObject): Canonical
name: info.propertyName,
type: type as PropertyTypeName,
optional: !!optional,
indexed: !!indexed,
indexed: indexed !== undefined ? indexed : false,
mapTo: propertySchema.mapTo || info.propertyName,
};

// Add optional properties only if defined (tests expect no 'undefined' properties)
if (objectType !== undefined) normalizedSchema.objectType = objectType;
if (property !== undefined) normalizedSchema.property = property;
Expand Down
8 changes: 7 additions & 1 deletion packages/realm/src/schema/to-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,16 @@ export function toBindingPropertySchema(name: string, schema: CanonicalPropertyS
const result: BindingProperty = {
name,
type: toBindingPropertyType(schema),
isIndexed: schema.indexed,
objectType: schema.objectType && schema.objectType in TYPE_MAPPINGS ? undefined : schema.objectType,
linkOriginPropertyName: schema.property,
};

if (schema.indexed === "full-text") {
result.isFulltextIndexed = true;
} else {
result.isIndexed = schema.indexed;
}

if (schema.mapTo && schema.mapTo !== schema.name) {
result.publicName = result.name;
result.name = schema.mapTo;
Expand Down
17 changes: 12 additions & 5 deletions packages/realm/src/schema/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,21 @@ export type CanonicalRealmSchema = CanonicalObjectSchema[];
*/
export type CanonicalObjectSchemaProperty = CanonicalPropertySchema;

/**
* The type of index on a property:
* - `true` enables a regular index
* - `"full-text"` enables a full-text search index and can only be applied to string properties.
*/
export type IndexedType = boolean | "full-text";

/**
* The canonical representation of the schema of a specific property.
*/
export type CanonicalPropertySchema = {
name: string;
type: PropertyTypeName;
optional: boolean;
indexed: boolean;
indexed: IndexedType;
mapTo: string; // TODO: Make this optional and leave it out when it equals the name
objectType?: string;
property?: string;
Expand Down Expand Up @@ -239,11 +246,11 @@ export type PropertySchema = {
*/
optional?: boolean;
/**
* Whether the property should be indexed.
* The type of index applied to the property.
*
* Default value: `false` if the property is not a primary key, otherwise `true`.
*/
indexed?: boolean;
indexed?: IndexedType;
/**
* The name to be persisted in the Realm file if it differs from the already-defined
* JavaScript/TypeScript (JS/TS) property name. This is useful for allowing different
Expand All @@ -261,8 +268,8 @@ export type PropertySchema = {
/**
* Keys used in the property schema that are common among all variations of {@link PropertySchemaStrict}.
*/
type PropertySchemaCommon = {
indexed?: boolean;
export type PropertySchemaCommon = {
indexed?: IndexedType;
mapTo?: string;
default?: unknown;
};
Expand Down
20 changes: 15 additions & 5 deletions packages/realm/src/schema/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ import {
Configuration,
DefaultObject,
ObjectSchema,
ObjectSchemaParseError,
PropertySchema,
PropertySchemaParseError,
RealmObject,
RealmObjectConstructor,
SchemaParseError,
assert,
PropertySchemaParseError,
ObjectSchemaParseError,
} from "../internal";

// Need to use `CanonicalObjectSchema` rather than `ObjectSchema` due to some
Expand Down Expand Up @@ -120,7 +119,7 @@ export function validateObjectSchema(
const propertySchema = properties[propertyName];
const isUsingShorthand = typeof propertySchema === "string";
if (!isUsingShorthand) {
validatePropertySchema(objectName, propertyName, propertySchema);
validatePropertySchema(objectName, propertyName, propertySchema, primaryKey);
}
}
}
Expand Down Expand Up @@ -149,6 +148,7 @@ export function validatePropertySchema(
objectName: string,
propertyName: string,
propertySchema: unknown,
primaryKey: string | undefined,
): asserts propertySchema is PropertySchema {
try {
assert.object(propertySchema, `'${propertyName}' on '${objectName}'`, { allowArrays: false });
Expand All @@ -164,7 +164,17 @@ export function validatePropertySchema(
assert.string(property, `'${propertyName}.property' on '${objectName}'`);
}
if (indexed !== undefined) {
assert.boolean(indexed, `'${propertyName}.indexed' on '${objectName}'`);
assert(
typeof indexed === "boolean" || indexed === "full-text",
`Expected '${propertyName}.indexed' on '${objectName}' to be a boolean or 'full-text'.`,
);

if (indexed == "full-text" && typeof primaryKey == "string") {
assert(
primaryKey != propertyName,
`'${propertyName} on '${objectName}' cannot be both a primary key and have a full-text index.`,
);
}
}
if (mapTo !== undefined) {
assert.string(mapTo, `'${propertyName}.mapTo' on '${objectName}'`);
Expand Down
13 changes: 13 additions & 0 deletions packages/realm/src/tests/schema-normalization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,19 @@ describe("normalizePropertySchema", () => {
{ isPrimaryKey: false },
);

itNormalizes(
{
type: "string",
indexed: "full-text",
},
{
type: "string",
indexed: "full-text",
optional: false,
},
{ isPrimaryKey: false },
);

itNormalizes(
{
type: "string",
Expand Down
2 changes: 1 addition & 1 deletion packages/realm/src/tests/schema-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ describe("validatePropertySchema", () => {
type: "",
indexed: NOT_A_BOOLEAN,
},
`Expected '${PROPERTY_NAME}.indexed' on '${OBJECT_NAME}' to be a boolean, got a number`,
`Expected '${PROPERTY_NAME}.indexed' on '${OBJECT_NAME}' to be a boolean or 'full-text'`,
);

itThrowsWhenValidating(
Expand Down

0 comments on commit 49a5351

Please sign in to comment.