From 8180c3755b0a28794bb583c8846bea7f7be5f84c Mon Sep 17 00:00:00 2001 From: cbullinger Date: Thu, 23 May 2024 10:21:18 -0400 Subject: [PATCH] Upgrade to 12.9.0 for collections in mixed support --- .../v12/__tests__/models/rql-data-models.ts | 2 + .../__tests__/realm-query-language.test.js | 20 +- .../__tests__/realm-query-language.test.ts | 138 ++++++------ examples/node/v12/package-lock.json | 14 +- examples/node/v12/package.json | 4 +- .../query/rql-example-data-model.rst | 71 ++++++ .../query-engines/realm-query-language.txt | 210 +++++++++++++----- 7 files changed, 333 insertions(+), 126 deletions(-) create mode 100644 source/includes/sdk-examples/query/rql-example-data-model.rst diff --git a/examples/node/v12/__tests__/models/rql-data-models.ts b/examples/node/v12/__tests__/models/rql-data-models.ts index 59c60d2344..776e1d1b94 100644 --- a/examples/node/v12/__tests__/models/rql-data-models.ts +++ b/examples/node/v12/__tests__/models/rql-data-models.ts @@ -64,6 +64,7 @@ export class Project extends Realm.Object { quota?: number; comments?: Realm.Dictionary; projectLocation?: Office; + additionalInfo!: Realm.Mixed; static schema: ObjectSchema = { name: "Project", @@ -74,6 +75,7 @@ export class Project extends Realm.Object { quota: "int?", comments: "string?{}", projectLocation: "Office?", + additionalInfo: "mixed", }, primaryKey: "_id", }; diff --git a/examples/node/v12/__tests__/realm-query-language.test.js b/examples/node/v12/__tests__/realm-query-language.test.js index 8110961261..2ced76f64c 100644 --- a/examples/node/v12/__tests__/realm-query-language.test.js +++ b/examples/node/v12/__tests__/realm-query-language.test.js @@ -64,7 +64,12 @@ describe("Realm Query Language Reference", () => { }, ], quota: 1, // doesn't meet quota - comments: { status: "Behind schedule", projectNumber: "70150" }, + comments: { + status: "Behind schedule", + projectNumber: 70150, + budget: 5000.0, + customerContact: ["Mina", "Devon"], + }, projectLocation: mainBranch, }); const project = realm.create(Project, { @@ -100,7 +105,11 @@ describe("Realm Query Language Reference", () => { }, ], quota: 1, // meets quota - comments: { status: "Ahead of schedule", projectNumber: "70187" }, + comments: { + status: "Ahead of schedule", + projectNumber: 70187, + startDate: new Date("2021-01-01"), + }, projectLocation: mainBranch, }); @@ -120,7 +129,12 @@ describe("Realm Query Language Reference", () => { }, ], quota: 11, // doesn't meet quota - comments: { status: "On track", projectNumber: "N/A" }, + comments: { + status: "On track", + projectNumber: 4444, + startDate: new Date("2021-02-01"), + budget: 10000.0, + }, projectLocation: austinBranch, }); }); diff --git a/examples/node/v12/__tests__/realm-query-language.test.ts b/examples/node/v12/__tests__/realm-query-language.test.ts index 97dccebb9f..673075c14b 100644 --- a/examples/node/v12/__tests__/realm-query-language.test.ts +++ b/examples/node/v12/__tests__/realm-query-language.test.ts @@ -65,6 +65,14 @@ describe("Realm Query Language Reference", () => { quota: 1, // doesn't meet quota comments: { status: "Behind schedule", projectNumber: "70150" }, projectLocation: mainBranch, + // Mixed property is of type dictionary of mixed values + // (date, list of strings, bool, and int) + additionalInfo: { + startDate: new Date("2021-01-01"), + customerContacts: ["Alice", "Bob"], + recurringCustomer: true, + budget: 10000, + }, }); const project = realm.create("Project", { _id: new BSON.ObjectId(), @@ -101,6 +109,8 @@ describe("Realm Query Language Reference", () => { quota: 1, // meets quota comments: { status: "Ahead of schedule", projectNumber: "70187" }, projectLocation: mainBranch, + // Mixed property is of type string + additionalInfo: "Customer is a VIP.", }); realm.create("Project", { @@ -121,6 +131,16 @@ describe("Realm Query Language Reference", () => { quota: 11, // doesn't meet quota comments: { status: "On track", projectNumber: "N/A" }, projectLocation: austinBranch, + // Mixed property is of type list, containing string and nested + // dictionary of mixed values (date, boolean, and int) + additionalInfo: [ + "Customer is difficult to work with.", + { + startDate: new Date("2021-03-01"), + recurringCustomer: false, + budget: 10000, + }, + ], }); }); }); @@ -470,31 +490,49 @@ describe("Realm Query Language Reference", () => { const items = realm.objects(Item); const projects = realm.objects(Project); - const sortedUniqueAliItems = items.filtered( + const sortedItems = items.filtered( // :snippet-start: sort-distinct-limit - "assignee == 'Ali' SORT(priority DESC) DISTINCT(name) LIMIT(5)" - // :snippet-end: + // Find incomplete items, sort by `priority` + // in descending order, then sort equal `priority` + // values by `progressMinutes` in ascending order. + "isComplete == false SORT(priority DESC, progressMinutes ASC)" + // :remove-start: ); + expect(sortedItems[0].name).toBe("Demo template app"); + const distinctItems = items.filtered( + // :remove-end: - expect(sortedUniqueAliItems.length).toBe(1); - const query = + // Find high priority items, then remove from the results + // any items with duplicate `name` AND `assignee` values. + "priority >= 5 DISTINCT(name, assignee)" + // :remove-start: + ); + expect(distinctItems.length).toBe(6); + const limitItems = items.filtered( + // :remove-end: + // Find in-progress items, then return the first 10 results. + "progressMinutes > 0 && isComplete != true LIMIT(10)" + // :snippet-end: + ); + expect(limitItems[0].name).toBe("Write tests"); + const sortFirst = items.filtered( // :snippet-start: sort-distinct-limit-order-matters - "SORT(priority DESC) LIMIT(2) DISTINCT(name)"; - // Returns 2 items with the highest priority - // :remove-start: - const orderMatters = items.filtered( - "isComplete != true SORT(priority DESC) LIMIT(2) DISTINCT(name)" + // 1. Sorts by highest priority. + // 2. Returns the first item. + // 3. Remove duplicate names (N/A because a single + // item is always considered distinct). + "assignee == null SORT(priority ASC) LIMIT(1) DISTINCT(name)" + // :remove-start: ); - expect(orderMatters.length).toBe(2); - const query2 = + const limitLast = items.filtered( // :remove-end: - "isComplete != true DISTINCT(name) SORT(priority DESC) LIMIT(2)"; - // Returns the first 2 uniquely named items with the highest priority - // :snippet-end: - const limitFirst = items.filtered( - "isComplete != true DISTINCT(name) SORT(priority DESC) LIMIT(2)" + + // 1. Removes any duplicates by name. + // 2. Sorts by highest priority. + // 3. Returns the first item. + "assignee == null DISTINCT(name) SORT(priority ASC) LIMIT(1)" + // :snippet-end: ); - console.log(limitFirst.toJSON()); }); test("Subquery queries", () => { @@ -672,58 +710,34 @@ describe("Realm Query Language Reference", () => { }); }); - describe("Type operator", () => { - // Uses a test-specific schema with mixed type - // TODO: Update main schema with mixed type property once collections-in-mixed is supported - const Mixed = { - name: "Mixed", - properties: { name: "string", mixedType: "mixed" }, - }; - let realm: Realm; - const path = "mixed.realm"; - - // Add, then delete objects for this test - beforeEach(async () => { - realm = await Realm.open({ - schema: [Mixed], - path, - }); - realm.write(() => { - realm.create("Mixed", { - name: "Marge", - mixedType: true, - }); - realm.create("Mixed", { - name: "Lisa", - mixedType: 22, - }); - realm.create("Mixed", { - name: "Bart", - mixedType: "carrumba", - }); - }); - }); - - afterEach(() => { - realm.close(); - Realm.deleteFile({ path }); - }); - + describe("Type operators", () => { test("Type operator", () => { - const mixed = realm.objects("Mixed"); - const mixedString = mixed.filtered( + const projects = realm.objects(Project); + const mixedString = projects.filtered( // :snippet-start: type-operator - "mixedType.@type == 'string'" + // Find projects with an `additionalInfo` property of + // string type. + "additionalInfo.@type == 'string'" + // :remove-start: + ); + const mixedCollection = projects.filtered( + // :remove-end: + // Find projects with an `additionalInfo` property of + // `collection` type, which matches list or dictionary types. + "additionalInfo.@type == 'collection'" // :remove-start: ); - const mixedBool = mixed.filtered( + const mixedBool = projects.filtered( // :remove-end: - "mixedType.@type == 'bool'" + // Find projects with an `additionalInfo` property of + // list type, where any list element is of type 'bool'. + "additionalInfo[*].@type == 'bool'" // :snippet-end: ); - expect(mixedString.length).toBe(1); - expect(mixedBool.length).toBe(1); + console.debug(mixedString[1].name + " and " + mixedBool[0].name); + // expect(mixedString.length).toBe(1); + // expect(mixedBool.length).toBe(1); }); }); diff --git a/examples/node/v12/package-lock.json b/examples/node/v12/package-lock.json index d51e9b2280..a2cd94e3d4 100644 --- a/examples/node/v12/package-lock.json +++ b/examples/node/v12/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "fs-extra": "^11.1.1", - "realm": "^12.8.0" + "realm": "^12.9.0" }, "devDependencies": { "@babel/core": "^7.21.8", @@ -7462,9 +7462,9 @@ } }, "node_modules/realm": { - "version": "12.8.0", - "resolved": "https://registry.npmjs.org/realm/-/realm-12.8.0.tgz", - "integrity": "sha512-U1w5+ncyURQFQTrshoGn3KV+pzR1rQlPT7s3Sw6HPIPVBH80EWU3mirwvqp6RQ+Qi32ctRrBMTNeGd5mzAyiSw==", + "version": "12.9.0", + "resolved": "https://registry.npmjs.org/realm/-/realm-12.9.0.tgz", + "integrity": "sha512-K7JblaSaqqFEu8kObgTCo1ITTYnR8rXYBdb5/5VXxD4VNKQVlrnO6Z4GhxDlohubmF8eNvRdC2wlHAsv2aUsOg==", "hasInstallScript": true, "dependencies": { "@realm/fetch": "^0.1.1", @@ -13846,9 +13846,9 @@ } }, "realm": { - "version": "12.8.0", - "resolved": "https://registry.npmjs.org/realm/-/realm-12.8.0.tgz", - "integrity": "sha512-U1w5+ncyURQFQTrshoGn3KV+pzR1rQlPT7s3Sw6HPIPVBH80EWU3mirwvqp6RQ+Qi32ctRrBMTNeGd5mzAyiSw==", + "version": "12.9.0", + "resolved": "https://registry.npmjs.org/realm/-/realm-12.9.0.tgz", + "integrity": "sha512-K7JblaSaqqFEu8kObgTCo1ITTYnR8rXYBdb5/5VXxD4VNKQVlrnO6Z4GhxDlohubmF8eNvRdC2wlHAsv2aUsOg==", "requires": { "@realm/fetch": "^0.1.1", "bson": "^4.7.2", diff --git a/examples/node/v12/package.json b/examples/node/v12/package.json index 70ba51024f..2aa2508fb3 100644 --- a/examples/node/v12/package.json +++ b/examples/node/v12/package.json @@ -5,7 +5,7 @@ "main": "index.js", "type": "module", "scripts": { - "test": "jest --runInBand --detectOpenHandles --forceExit", + "test": "jest --runInBand --detectOpenHandles --silent=false --forceExit", "posttest": "npm run delete-realm-files", "test:js": "jest --selectProjects JavaScript --runInBand --detectOpenHandles --forceExit; npm run delete-realm-files", "test:ts": "NODE_OPTIONS=--experimental-vm-modules jest --selectProjects TypeScript --runInBand --detectOpenHandles --forceExit; npm run delete-realm-files", @@ -15,7 +15,7 @@ "license": "ISC", "dependencies": { "fs-extra": "^11.1.1", - "realm": "^12.8.0" + "realm": "^12.9.0" }, "devDependencies": { "@babel/core": "^7.21.8", diff --git a/source/includes/sdk-examples/query/rql-example-data-model.rst b/source/includes/sdk-examples/query/rql-example-data-model.rst new file mode 100644 index 0000000000..7f866902ab --- /dev/null +++ b/source/includes/sdk-examples/query/rql-example-data-model.rst @@ -0,0 +1,71 @@ +.. tabs-drivers:: + + tabs: + - id: cpp + content: | + + .. literalinclude:: /examples/generated/cpp/asymmetric-sync.snippet.create-asymmetric-object.cpp + :language: cpp + + - id: csharp + content: | + + .. literalinclude:: /examples/generated/dotnet/RqlSchemaExamples.snippet.rql-schema-examples.cs + :language: csharp + + - id: dart + content: | + + .. literalinclude:: /examples/generated/flutter/task_project_models_test.snippet.task-project-models.dart + :language: dart + + - id: java + content: | + + .. code-block:: java + + public class Item extends RealmObject { + ObjectId id = new ObjectId(); + String name; + Boolean isComplete = false; + String assignee; + Integer priority = 0; + Integer progressMinutes = 0; + @LinkingObjects("items") + final RealmResults projects = null; + } + public class Project extends RealmObject { + ObjectId id = new ObjectId(); + String name; + RealmList items; + Integer quota = null; + } + + - id: javascript + content: | + + .. literalinclude:: /examples/generated/node/rql-data-models.snippet.rql-data-models.js + :language: javascript + + - id: kotlin + content: | + + .. literalinclude:: /examples/generated/kotlin/RQLTest.snippet.rql-schema-example.kt + :language: kotlin + + - id: objectivec + content: | + + .. literalinclude:: /examples/MissingPlaceholders/api.m + :language: objectivec + + - id: swift + content: | + + .. literalinclude:: /examples/MissingPlaceholders/api.swift + :language: swift + + - id: typescript + content: | + + .. include:: /examples/generated/node/v12/formatted/rql-data-models.snippet.rql-data-models.ts.rst diff --git a/source/sdk/crud/query-engines/realm-query-language.txt b/source/sdk/crud/query-engines/realm-query-language.txt index 3eb1f675e5..6220062176 100644 --- a/source/sdk/crud/query-engines/realm-query-language.txt +++ b/source/sdk/crud/query-engines/realm-query-language.txt @@ -80,16 +80,41 @@ See the schema for these two classes, ``Project`` and ``Item``, below: Query Syntax ------------ +Expressions and Predicates +~~~~~~~~~~~~~~~~~~~~~~~~~~ +Realm Query Language uses expressions and predicates to evaluate queries. -Dot Notation -~~~~~~~~~~~~ +- **Expressions** are the building blocks of queries. They can be simple or complex. + For example, ``name == 'Ali'`` is a simple expression, while + ``name == 'Ali' AND priority > 5`` is a complex expression. +- **Predicates** are expressions that evaluate to ``true`` or ``false``. + For example, ``name == 'Ali'`` is a predicate that evaluates to ``true`` + if the name property is 'Ali'. -You can use **dot notation** to refer to child properties of an object, including the properties of embedded objects and relationships: +TRUEPREDICATE and FALSEPREDICATE + +Case Sensitivity +~~~~~~~~~~~~~~~~ + +Unless otherwise noted, RQL operators and keywords are case insensitive. + +``[c]`` denotes case sensitivity in the following examples. + +Dot & Bracket Notation +~~~~~~~~~~~~~~~~~~~~~~ + +.. TODO: confirm how we want to communicate which SDK versions support bracket notation + +You can use **dot notation** or **bracket notation** to refer to child +properties of an object, including the properties of embedded objects and +relationships: + +.. TODO: add bracket notation to example .. include:: /examples/generated/node/v12/formatted/realm-query-language.test.snippet.dot-notation.ts.rst -You can also chain dot notations for nested properties. +You can also chain these notations for nested properties. For example, each Project has a ``projectLocation`` property that refers to an Office object, which itself contains an embedded object property ``address``. You can chain dot notations to query the deeply nested @@ -98,9 +123,7 @@ property ``address``. You can chain dot notations to query the deeply nested .. include:: /examples/generated/node/v12/formatted/realm-query-language.test.snippet.deep-dot-notation.ts.rst -.. TODO: add as part of collections-in-mixed epic -Bracket Notation -~~~~~~~~~~~~~~~~ + .. _rql-parameterized-queries: Parameterized vs. Serialized Queries @@ -218,6 +241,78 @@ property values match certain criteria: including decimal, float, and Decimal128. +.. _rql-string-operators: + +String Operators +~~~~~~~~~~~~~~~~ + +Compare string values using these string operators. +Regex-like wildcards allow more flexibility in search. + +.. note:: + + You can use the following modifiers with the string operators: + + - ``[c]`` for case insensitivity. + + .. code-block:: javascript + + "name CONTAINS[c] $0", 'a' + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Operator + - Description + + * - | ``BEGINSWITH`` + - Evaluates to ``true`` if the left-hand string expression begins with + the right-hand string expression. This is similar to ``contains``, + but only matches if the right-hand string expression is found + at the beginning of the left-hand string expression. + + * - | ``CONTAINS`` + - Evaluates to ``true`` if the right-hand string expression + is found anywhere in the left-hand string expression. + + * - | ``ENDSWITH`` + - Evaluates to ``true`` if the left-hand string expression ends + with the right-hand string expression. This is similar to ``contains``, + but only matches if the left-hand string expression is found + at the very end of the right-hand string expression. + + * - | ``LIKE`` + - Evaluates to ``true`` if the left-hand string expression + matches the right-hand string wildcard string + expression. A wildcard string expression is a string + that uses normal characters with two special wildcard + characters: + + - The ``*`` wildcard matches zero or more of any character + - The ``?`` wildcard matches any character. + + For example, the wildcard string "d?g" matches "dog", + "dig", and "dug", but not "ding", "dg", or "a dog". + + * - | ``==``, ``=`` + - Evaluates to ``true`` if the left-hand string is lexicographically equal + to the right-hand string. + + * - | ``!=``, ``<>`` + - Evaluates to ``true`` if the left-hand string is not lexicographically + equal to the right-hand string. + +.. example:: + + We use the query engine's string operators to find: + + - Projects with a name starting with the letter 'e' + - Projects with names that contain 'ie' + + .. literalinclude:: /examples/generated/node/v12/formatted/realm-query-language.test.snippet.string-operators.js + :language: javascript + .. _rql-logical-operators: Logical Operators @@ -282,6 +377,7 @@ The following example uses arithmetic operators on Item object properties contai Aggregate Operators ~~~~~~~~~~~~~~~~~~~ +.. TODO: link to collection query section Aggregate operators traverse a collection and reduce it to a single value. You can use aggregate operators on collection properties, such as a list, or on @@ -314,27 +410,19 @@ You can use aggregate operators on collection properties, such as a list, or on excluding ``null`` values. -The following example uses aggregate operators to find project whose ``items`` +The following example uses aggregate operators to find projects whose ``items`` collection property meets certain criteria: .. include:: /examples/generated/node/v12/formatted/realm-query-language.test.snippet.aggregate-operators.ts.rst .. _rql-sort-distinct-limit: -Sort and Limit Operators -~~~~~~~~~~~~~~~~~~~~~~~~ - -Use sort and limit operators to shape your query results collection. You can -combine these operators across multiple properties. - - For example, if you ``SORT (priority DESC, name DESC)``, the query - returns sorted by priority, and then by name when priority - value is the same. +Sort, Distinct, and Limit Operators +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - - -Sort and limit the results collection of your query using additional operators. +Use sort, distinct, and limit operators to shape your query results collection. You can +combine these operators in a single query across multiple properties. Operators +are applied in the order they appear in the query. .. list-table:: :header-rows: 1 @@ -344,51 +432,69 @@ Sort and limit the results collection of your query using additional operators. - Description * - ``SORT`` (``ASC`` or ``DESC``) - - Specify the name of the property to compare, and whether to sort by - ascending (``ASC``) or descending (``DESC``) order. If you specify - multiple SORT fields, you must specify sort order for each field. - With multiple sort fields, the query sorts by the first field, and - then the second. + - Sort the results collection by the specified property or properties, either in ascending (``ASC``) or descending + (``DESC``) order. Separate multiple properties by comma and define the + sort order for each property. The SDK applies each sort operation one at a time, in order. * - ``DISTINCT`` - - Specify a name of the property to compare. Remove duplicates - for that property in the results collection. If you specify multiple - DISTINCT fields, the query removes duplicates by the first field, and - then the second. For example, if you ``DISTINCT (name, assignee)``, - the query only removes duplicates where the values of both properties - are the same. + - Remove duplicates of the specified property or properties from the + results collection. Separate multiple properties by comma. The SDK + applies all distinct operations as a single AND condition, + where duplicates must match all specified properties. * - ``LIMIT`` - Limit the results collection to the specified number. -.. example:: +The following example finds all incomplete items, then +shapes the returned results using sort, distinct, and limit operators: - Use the query engine's sort, distinct, and limit operators to find to-do items - where the assignee is Ali: +.. include:: /examples/generated/node/v12/formatted/realm-query-language.test.snippet.sort-distinct-limit.ts.rst - - Sorted by priority in descending order - - Enforcing uniqueness by name - - Limiting the results to 5 items +.. important:: Order Matters - .. literalinclude:: /examples/generated/node/v12/formatted/realm-query-language.test.snippet.sort-distinct-limit.js - :language: javascript + The SDK executes queries in order. This includes the order of any ``SORT``, ``DISTINCT``, and ``LIMIT`` operators in + the query *and* the order of any properties within those operators. + This can greatly impact the results returned. For example, sorting a query + before limiting it can return very different results than sorting *after* + limiting it. -.. important:: Order Matters + .. include:: /examples/generated/node/v12/formatted/realm-query-language.test.snippet.sort-distinct-limit-order-matters.ts.rst + + +Data Type Operators +------------------- + +.. _rql-type-operator: + +Type Operator +~~~~~~~~~~~~~ + +Check the data type of a mixed type or dictionary property using the +``@type`` operator. Evaluate the property against a string representation of the +data type name using string comparison operators. - Queries are executed in order. This includes the order of the operators in - the query *and* the order of properties within an operator, if applicable. - This can greatly impact the results returned. For example, sorting a query before - limiting it can return very different results than sorting *after* limiting - it. +For information on how each SDK language's data types map to database-specific data types, refer to :ref:`sdks-data-types`. + +You can currently only use the type operator with mixed types and +dictionaries. + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Operator + - Description - .. .. include:: /includes/file.rst - :start-after: text to start after (Optional} - :end-before: text to end before (Optional} + * - ``@type`` + - Check if type of a property is the specified data type represented as a string. + Use ``==`` and ``!=`` to compare equality. - For example, +The following example uses the ``@type`` operator to find projects +whose mixed data type ``additionalInfo`` property type matches specific +criteria: - - ``LIMIT(5) SORT(priority DESC)`` returns - the top 5 items in the entire collection, which are then sorted by priority. However, ``SORT(priority DESC) LIMIT(5)`` +.. literalinclude:: /examples/generated/node/v12/formatted/realm-query-language.test.snippet.type-operator.js + :language: js Data Type Queries