From 74f1e2828cde8429edcb1e496c9bc1c962365e58 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:17:13 -0800 Subject: [PATCH 1/6] build: update gapic-generator-typescript to v4.4.1 (#1232) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build: update gapic-generator-typescript to v4.4.1 PiperOrigin-RevId: 604765466 Source-Link: https://github.com/googleapis/googleapis/commit/40203ca1880849480bbff7b8715491060bbccdf1 Source-Link: https://github.com/googleapis/googleapis-gen/commit/07b7f3dad8aa1912d4acdcfd6365bb4236e4b54b Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMDdiN2YzZGFkOGFhMTkxMmQ0YWNkY2ZkNjM2NWJiNDIzNmU0YjU0YiJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot Co-authored-by: danieljbruce --- src/v1/datastore_admin_client.ts | 15 +++------------ src/v1/datastore_client.ts | 15 +++------------ test/gapic_datastore_admin_v1.ts | 14 ++++---------- test/gapic_datastore_v1.ts | 14 ++++---------- 4 files changed, 14 insertions(+), 44 deletions(-) diff --git a/src/v1/datastore_admin_client.ts b/src/v1/datastore_admin_client.ts index 9e2b96bb..87e88839 100644 --- a/src/v1/datastore_admin_client.ts +++ b/src/v1/datastore_admin_client.ts @@ -418,7 +418,7 @@ export class DatastoreAdminClient { /** * The DNS address for this API service. - * @deprecated + * @deprecated Use the apiEndpoint method of the client instance. * @returns {string} The DNS address for this service. */ static get servicePath() { @@ -435,9 +435,8 @@ export class DatastoreAdminClient { } /** - * The DNS address for this API service - same as servicePath, - * exists for compatibility reasons. - * @deprecated + * The DNS address for this API service - same as servicePath. + * @deprecated Use the apiEndpoint method of the client instance. * @returns {string} The DNS address for this service. */ static get apiEndpoint() { @@ -457,14 +456,6 @@ export class DatastoreAdminClient { * The DNS address for this API service. * @returns {string} The DNS address for this service. */ - get servicePath() { - return this._servicePath; - } - - /** - * The DNS address for this API service - same as servicePath(). - * @returns {string} The DNS address for this service. - */ get apiEndpoint() { return this._servicePath; } diff --git a/src/v1/datastore_client.ts b/src/v1/datastore_client.ts index 3a8f603a..d482632e 100644 --- a/src/v1/datastore_client.ts +++ b/src/v1/datastore_client.ts @@ -318,7 +318,7 @@ export class DatastoreClient { /** * The DNS address for this API service. - * @deprecated + * @deprecated Use the apiEndpoint method of the client instance. * @returns {string} The DNS address for this service. */ static get servicePath() { @@ -335,9 +335,8 @@ export class DatastoreClient { } /** - * The DNS address for this API service - same as servicePath, - * exists for compatibility reasons. - * @deprecated + * The DNS address for this API service - same as servicePath. + * @deprecated Use the apiEndpoint method of the client instance. * @returns {string} The DNS address for this service. */ static get apiEndpoint() { @@ -357,14 +356,6 @@ export class DatastoreClient { * The DNS address for this API service. * @returns {string} The DNS address for this service. */ - get servicePath() { - return this._servicePath; - } - - /** - * The DNS address for this API service - same as servicePath(). - * @returns {string} The DNS address for this service. - */ get apiEndpoint() { return this._servicePath; } diff --git a/test/gapic_datastore_admin_v1.ts b/test/gapic_datastore_admin_v1.ts index 10a29499..fe1cdb11 100644 --- a/test/gapic_datastore_admin_v1.ts +++ b/test/gapic_datastore_admin_v1.ts @@ -161,12 +161,6 @@ function stubAsyncIterationCall( describe('v1.DatastoreAdminClient', () => { describe('Common methods', () => { - it('has servicePath', () => { - const client = new datastoreadminModule.v1.DatastoreAdminClient(); - const servicePath = client.servicePath; - assert.strictEqual(servicePath, 'datastore.googleapis.com'); - }); - it('has apiEndpoint', () => { const client = new datastoreadminModule.v1.DatastoreAdminClient(); const apiEndpoint = client.apiEndpoint; @@ -201,19 +195,19 @@ describe('v1.DatastoreAdminClient', () => { stub.restore(); }); } - it('sets servicePath according to universe domain camelCase', () => { + it('sets apiEndpoint according to universe domain camelCase', () => { const client = new datastoreadminModule.v1.DatastoreAdminClient({ universeDomain: 'example.com', }); - const servicePath = client.servicePath; + const servicePath = client.apiEndpoint; assert.strictEqual(servicePath, 'datastore.example.com'); }); - it('sets servicePath according to universe domain snakeCase', () => { + it('sets apiEndpoint according to universe domain snakeCase', () => { const client = new datastoreadminModule.v1.DatastoreAdminClient({ universe_domain: 'example.com', }); - const servicePath = client.servicePath; + const servicePath = client.apiEndpoint; assert.strictEqual(servicePath, 'datastore.example.com'); }); it('does not allow setting both universeDomain and universe_domain', () => { diff --git a/test/gapic_datastore_v1.ts b/test/gapic_datastore_v1.ts index 616a6c13..331cd1f9 100644 --- a/test/gapic_datastore_v1.ts +++ b/test/gapic_datastore_v1.ts @@ -89,12 +89,6 @@ function stubAsyncIterationCall( describe('v1.DatastoreClient', () => { describe('Common methods', () => { - it('has servicePath', () => { - const client = new datastoreModule.v1.DatastoreClient(); - const servicePath = client.servicePath; - assert.strictEqual(servicePath, 'datastore.googleapis.com'); - }); - it('has apiEndpoint', () => { const client = new datastoreModule.v1.DatastoreClient(); const apiEndpoint = client.apiEndpoint; @@ -127,19 +121,19 @@ describe('v1.DatastoreClient', () => { stub.restore(); }); } - it('sets servicePath according to universe domain camelCase', () => { + it('sets apiEndpoint according to universe domain camelCase', () => { const client = new datastoreModule.v1.DatastoreClient({ universeDomain: 'example.com', }); - const servicePath = client.servicePath; + const servicePath = client.apiEndpoint; assert.strictEqual(servicePath, 'datastore.example.com'); }); - it('sets servicePath according to universe domain snakeCase', () => { + it('sets apiEndpoint according to universe domain snakeCase', () => { const client = new datastoreModule.v1.DatastoreClient({ universe_domain: 'example.com', }); - const servicePath = client.servicePath; + const servicePath = client.apiEndpoint; assert.strictEqual(servicePath, 'datastore.example.com'); }); it('does not allow setting both universeDomain and universe_domain', () => { From 1585d4a4e1b4b16d198307a3e97ffcf156d000b1 Mon Sep 17 00:00:00 2001 From: danieljbruce Date: Mon, 26 Feb 2024 13:09:01 -0500 Subject: [PATCH 2/6] feat: Nodejs transaction redesign feature branch (#1235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Break transaction.run into smaller pieces for use with async functions and other read/write calls (#1196) * Add a unit test for commit as a non-tx The fact that using a transaction object to do a commit results in a non-transaction should be documented so that if we decide to introduce a change later where this behaves differently then it is well documented. # Conflicts: # test/transaction.ts * use linter * Write tests to capture current behavior of error When begin transaction sends back an error, we want some tests to capture what the behavior is so that when we make changes to the run function then behavior is preserved. * Add tests for passing a response A response should reach the user the right way. Add tests to make sure behavior is preserved. * run async close to working In the run function delegate calls to runAsync and use run async to make promise calls * Add runAsync to promise excludes This allows this function to return a promise instead of a promise wrapped in a promise. This makes the tests pass and behave the way they should. * Remove space * Change to use this instead of self Do not call request from self * Eliminate unused comments * Add two comments Comments should actually explain what is being done * Remove the commit test for this PR The commit test for this PR should be removed because it is not really relevant for the async run functionality. * Clarify types throughout the function The types used should be very specific so that reading the code isn’t confusing. * Add a bit more typing for clarity Add a type to the resolve function just to introduce more clarity * Change types used in the data client callback Make the types more specific in the data client callback so that it is easier to track down the signature and match against the begin transaction function. * run the linter * Add comments to clarify PR * Refactor the parsing logic out of run The parsing logic is going to be needed elsewhere so taking it apart now. * Change interface of request promise callback The interface name should be changed so that it matches what it is. It is the callback used to form a promise. * Hide data completely Change accessors to hide data completely instead of using the private modifier * PR use if/else block Eliminate the early return as suggested in the PR * Add comments to document the new functions The comments capture the parameters and return type. * Update return type in docs * Update the tests to include runAsync runAsync should be in promisfy excludes * refactor: Break transaction.run into smaller pieces for use with async functions and other read/write calls * Rename a function to be more descriptive Make sure it is explicit that we are parsing begin results. * Modify comment Modify comment so that it doesn’t reference the way the code was before. * refactor: Move commit logic and add tests that prepare for transaction function changes (#1202) * Add a unit test for commit as a non-tx The fact that using a transaction object to do a commit results in a non-transaction should be documented so that if we decide to introduce a change later where this behaves differently then it is well documented. # Conflicts: # test/transaction.ts * use linter * Write tests to capture current behavior of error When begin transaction sends back an error, we want some tests to capture what the behavior is so that when we make changes to the run function then behavior is preserved. * Add tests for passing a response A response should reach the user the right way. Add tests to make sure behavior is preserved. * run async close to working In the run function delegate calls to runAsync and use run async to make promise calls * Add runAsync to promise excludes This allows this function to return a promise instead of a promise wrapped in a promise. This makes the tests pass and behave the way they should. * Remove space * Change to use this instead of self Do not call request from self * Eliminate unused comments * Add two comments Comments should actually explain what is being done * Remove the commit test for this PR The commit test for this PR should be removed because it is not really relevant for the async run functionality. * Clarify types throughout the function The types used should be very specific so that reading the code isn’t confusing. * Add a bit more typing for clarity Add a type to the resolve function just to introduce more clarity * Change types used in the data client callback Make the types more specific in the data client callback so that it is easier to track down the signature and match against the begin transaction function. * run the linter * Add comments to clarify PR * Refactor the parsing logic out of run The parsing logic is going to be needed elsewhere so taking it apart now. * Change interface of request promise callback The interface name should be changed so that it matches what it is. It is the callback used to form a promise. * Move commit functionality to a new function The internals of commit should be moved to a new function. This way we can sandwich a commit async call between commit and runCommitAsync. * Add the commit tests Commit tests added to ensure that commit behaves the same way as before. * Fix the tests so that they pass on commit The tests should pass before we make changes to commit. * refactor one of the mocks One of the mocks does not need to be written twice * Hide data completely Change accessors to hide data completely instead of using the private modifier * PR use if/else block Eliminate the early return as suggested in the PR * Add comments to document the new functions The comments capture the parameters and return type. * Update return type in docs * Update the tests to include runAsync runAsync should be in promisfy excludes * refactor: Break transaction.run into smaller pieces for use with async functions and other read/write calls * Rename a function to be more descriptive Make sure it is explicit that we are parsing begin results. * chore(deps): update dependency @types/sinon to v17 (#1197) * chore(deps): update dependency @types/is to v0.0.25 (#1198) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@types/is](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/is) ([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped)) | [`0.0.24` -> `0.0.25`](https://renovatebot.com/diffs/npm/@types%2fis/0.0.24/0.0.25) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fis/0.0.25?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fis/0.0.25?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fis/0.0.24/0.0.25?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fis/0.0.24/0.0.25?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/nodejs-datastore). * Modify comment Modify comment so that it doesn’t reference the way the code was before. * Create a transaction wrapper class for testing This is going to be a useful test for mocking out various layers to make sure they work the same way as before. * Clean up mocked transaction wrapper The mocked transaction wrapper should reset all mocked gapic functions and not have to be told which ones to reset. * Move test information for commit into commit block * Add gapic mocked tests for aggregation query Gapic mocked tests for run aggregation query need to be written that use the mock transaction object. * Fixing up the runQuery test * Finished the runQuery tests The runQuery tests are finished so we can use this to take apart the function from end to end. * Finished the get tests The get tests now make sure that the data coming back from the gapic layer results in the same values for users. * remove only * Add try catch blocks to handle errors in the tests try/catch logic in the test suite is added so that errors bubble up to the test runner and it is easier to see why tests failed * chore: update cloud-rad version to ^0.4.0 (#1199) Source-Link: https://github.com/googleapis/synthtool/commit/1063ef32bfe41b112bade7a2dfad4e84d0058ebd Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest@sha256:e92044720ab3cb6984a70b0c6001081204375959ba3599ef6c42dd99a7783a67 Co-authored-by: Owl Bot * Add comments and general cleanup Cleanup includes adding comments so that the tests are more readable. * Remove some redundant tests Some of these tests are captured in the various functions describe block. * Correct the comment to be more accurate The comment should talk about what the tests actually intend to do * General improvements to code quality Move declared transactionWrapper and other variables out to shorten code. Remove require and replace with import. Use more specific type. * Add data client check Add data client check and add type for data client. * Eliminate the mocked function variable The mocked function variable is not used so remove it from code. * Move begin transaction setup code The same block of code for setting up begin transaction is repeated twice. Move it into one function and use it from both before blocks. * Replace the TODO for the key Add some try blocks to make the errors more visible also. * Update description Update the description for the test to give a better explanation of what the describe block does. * Add comments to describe the purpose of signaller The comments describing the callback signaller should explain the problem it solves. * Add both dones back in The done functions were lost in the refactors. Let us add them back in. * mockedBeginTransaction should be Function Make a more specific type for mockedBeginTransaction and the functions mocked. * Use ECMA script modifier Make sure this function is completely hidden. * Remove TODOs that no longer apply The TODO statements no longer need to be followed up on so remove them. * beginTransaction type definition Remove the type definition. It is not used. * Add the setupBeginTransaction method Refactor test code to provide better error catching and also not repeat setup code for the run function. * Add a comment for private runCommit method The comment for the runCommit method just captures the fact that the function is used as a pass-through. * Modify comment to be more clear * Update comments The comments should more directly address the problem the tests are meant to solve * Eliminate redundant test code This describe block is now duplicated. Remove it. * revert comment This was a typo * refactor: Move commit logic and add tests that prepare for transaction function changes --------- Co-authored-by: Mend Renovate Co-authored-by: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Co-authored-by: Owl Bot * feat: Begin transactions before each transaction read/write (#1205) * Add a unit test for commit as a non-tx The fact that using a transaction object to do a commit results in a non-transaction should be documented so that if we decide to introduce a change later where this behaves differently then it is well documented. # Conflicts: # test/transaction.ts * use linter * Write tests to capture current behavior of error When begin transaction sends back an error, we want some tests to capture what the behavior is so that when we make changes to the run function then behavior is preserved. * Add tests for passing a response A response should reach the user the right way. Add tests to make sure behavior is preserved. * run async close to working In the run function delegate calls to runAsync and use run async to make promise calls * Add runAsync to promise excludes This allows this function to return a promise instead of a promise wrapped in a promise. This makes the tests pass and behave the way they should. * Remove space * Change to use this instead of self Do not call request from self * Eliminate unused comments * Add two comments Comments should actually explain what is being done * Remove the commit test for this PR The commit test for this PR should be removed because it is not really relevant for the async run functionality. * Clarify types throughout the function The types used should be very specific so that reading the code isn’t confusing. * Add a bit more typing for clarity Add a type to the resolve function just to introduce more clarity * Change types used in the data client callback Make the types more specific in the data client callback so that it is easier to track down the signature and match against the begin transaction function. * run the linter * Add comments to clarify PR * Refactor the parsing logic out of run The parsing logic is going to be needed elsewhere so taking it apart now. * Change interface of request promise callback The interface name should be changed so that it matches what it is. It is the callback used to form a promise. * Move commit functionality to a new function The internals of commit should be moved to a new function. This way we can sandwich a commit async call between commit and runCommitAsync. * Add the mutex and transaction state etc. Still an issue with system tests. commit still needs tweaks to work with async. * Remove no-op, get commit tests working Remove the no-op, get commit tests in place. * Add mocks and additional debugging Mocks and additional debugging hooks to introspect what is going on. * Add tests for commit Make sure commit behaves the same way as before. * Add the commit tests Commit tests added to ensure that commit behaves the same way as before. * Fix the tests so that they pass on commit The tests should pass before we make changes to commit. * refactor one of the mocks One of the mocks does not need to be written twice * reverting changes to add new test on transaction * Change the promise Make the promise simpler. Change the tests to exclude functions with promisify. * Hide data completely Change accessors to hide data completely instead of using the private modifier * PR use if/else block Eliminate the early return as suggested in the PR * Add comments to document the new functions The comments capture the parameters and return type. * Update return type in docs * Update the tests to include runAsync runAsync should be in promisfy excludes * refactor: Break transaction.run into smaller pieces for use with async functions and other read/write calls * Rename a function to be more descriptive Make sure it is explicit that we are parsing begin results. * Move the mutex and the state down to derived class The mutex and state should be moved down to the derived class. We are going to override get/runQuery/runAggregateQuery there. * chore(deps): update dependency @types/sinon to v17 (#1197) * Add hook to call run before commit Add the hook to call run before calling commit in existing tests. * Add commitAsync to promisify excludes commitAsync should be resolved and then() function should be called as it was not being called before * remove the console logs * Delete run commit * Remove the private identifier Use the private modifier instead to hide data * Add withBeginTransaction withBeginTransaction will be used with all calls that begin transactions and then intend to use the mutex for locking when the begin transaction call is made. * Add another level of abstraction Add #beginWithCallback so that all the read calls can be made with one line of code. * commit async is not needed anymore * This data structure is not needed anymore * Replace types associated with run to use generics The generic parameter should be used for types with run. * Make response type more specific for parseRunAsync More specific types are better and it make code easier to read * chore(deps): update dependency @types/is to v0.0.25 (#1198) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@types/is](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/is) ([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped)) | [`0.0.24` -> `0.0.25`](https://renovatebot.com/diffs/npm/@types%2fis/0.0.24/0.0.25) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fis/0.0.25?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fis/0.0.25?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fis/0.0.24/0.0.25?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fis/0.0.24/0.0.25?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/nodejs-datastore). * Modify comment Modify comment so that it doesn’t reference the way the code was before. * Add implementation for runQuery runQuery should make the call to begin the transaction first if that hasn’t already happened yet. * Making fixes for runAggregationQuery Fixes for run aggregation query. Still getting undefined results. * Write tests for runAggregateQuery Make sure that runAggregateQuery return results make it back to the user. * Get one test case passing for runAggregateQuery runAggregateQuery should not be excluded by promisify. Otherwise it will not return a promise, but we want it to return a promise. * remove console log clutter * Change tests for runAggregationQuery Change the test to use deep strict equal since the objects being compared will not be reference equal. * Add resolver type Eliminate some unused code * Create a transaction wrapper class for testing This is going to be a useful test for mocking out various layers to make sure they work the same way as before. * Clean up mocked transaction wrapper The mocked transaction wrapper should reset all mocked gapic functions and not have to be told which ones to reset. * Move test information for commit into commit block * Add gapic mocked tests for aggregation query Gapic mocked tests for run aggregation query need to be written that use the mock transaction object. * Fixing up the runQuery test * Finished the runQuery tests The runQuery tests are finished so we can use this to take apart the function from end to end. * Finished the get tests The get tests now make sure that the data coming back from the gapic layer results in the same values for users. * remove only * remove only and console log * Try modifications to runQuery Add modifications to runQuery to use some function to return values * Add try catch blocks to handle errors in the tests try/catch logic in the test suite is added so that errors bubble up to the test runner and it is easier to see why tests failed * Modify commit so it doesn’t run early Don’t start commit with a promise or the promise will run early. * Update get with resolver get should use the same pattern as runQuery and runAggregationQuery to use a resolver for the mutex business logic * remove #beginWithCallback #beginWithCallback is no longer used so remove it * Remove #withBeginTransaction This function is no longer used * Rename #someFunction with begin transaction #someFunction should be named differently * Fix promisify. Change to a deepStrictEqual check. A deepStrictEqual check is all that is needed in this test that currently reuses a transaction. * Add setImmediate to the tests If we add a delay to the tests then the mutex has the opportunity to unlock before running the tests. * Added some tests for call ordering We want to make sure that the calls get delivered and received in order * Pack testing tool into an object Put the testing tool in an object so that we can explore more orderings and possibilities. * Eliminate console logs, use expected order Allow expected order to be passed into the tester. This will make the object reusable. * Add a check for transaction not started yet Need a check to be sure that the transaction is not in progress if making another beginTransaction call. * Remove NOT_TRANSACTION Remove the NOT_TRANSACTION and default to NOT_STARTED * Remove only Remove only and let all unit tests run * Use an enum instead of static class members Use an enum to track transaction state. * TODOs are done * Move excludes to proper position Excludes should contain the right functions in the right order. * Simplify the run function a little bit Regroup functionality to simplify the run function * Add comments to tests Explain purpose of testing objects * Modify tests and fix a bug from the merge The merge added a bug because the pound sign wasn’t used and it should have been. * Fix error message * Comments and general cleanup Remove constructor parameter that is not used. Add comments to functions so that the tests are more readable. * Added comments for functions in transaction.ts Functions in transaction.ts need JSdoc comments. * Add a comment for aggregate queries Add JSdoc comment * Add an assertion to test The assertion should make sure the first read returns undefined. * Add tests for lookup, put, commit For calls with run and without run, do lookup, put, commit. Make sure to clean up after the tests too. * refactor the test Test does lookup, put, commit. This is done in an async function. * Add tests for put, lookup, commit A group of tests should be added for put, lookup, commit. One with run and one without run. * chore: update cloud-rad version to ^0.4.0 (#1199) Source-Link: https://github.com/googleapis/synthtool/commit/1063ef32bfe41b112bade7a2dfad4e84d0058ebd Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest@sha256:e92044720ab3cb6984a70b0c6001081204375959ba3599ef6c42dd99a7783a67 Co-authored-by: Owl Bot * Add comments and general cleanup Cleanup includes adding comments so that the tests are more readable. * Remove some redundant tests Some of these tests are captured in the various functions describe block. * Correct the comment to be more accurate The comment should talk about what the tests actually intend to do * General improvements to code quality Move declared transactionWrapper and other variables out to shorten code. Remove require and replace with import. Use more specific type. * Add data client check Add data client check and add type for data client. * Eliminate the mocked function variable The mocked function variable is not used so remove it from code. * Move begin transaction setup code The same block of code for setting up begin transaction is repeated twice. Move it into one function and use it from both before blocks. * Replace the TODO for the key Add some try blocks to make the errors more visible also. * Update description Update the description for the test to give a better explanation of what the describe block does. * Add comments to describe the purpose of signaller The comments describing the callback signaller should explain the problem it solves. * Add both dones back in The done functions were lost in the refactors. Let us add them back in. * mockedBeginTransaction should be Function Make a more specific type for mockedBeginTransaction and the functions mocked. * Use ECMA script modifier Make sure this function is completely hidden. * Remove TODOs that no longer apply The TODO statements no longer need to be followed up on so remove them. * beginTransaction type definition Remove the type definition. It is not used. * Add the setupBeginTransaction method Refactor test code to provide better error catching and also not repeat setup code for the run function. * Add a comment for private runCommit method The comment for the runCommit method just captures the fact that the function is used as a pass-through. * Modify comment to be more clear * Update comments The comments should more directly address the problem the tests are meant to solve * Eliminate redundant test code This describe block is now duplicated. Remove it. * revert comment This was a typo * refactor: Move commit logic and add tests that prepare for transaction function changes * runQuery, put, commit Another some more integration tests for runQuery, put, commit * run linter and group some tests into a describe block so that they don’t run before some new tests we are going to add. * Add some runAggregationQuery integration tests Add one of the test cases from the document (runAggregationQuery tests) * Add tests for put, runAggregationQuery, commit put, runAggregationQuery, commit tests have been added and now run properly. * Write some latency tests The latency tests measure time taken with and without using the run call. * Add logs to make latency tests run The logs will output the time required for the latency tests. * Add two tests for put, commit The two tests for put, commit should make sure that begin transaction is called. * Build requests into the transaction order tester To meet the needs we want for unit testing we must add some checker that lets us record the requests and verify that they are the right values. * Modify order testing Modify the order tester object to include more calls. Also add expected requests to the existing tests. * Create lookup, lookup, put, commit Create test for a read, read and then commit. * Fix the unit test Unit test should capture the fact that the run callback is used. * Add lookup requests When using get requests without passing consistency, we should see two lookup requests reach the gapic layer. Consistency should be removed from the test because it is not meant to reach the Gapic layer. * Remove the console logs console logs are not needed. Remove them so that the tests are cleaner. * Remove only * remove the latency tests The latency tests are not needed anymore so remove them. * Separate into a section transactions with/without A section name transactions with and without run should be used. * Remove a describe block and run linter This should make the diff a lot cleaner * Re-introduce a test that was there from before We don’t want the diff to say that we have removed tests. Therefore, add the test that was there before. * Update comment Modify the comment so that it is more objective * Add a comment for the runAggregationQuery function The comment for the aggregation query function in transaction.ts is needed here. * Simplify argument type In #withBeginTransaction there is a data type that matches the complex data type in the second argument of this function. * remove unnecessary extra variable The extra variable is not needed * Eliminate redundant definition Eliminate a definition and use the commit response data type here instead. * Add a comment for the #withBeginTransaction fn Need to clearly explain what the withBeginTransactionFunction does * Eliminate a line that was used for debugging * Add space back in * remove indent * Add empty line back in This simplifies the diff a bit * Remove ambiguous pronoun * Shorten comment * Add a comment for begin Add a comment that incorporates how this function interacts with #withBeginTransaction. * Shorten comments Don’t repeat information from the comments in the super class. Just mention the super class comments. * Type is only used once Inline the type to eliminate redundancy in the code. * Inline another promise type Inline another promise type which lets us eliminate one line of code. * Rename type to executor Executor is what it is called in the promise argument. * run linter * Simplify the data types more The types should line up for the executor in the runAsync function * Rename type to describe how it is used This name is clearer * Rename to userCallbackData. Add comments UserCallbackData is a more specific description of the type of data the generic type is. We also need comments to explain what these interfaces should represent. * resolver is a better term to use here * Change the type in the comments The type has updated since to be a more general type. * Flip if/else Flip the if else. Improvements are that the if checks for the affirmative and the longer block comes first * run linter * Add brackets Introduce brackets so that the word matching the parameter is more isolated. * Stronger type checking for runAsync Use more specific types in the runAsync function so that the compiler will complain if anything isn’t right. * Call the function fed into the promise resolver resolver should be the name used just to be consistent with all other code. * Rename variables and add comments Rename the variables so that the relationships between them are easier to understand. Add comments explaining the purpose of the variables. * Add comment for the done function * Make checkForCompletion private To avoid confusion make checkForCompletion private so that the user of the object does not need to think about using it. * Replace all events with enum values The events should not be strings. If they are strings then we might be magic string matching and this should not be done in code. * Make code more succinct Remove the need for a map. Remove occurrences of strings throughout tests and replace them with enum values. * Rename event to UserCodeEvent Call this enum UserCodeEvent as it is a better description of what the values could potentially be. * Remove the map from string names to gapic layer This map is not needed anymore because now we use the Gapic layer function event directly. * More specific type Shouldn’t just be a string. This should be a Gapic Layer function. * todo is done Assertion check is done * Eliminate need to define extra variable This just makes the code a bit more concise. * Add comments to describe test objects The comments describe Gapic layer data passed back. * There is no need to make these functions private Just inline the callbacks. A private function makes their relationship with the code that uses them confusing and each one of them is only used in one place. * Renames all callbacks to just callback Rename all callbacks to callback because that is the variable name inside the client function. * Take requests private * Take expectedRequests private expectedRequests is not used outside of the transaction order tester so we should take it private. * Take expected event order private Expected event order should be taken private. * Take event order private event order is not used outside this object * Fix some warning messages Various warnings show up in the es-lint. Fix them. * Use unknown for Gapic Layer Response Change the type to unknown to address the ES-lint error. * Get rid of any type for runQuery Use the more specific type provided by runQuery instead. * More linter fixes Addressing some of the problems in the new tests according to ES-lint. * Change parameters to match the get callback Stronger type enforcement helping us catch errors later. * Another ESLInt correction Use GetCallback here instead of any for the Get response type. * Remove query variable as it is not used * Take done private When done is passed in and saved it should only be used internally. * Eliminate unused arguments in callback Eliminate the arguments because if they are not used then they cause ES-lint errors. * Add readOnly reserved word Various properties can be readOnly as recommended by the linter. * Make type for done explicit Explicitly call out the fact that done should equal mocha’s done function. * Another place where done should be mentioned The done type should explicitly be mentioned here. * No need to access order tester to get wrapper There is no need to access the transaction wrapper to get the order tester. Just access the transaction wrapper directly. * private variable transaction wrapper Take transaction wrapper private. It does not need to be visible to the user. * Capitalize RequestType Change RequestType to be capitalized and use it whenever a request needs to be made. * request should be a more specific type RequestType should be used here. * Do info checks for error and response cases Add another assertion check. Do the check for the error and response cases. * These are actually results from runQuery * Eliminate extra line of code Add assertion check for info. Eliminate the code that stores all the results. * rename enum The word Layer is redundant. Also emphasize that the enum captures the function name. * Lookup response The variable should include the word lookup since technically that is the name of the Gapic layer endpoint. * Add comments for the transaction order tester Explain each function with a comment describing what it does. * Introduce a new push function Add a push function so that in each test it is easier to see the events line up. * Remove middle layer functions Remove functions in the transactionOrderTester and use functionality from those functions directly because then in the test it is easier to read the test’s intent. Also, add GapicRequestData type to simplify types in the test class. Shorten transactionOrderTester to just tester. * Prefer block comments The comments are multi-line so use block comments instead. * Rename event to CUSTOM_EVENT CUSTOM_EVENT should be used instead of FUNCTIONS_CALLED. FUNCTIONS_CALLED is sort of ambiguous. * Add square brackets to comments square brackets added to make the comment more readable and user friendly. * Remove unused comment * Eliminate the promiseType variable This variable is only used once. We should not define it here. * Eliminate double try/catch blocks The nested try/catch blocks are not necessary. The finally block is going to run even if it returns an error in the catch block. * Add comments to describe what T is Need to make sure the generic type is clear. * Use wrapped promise The wrapped promise and runExclusive should be used instead of releasing the mutex manually. This is safer. * Change name to callback instead Name the variable to match the parameter name being passed into runExclusive. * Eliminate extra variable Use the async function in place. Don’t define an extra variable just for one usage. * Add a test for commit first If commit is first then make sure the calls happen in the right order. * Remove a redundant check for transaction started The results won’t be any different if the first check is removed. Therefore, should remove it just because it makes the code a lot simpler. * Rename variable and use runExclusive runExclusive in the run function will get rid of excess code that is not needed. Also rename a variable to runResults to be more specific. * refactor: remove resolvers, remove promise in withBeginTransaction, simplify code calling withBeginTransaction (#1212) * Change resolvers Change the resolvers so that they all follow the same pattern. * Add a wrapWithBeginTransaction function for get Generalize the calls to withBeginTransaction. Don’t allow the error type to be undefined in UserCallbackData since the error never actually will be undefined. This is necessary to solve various compile time errors. * Use wrapWithBeginTransaction in more places commit and runQuery can now use #wrapWithBeginTransaction and this will reduce the amount of code necessary in each function respectively. * Add callbackWithError function This function replaces a common pattern that occurs in transactions where we provide a callback that accepts an error as the first argument and some generic type that extends an array of any as the rest of the arguments. * Add return type to callbackWithError Adding a return type just makes type checking stronger in the client library. We should introduce it here to make it more likely a compiler error will be thrown if the function is misused. * Delete code that is not used When playing around with code to return a standard callback, code was written that is not required anymore. Also, rename StandardCallbackArgumentsAny. * There is no need to define a separate type The UserCallbackArguments do not need a separate type if they are only used once. Define the type inline where it is needed. * TODO no longer applies * Remove TODO * Inline the error type Error or null is only used in two places so inline it here. * Add a comment for callbackWithError callbackWithError is an abstract function that needs to be explained. This comment helps with that. * Added comments to document sendUserCallbackData sendUserCallbackData needs to be described so that its purpose is clear. * Move function Move function to the right order alphabetically * Do not define a commit type only used once Do not define a separate variable for only one use. * Replace the type in the comment with Function Replace with Function to eliminate references to the generic parameter which make CI tests fail. * Just use function type for the callback No need to mention the argument types and return types. * Remove withBeginTransaction withBeginTransaction is only called by one function so remove it to condense call stack. * Simplify code usage of withBeginTransaction Eliminate the need to build a resolver for all code usages of withBeginTransaction. Eliminate the need for a promise in withBeginTransaction. Eliminate the need for a callbackWithError function. * function should not have any arguments * Remove generic Args parameter It is not used. * Remove TODOs that are not relevant anymore. * Rename to withBeginTransaction Rename the function and eliminate redundant code. * Update comments for withBeginTransaction Update the comments to reflect new parameters. * Simplify diff * Remove error as null * withBeginTransaction Move withBeginTransaction to last alphabetically * Add a comment to indicate error In the code block it is not clear what kind of error will be produced and caught in catch so a comment is added to clarify the type of error that gets produced. * Rename method to beginTxAsync Calling the method `run` is too ambiguous. Use #beginTransactionAsync instead. * Remove abstract types and inline instead With changes from before, these abstract data types are now only used once in the async function that begins a transaction. This change inlines the code that creates the promise so that these don’t have to be used at all. * Change the type of the id The id for a transaction should actually match the value returned by the server. Adding this change also allows us to be more specific with types in function arguments. * For types to align, previous txn must be flexible Previous transaction must accept the Uint8Array type because after all, that is what the id type usually is. * Set transaction type in request options The request options must accept a Uint8Array for the compiler to work now that the ids can accept this type too. * For read options, more types should be accepted The type for read options should line up with the type for other properties that store a transaction id. * Replace UserCallbackData with BeginAsyncResponse UserCallbackData is only used in one place. Therefore, the generic type is not necessary and this can be replaced with a more specific type for readability. * Rewrite description for withBeginTransaction. This description adds a few more details. * Add comments to withBeginTransaction Ensure that the use of withBeginTransaction is clear and that this function contains comments throughout it. * Make the MockedTransactionWrapper class flexible We want to test what happens when sending an error back from the begin transaction call. * Move the after hook out The after hook does the same thing for all four of the describe blocks. Move it out to reduce the amount of code required. * Add beginTransaction error test Add a test to ensure that when beginTransaction sends back an error that the error actually reaches the user when they are using a promise or a callback. * Add comments to withBeginTransaction Comments are needed in withBeginTransaction to clearly indicate the flow of control within the function. * Added a few comments for withBeginTxn Comments will help all functions that are using withBeginTransaction. --------- Co-authored-by: Mend Renovate Co-authored-by: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Co-authored-by: Owl Bot * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Mend Renovate Co-authored-by: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Co-authored-by: Owl Bot --- package.json | 1 + src/request.ts | 21 +- src/transaction.ts | 537 ++++++++++++---- system-test/datastore.ts | 197 +++++- test/transaction.ts | 1262 +++++++++++++++++++++++++++++++++++++- 5 files changed, 1871 insertions(+), 147 deletions(-) diff --git a/package.json b/package.json index 06751f1e..8423fb7b 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "dependencies": { "@google-cloud/promisify": "^4.0.0", "arrify": "^2.0.1", + "async-mutex": "^0.4.0", "concat-stream": "^2.0.0", "extend": "^3.0.2", "google-gax": "^4.0.5", diff --git a/src/request.ts b/src/request.ts index d39f572e..047cf0ab 100644 --- a/src/request.ts +++ b/src/request.ts @@ -79,7 +79,7 @@ const CONSISTENCY_PROTO_CODE: ConsistencyProtoCode = { * @class */ class DatastoreRequest { - id: string | undefined; + id: string | undefined | Uint8Array | null; requests_: | Entity | { @@ -546,6 +546,19 @@ class DatastoreRequest { ); } + /** + * Datastore allows you to run aggregate queries by supplying aggregate fields + * which will determine the type of aggregation that is performed. + * + * The query is run, and the results are returned in the second argument of + * the callback provided. + * + * @param {AggregateQuery} query AggregateQuery object. + * @param {RunQueryOptions} options Optional configuration + * @param {function} [callback] The callback function. If omitted, a promise is + * returned. + * + **/ runAggregationQuery( query: AggregateQuery, options?: RunQueryOptions @@ -1157,7 +1170,7 @@ export interface SharedQueryOptions { partitionId?: google.datastore.v1.IPartitionId | null; readOptions?: { readConsistency?: number; - transaction?: string; + transaction?: string | Uint8Array | null; readTime?: ITimestamp; }; } @@ -1166,9 +1179,9 @@ export interface RequestOptions extends SharedQueryOptions { keys?: Entity; transactionOptions?: { readOnly?: {}; - readWrite?: {previousTransaction?: string}; + readWrite?: {previousTransaction?: string | Uint8Array | null}; } | null; - transaction?: string | null; + transaction?: string | null | Uint8Array; mode?: string; query?: QueryProto; filter?: string; diff --git a/src/transaction.ts b/src/transaction.ts index 9bc760dd..0b7883b6 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -22,15 +22,41 @@ import {google} from '../protos/protos'; import {Datastore, TransactionOptions} from '.'; import {entity, Entity, Entities} from './entity'; -import {Query} from './query'; +import { + Query, + RunQueryCallback, + RunQueryInfo, + RunQueryOptions, + RunQueryResponse, +} from './query'; import { CommitCallback, CommitResponse, DatastoreRequest, RequestOptions, PrepareEntityObjectResponse, + CreateReadStreamOptions, + GetResponse, + GetCallback, + RequestCallback, } from './request'; import {AggregateQuery} from './aggregate'; +import {Mutex} from 'async-mutex'; + +/* + * This type matches the value returned by the promise in the + * #beginTransactionAsync function and subsequently passed into various other + * methods in this class. + */ +interface BeginAsyncResponse { + err?: Error | null; + resp?: google.datastore.v1.IBeginTransactionResponse; +} + +enum TransactionState { + NOT_STARTED, + IN_PROGRESS, // IN_PROGRESS currently tracks the expired state as well +} /** * A transaction is a set of Datastore operations on one or more entities. Each @@ -58,6 +84,8 @@ class Transaction extends DatastoreRequest { request: Function; modifiedEntities_: ModifiedEntities; skipCommit?: boolean; + #mutex = new Mutex(); + #state = TransactionState.NOT_STARTED; constructor(datastore: Datastore, options?: TransactionOptions) { super(); /** @@ -149,116 +177,13 @@ class Transaction extends DatastoreRequest { : () => {}; const gaxOptions = typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {}; - - if (this.skipCommit) { - setImmediate(callback); - return; - } - - const keys: Entities = {}; - - this.modifiedEntities_ - // Reverse the order of the queue to respect the "last queued request - // wins" behavior. - .reverse() - // Limit the operations we're going to send through to only the most - // recently queued operations. E.g., if a user tries to save with the - // same key they just asked to be deleted, the delete request will be - // ignored, giving preference to the save operation. - .filter((modifiedEntity: Entity) => { - const key = modifiedEntity.entity.key; - - if (!entity.isKeyComplete(key)) return true; - - const stringifiedKey = JSON.stringify(modifiedEntity.entity.key); - - if (!keys[stringifiedKey]) { - keys[stringifiedKey] = true; - return true; - } - - return false; - }) - // Group entities together by method: `save` mutations, then `delete`. - // Note: `save` mutations being first is required to maintain order when - // assigning IDs to incomplete keys. - .sort((a, b) => { - return a.method < b.method ? 1 : a.method > b.method ? -1 : 0; - }) - // Group arguments together so that we only make one call to each - // method. This is important for `DatastoreRequest.save`, especially, as - // that method handles assigning auto-generated IDs to the original keys - // passed in. When we eventually execute the `save` method's API - // callback, having all the keys together is necessary to maintain - // order. - .reduce((acc: Entities, entityObject: Entity) => { - const lastEntityObject = acc[acc.length - 1]; - const sameMethod = - lastEntityObject && entityObject.method === lastEntityObject.method; - - if (!lastEntityObject || !sameMethod) { - acc.push(entityObject); - } else { - lastEntityObject.args = lastEntityObject.args.concat( - entityObject.args - ); - } - - return acc; - }, []) - // Call each of the mutational methods (DatastoreRequest[save,delete]) - // to build up a `req` array on this instance. This will also build up a - // `callbacks` array, that is the same callback that would run if we - // were using `save` and `delete` outside of a transaction, to process - // the response from the API. - .forEach( - (modifiedEntity: {method: string; args: {reverse: () => void}}) => { - const method = modifiedEntity.method; - const args = modifiedEntity.args.reverse(); - Datastore.prototype[method].call(this, args, () => {}); - } - ); - - // Take the `req` array built previously, and merge them into one request to - // send as the final transactional commit. - const reqOpts = { - mutations: this.requests_ - .map((x: {mutations: google.datastore.v1.Mutation}) => x.mutations) - .reduce( - (a: {concat: (arg0: Entity) => void}, b: Entity) => a.concat(b), - [] - ), - }; - - this.request_( - { - client: 'DatastoreClient', - method: 'commit', - reqOpts, - gaxOpts: gaxOptions || {}, + // This ensures that the transaction is started before calling runCommit + this.#withBeginTransaction( + gaxOptions, + () => { + this.#runCommit(gaxOptions, callback); }, - (err, resp) => { - if (err) { - // Rollback automatically for the user. - this.rollback(() => { - // Provide the error & API response from the failed commit to the - // user. Even a failed rollback should be transparent. RE: - // https://github.com/GoogleCloudPlatform/google-cloud-node/pull/1369#discussion_r66833976 - callback(err, resp); - }); - return; - } - - // The `callbacks` array was built previously. These are the callbacks - // that handle the API response normally when using the - // DatastoreRequest.save and .delete methods. - this.requestCallbacks_.forEach( - (cb: (arg0: null, arg1: Entity) => void) => { - cb(null, resp); - } - ); - callback(null, resp); - } + callback ); } @@ -398,6 +323,47 @@ class Transaction extends DatastoreRequest { }); } + /** + * This function calls get on the super class. If the transaction + * has not been started yet then the transaction is started before the + * get call is made. + * + * @param {Key|Key[]} keys Datastore key object(s). + * @param {object} [options] Optional configuration. + * @param {function} callback The callback function. + * + */ + get( + keys: entity.Key | entity.Key[], + options?: CreateReadStreamOptions + ): Promise; + get(keys: entity.Key | entity.Key[], callback: GetCallback): void; + get( + keys: entity.Key | entity.Key[], + options: CreateReadStreamOptions, + callback: GetCallback + ): void; + get( + keys: entity.Key | entity.Key[], + optionsOrCallback?: CreateReadStreamOptions | GetCallback, + cb?: GetCallback + ): void | Promise { + const options = + typeof optionsOrCallback === 'object' && optionsOrCallback + ? optionsOrCallback + : {}; + const callback = + typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!; + // This ensures that the transaction is started before calling get + this.#withBeginTransaction( + options.gaxOptions, + () => { + super.get(keys, options, callback); + }, + callback + ); + } + /** * Maps to {@link https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/transaction#_google_cloud_datastore_Transaction_save_member_1_|Datastore#save}, forcing the method to be `insert`. * @@ -544,10 +510,195 @@ class Transaction extends DatastoreRequest { typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!; + this.#mutex.runExclusive(async () => { + if (this.#state === TransactionState.NOT_STARTED) { + const runResults = await this.#beginTransactionAsync(options); + this.#processBeginResults(runResults, callback); + } else { + process.emitWarning( + 'run has already been called and should not be called again.' + ); + callback(null, this, {transaction: this.id}); + } + }); + } + + /** + * This function is a pass-through for the transaction.commit method + * It contains the business logic used for committing a transaction + * + * @param {object} [gaxOptions] Request configuration options, outlined here: + * https://googleapis.github.io/gax-nodejs/global.html#CallOptions. + * @param {function} callback The callback function. + * @private + */ + #runCommit( + gaxOptions: CallOptions, + callback: CommitCallback + ): void | Promise { + if (this.skipCommit) { + setImmediate(callback); + return; + } + + const keys: Entities = {}; + + this.modifiedEntities_ + // Reverse the order of the queue to respect the "last queued request + // wins" behavior. + .reverse() + // Limit the operations we're going to send through to only the most + // recently queued operations. E.g., if a user tries to save with the + // same key they just asked to be deleted, the delete request will be + // ignored, giving preference to the save operation. + .filter((modifiedEntity: Entity) => { + const key = modifiedEntity.entity.key; + + if (!entity.isKeyComplete(key)) return true; + + const stringifiedKey = JSON.stringify(modifiedEntity.entity.key); + + if (!keys[stringifiedKey]) { + keys[stringifiedKey] = true; + return true; + } + + return false; + }) + // Group entities together by method: `save` mutations, then `delete`. + // Note: `save` mutations being first is required to maintain order when + // assigning IDs to incomplete keys. + .sort((a, b) => { + return a.method < b.method ? 1 : a.method > b.method ? -1 : 0; + }) + // Group arguments together so that we only make one call to each + // method. This is important for `DatastoreRequest.save`, especially, as + // that method handles assigning auto-generated IDs to the original keys + // passed in. When we eventually execute the `save` method's API + // callback, having all the keys together is necessary to maintain + // order. + .reduce((acc: Entities, entityObject: Entity) => { + const lastEntityObject = acc[acc.length - 1]; + const sameMethod = + lastEntityObject && entityObject.method === lastEntityObject.method; + + if (!lastEntityObject || !sameMethod) { + acc.push(entityObject); + } else { + lastEntityObject.args = lastEntityObject.args.concat( + entityObject.args + ); + } + + return acc; + }, []) + // Call each of the mutational methods (DatastoreRequest[save,delete]) + // to build up a `req` array on this instance. This will also build up a + // `callbacks` array, that is the same callback that would run if we + // were using `save` and `delete` outside of a transaction, to process + // the response from the API. + .forEach( + (modifiedEntity: {method: string; args: {reverse: () => void}}) => { + const method = modifiedEntity.method; + const args = modifiedEntity.args.reverse(); + Datastore.prototype[method].call(this, args, () => {}); + } + ); + // Take the `req` array built previously, and merge them into one request to + // send as the final transactional commit. const reqOpts = { + mutations: this.requests_ + .map((x: {mutations: google.datastore.v1.Mutation}) => x.mutations) + .reduce( + (a: {concat: (arg0: Entity) => void}, b: Entity) => a.concat(b), + [] + ), + }; + + this.request_( + { + client: 'DatastoreClient', + method: 'commit', + reqOpts, + gaxOpts: gaxOptions || {}, + }, + (err, resp) => { + if (err) { + // Rollback automatically for the user. + this.rollback(() => { + // Provide the error & API response from the failed commit to the + // user. Even a failed rollback should be transparent. RE: + // https://github.com/GoogleCloudPlatform/google-cloud-node/pull/1369#discussion_r66833976 + callback(err, resp); + }); + return; + } + + // The `callbacks` array was built previously. These are the callbacks + // that handle the API response normally when using the + // DatastoreRequest.save and .delete methods. + this.requestCallbacks_.forEach( + (cb: (arg0: null, arg1: Entity) => void) => { + cb(null, resp); + } + ); + callback(null, resp); + } + ); + } + + /** + * This function parses results from a beginTransaction call + * + * @param {BeginAsyncResponse} [response] + * The response data from a call to begin a transaction. + * @param {RunCallback} [callback] A callback that accepts an error and a + * response as arguments. + * + **/ + #processBeginResults( + runResults: BeginAsyncResponse, + callback: RunCallback + ): void { + const err = runResults.err; + const resp = runResults.resp; + if (err) { + callback(err, null, resp); + } else { + this.#parseRunSuccess(runResults); + callback(null, this, resp); + } + } + + /** + * This function saves results from a successful beginTransaction call. + * + * @param {BeginAsyncResponse} [response] The response from a call to + * begin a transaction that completed successfully. + * + **/ + #parseRunSuccess(runResults: BeginAsyncResponse) { + const resp = runResults.resp; + this.id = resp!.transaction; + this.#state = TransactionState.IN_PROGRESS; + } + + /** + * This async function makes a beginTransaction call and returns a promise with + * the information returned from the call that was made. + * + * @param {RunOptions} options The options used for a beginTransaction call. + * @returns {Promise} + * + * + **/ + async #beginTransactionAsync( + options: RunOptions + ): Promise { + const reqOpts: RequestOptions = { transactionOptions: {}, - } as RequestOptions; + }; if (options.readOnly || this.readOnly) { reqOpts.transactionOptions!.readOnly = {}; @@ -562,22 +713,104 @@ class Transaction extends DatastoreRequest { if (options.transactionOptions) { reqOpts.transactionOptions = options.transactionOptions; } + return new Promise((resolve: (value: BeginAsyncResponse) => void) => { + this.request_( + { + client: 'DatastoreClient', + method: 'beginTransaction', + reqOpts, + gaxOpts: options.gaxOptions, + }, + // Always use resolve because then this function can return both the error and the response + (err, resp) => { + resolve({ + err, + resp, + }); + } + ); + }); + } - this.request_( - { - client: 'DatastoreClient', - method: 'beginTransaction', - reqOpts, - gaxOpts: options.gaxOptions, + /** + * + * This function calls runAggregationQuery on the super class. If the transaction + * has not been started yet then the transaction is started before the + * runAggregationQuery call is made. + * + * @param {AggregateQuery} [query] AggregateQuery object. + * @param {RunQueryOptions} [options] Optional configuration + * @param {function} [callback] The callback function. If omitted, a promise is + * returned. + * + **/ + runAggregationQuery( + query: AggregateQuery, + options?: RunQueryOptions + ): Promise; + runAggregationQuery( + query: AggregateQuery, + options: RunQueryOptions, + callback: RequestCallback + ): void; + runAggregationQuery(query: AggregateQuery, callback: RequestCallback): void; + runAggregationQuery( + query: AggregateQuery, + optionsOrCallback?: RunQueryOptions | RequestCallback, + cb?: RequestCallback + ): void | Promise { + const options = + typeof optionsOrCallback === 'object' && optionsOrCallback + ? optionsOrCallback + : {}; + const callback = + typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!; + // This ensures that the transaction is started before calling runAggregationQuery + this.#withBeginTransaction( + options.gaxOptions, + () => { + super.runAggregationQuery(query, options, callback); }, - (err, resp) => { - if (err) { - callback(err, null, resp); - return; - } - this.id = resp!.transaction; - callback(null, this, resp); - } + callback + ); + } + + /** + * This function calls runQuery on the super class. If the transaction + * has not been started yet then the transaction is started before the + * runQuery call is made. + * + * @param {Query} query Query object. + * @param {object} [options] Optional configuration. + * @param {function} [callback] The callback function. If omitted, a readable + * stream instance is returned. + * + */ + runQuery(query: Query, options?: RunQueryOptions): Promise; + runQuery( + query: Query, + options: RunQueryOptions, + callback: RunQueryCallback + ): void; + runQuery(query: Query, callback: RunQueryCallback): void; + runQuery( + query: Query, + optionsOrCallback?: RunQueryOptions | RunQueryCallback, + cb?: RunQueryCallback + ): void | Promise { + const options = + typeof optionsOrCallback === 'object' && optionsOrCallback + ? optionsOrCallback + : {}; + const callback = + typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!; + // This ensures that the transaction is started before calling runQuery + this.#withBeginTransaction( + options.gaxOptions, + () => { + super.runQuery(query, options, callback); + }, + callback ); } @@ -771,6 +1004,59 @@ class Transaction extends DatastoreRequest { this.save(entities); } + + /** + * Some rpc calls require that the transaction has been started (i.e, has a + * valid id) before they can be sent. #withBeginTransaction acts as a wrapper + * over those functions. + * + * If the transaction has not begun yet, `#withBeginTransaction` will first + * send an rpc to begin the transaction, and then execute the wrapped + * function. If it has begun, the wrapped function will be called directly + * instead. If an error is encountered during the beginTransaction call, the + * callback will be executed instead of the wrapped function. + * + * @param {CallOptions | undefined} [gaxOptions] Gax options provided by the + * user that are used for the beginTransaction grpc call. + * @param {function} [fn] A function which is run after ensuring a + * beginTransaction call is made. + * @param {function} [callback] A callback provided by the user that expects + * an error in the first argument and a custom data type for the rest of the + * arguments. + * @private + */ + #withBeginTransaction( + gaxOptions: CallOptions | undefined, + fn: () => void, + callback: (...args: [Error | null, ...T] | [Error | null]) => void + ): void { + (async () => { + if (this.#state === TransactionState.NOT_STARTED) { + try { + await this.#mutex.runExclusive(async () => { + if (this.#state === TransactionState.NOT_STARTED) { + // This sends an rpc call to get the transaction id + const runResults = await this.#beginTransactionAsync({ + gaxOptions, + }); + if (runResults.err) { + // The rpc getting the id was unsuccessful. + // Do not call the wrapped function. + throw runResults.err; + } + this.#parseRunSuccess(runResults); + // The rpc saving the transaction id was successful. + // Now the wrapped function fn will be called. + } + }); + } catch (err: any) { + // Handle an error produced by the beginTransactionAsync call + return callback(err); + } + } + return fn(); + })(); + } } export type ModifiedEntities = Array<{ @@ -810,6 +1096,7 @@ promisifyAll(Transaction, { 'createQuery', 'delete', 'insert', + '#runAsync', 'save', 'update', 'upsert', diff --git a/system-test/datastore.ts b/system-test/datastore.ts index dffcc724..62e1f89f 100644 --- a/system-test/datastore.ts +++ b/system-test/datastore.ts @@ -1749,6 +1749,187 @@ async.each( assert.deepStrictEqual(results, [{property_1: 4}]); }); }); + describe('transactions with and without run', () => { + describe('lookup, put, commit', () => { + const key = datastore.key(['Company', 'Google']); + const obj = { + url: 'www.google.com', + }; + afterEach(async () => { + await datastore.delete(key); + }); + async function doLookupPutCommit(transaction: Transaction) { + const [firstRead] = await transaction.get(key); + assert(!firstRead); + transaction.save({key, data: obj}); + await transaction.commit(); + const [entity] = await datastore.get(key); + delete entity[datastore.KEY]; + assert.deepStrictEqual(entity, obj); + } + it('should run in a transaction', async () => { + const transaction = datastore.transaction(); + await transaction.run(); + await doLookupPutCommit(transaction); + }); + it('should run in a transaction without run', async () => { + const transaction = datastore.transaction(); + await doLookupPutCommit(transaction); + }); + }); + describe('put, lookup, commit', () => { + const key = datastore.key(['Company', 'Google']); + const obj = { + url: 'www.google.com', + }; + afterEach(async () => { + await datastore.delete(key); + }); + async function doPutLookupCommit(transaction: Transaction) { + transaction.save({key, data: obj}); + const [firstRead] = await transaction.get(key); + assert(!firstRead); + await transaction.commit(); + const [entity] = await datastore.get(key); + delete entity[datastore.KEY]; + assert.deepStrictEqual(entity, obj); + } + it('should run in a transaction', async () => { + const transaction = datastore.transaction(); + await transaction.run(); + await doPutLookupCommit(transaction); + }); + it('should run in a transaction without run', async () => { + const transaction = datastore.transaction(); + await doPutLookupCommit(transaction); + }); + }); + describe('runQuery, put, commit', () => { + const key = datastore.key(['Company', 'Google']); + const obj = { + url: 'www.google.com', + }; + afterEach(async () => { + await datastore.delete(key); + }); + async function doRunQueryPutCommit(transaction: Transaction) { + const query = transaction.createQuery('Company'); + const [results] = await transaction.runQuery(query); + assert.deepStrictEqual(results, []); + transaction.save({key, data: obj}); + await transaction.commit(); + const [entity] = await datastore.get(key); + delete entity[datastore.KEY]; + assert.deepStrictEqual(entity, obj); + } + it('should run in a transaction', async () => { + const transaction = datastore.transaction(); + await transaction.run(); + await doRunQueryPutCommit(transaction); + }); + it('should run in a transaction without run', async () => { + const transaction = datastore.transaction(); + await doRunQueryPutCommit(transaction); + }); + }); + describe('put, runQuery, commit', () => { + const key = datastore.key(['Company', 'Google']); + const obj = { + url: 'www.google.com', + }; + afterEach(async () => { + await datastore.delete(key); + }); + async function doPutRunQueryCommit(transaction: Transaction) { + transaction.save({key, data: obj}); + const query = transaction.createQuery('Company'); + const [results] = await transaction.runQuery(query); + assert.deepStrictEqual(results, []); + await transaction.commit(); + const [entity] = await datastore.get(key); + delete entity[datastore.KEY]; + assert.deepStrictEqual(entity, obj); + } + it('should run in a transaction', async () => { + const transaction = datastore.transaction(); + await transaction.run(); + await doPutRunQueryCommit(transaction); + }); + it('should run in a transaction without run', async () => { + const transaction = datastore.transaction(); + await doPutRunQueryCommit(transaction); + }); + }); + + describe('runAggregationQuery, put, commit', () => { + const key = datastore.key(['Company', 'Google']); + const obj = { + url: 'www.google.com', + }; + afterEach(async () => { + await datastore.delete(key); + }); + async function doRunAggregationQueryPutCommit( + transaction: Transaction + ) { + const query = transaction.createQuery('Company'); + const aggregateQuery = transaction + .createAggregationQuery(query) + .count('total'); + const [results] = + await transaction.runAggregationQuery(aggregateQuery); + assert.deepStrictEqual(results, [{total: 0}]); + transaction.save({key, data: obj}); + await transaction.commit(); + const [entity] = await datastore.get(key); + delete entity[datastore.KEY]; + assert.deepStrictEqual(entity, obj); + } + it('should run in a transaction', async () => { + const transaction = datastore.transaction(); + await transaction.run(); + await doRunAggregationQueryPutCommit(transaction); + }); + it('should run in a transaction without run', async () => { + const transaction = datastore.transaction(); + await doRunAggregationQueryPutCommit(transaction); + }); + }); + describe('put, runAggregationQuery, commit', () => { + const key = datastore.key(['Company', 'Google']); + const obj = { + url: 'www.google.com', + }; + afterEach(async () => { + await datastore.delete(key); + }); + async function doPutRunAggregationQueryCommit( + transaction: Transaction + ) { + transaction.save({key, data: obj}); + const query = transaction.createQuery('Company'); + const aggregateQuery = transaction + .createAggregationQuery(query) + .count('total'); + const [results] = + await transaction.runAggregationQuery(aggregateQuery); + assert.deepStrictEqual(results, [{total: 0}]); + await transaction.commit(); + const [entity] = await datastore.get(key); + delete entity[datastore.KEY]; + assert.deepStrictEqual(entity, obj); + } + it('should run in a transaction', async () => { + const transaction = datastore.transaction(); + await transaction.run(); + await doPutRunAggregationQueryCommit(transaction); + }); + it('should run in a transaction without run', async () => { + const transaction = datastore.transaction(); + await doPutRunAggregationQueryCommit(transaction); + }); + }); + }); describe('transactions', () => { it('should run in a transaction', async () => { const key = datastore.key(['Company', 'Google']); @@ -1873,9 +2054,7 @@ async.each( [result] = await aggregateQuery.run(); } catch (e) { await transaction.rollback(); - assert.fail( - 'The aggregation query run should have been successful' - ); + throw e; } assert.deepStrictEqual(result, [{total: 2}]); await transaction.commit(); @@ -1892,9 +2071,7 @@ async.each( [result] = await aggregateQuery.run(); } catch (e) { await transaction.rollback(); - assert.fail( - 'The aggregation query run should have been successful' - ); + throw e; } assert.deepStrictEqual(result, [{'total rating': 200}]); await transaction.commit(); @@ -1911,9 +2088,7 @@ async.each( [result] = await aggregateQuery.run(); } catch (e) { await transaction.rollback(); - assert.fail( - 'The aggregation query run should have been successful' - ); + throw e; } assert.deepStrictEqual(result, [{'average rating': 100}]); await transaction.commit(); @@ -1929,9 +2104,7 @@ async.each( [result] = await aggregateQuery.run(); } catch (e) { await transaction.rollback(); - assert.fail( - 'The aggregation query run should have been successful' - ); + throw e; } return result; } diff --git a/test/transaction.ts b/test/transaction.ts index 06faf342..c1346f3d 100644 --- a/test/transaction.ts +++ b/test/transaction.ts @@ -24,21 +24,33 @@ import { DatastoreRequest, Query, TransactionOptions, + Transaction, + AggregateField, } from '../src'; -import {Entity} from '../src/entity'; +import {Entities, Entity, entity} from '../src/entity'; import * as tsTypes from '../src/transaction'; import * as sinon from 'sinon'; -import {RequestConfig} from '../src/request'; +import {Callback, CallOptions, ClientStub} from 'google-gax'; +import { + CommitCallback, + CreateReadStreamOptions, + GetCallback, + RequestCallback, + RequestConfig, +} from '../src/request'; import {SECOND_DATABASE_ID} from './index'; +import {google} from '../protos/protos'; +import {RunCallback} from '../src/transaction'; +import * as protos from '../protos/protos'; +import {AggregateQuery} from '../src/aggregate'; +import {RunQueryCallback, RunQueryInfo, RunQueryOptions} from '../src/query'; +import * as mocha from 'mocha'; const async = require('async'); // eslint-disable-next-line @typescript-eslint/no-explicit-any type Any = any; type Path = string | [string] | [string, number]; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const {entity} = require('../src/entity'); - let promisified = false; const fakePfy = Object.assign({}, pfy, { promisifyAll(klass: Function, options: pfy.PromisifyAllOptions) { @@ -51,6 +63,7 @@ const fakePfy = Object.assign({}, pfy, { 'createQuery', 'delete', 'insert', + '#runAsync', 'save', 'update', 'upsert', @@ -147,9 +160,1246 @@ async.each( }); }); + describe('testing various transaction functions when transaction.run returns a response', () => { + type RequestType = + | protos.google.datastore.v1.ICommitRequest + | protos.google.datastore.v1.IBeginTransactionRequest + | protos.google.datastore.v1.ILookupRequest + | protos.google.datastore.v1.IRunQueryRequest + | protos.google.datastore.v1.IRunAggregationQueryRequest; + // These tests were created to ensure that various transaction functions work correctly after run is called. + // This allows us to catch any breaking changes to code usages that should remain the same. + const testRunResp = { + transaction: Buffer.from(Array.from(Array(100).keys())), + }; + enum GapicFunctionName { + BEGIN_TRANSACTION = 'beginTransaction', + LOOKUP = 'lookup', + RUN_QUERY = 'runQuery', + RUN_AGGREGATION_QUERY = 'runAggregationQuery', + COMMIT = 'commit', + } + + // MockedTransactionWrapper is a helper class for mocking out various + // Gapic functions and ensuring that responses and errors actually make it + // back to the user. + class MockedTransactionWrapper { + datastore: Datastore; + transaction: Transaction; + dataClient?: ClientStub; + mockedBeginTransaction: Function; + functionsMocked: { + name: GapicFunctionName; + mockedFunction: Function; + }[]; + // The callBackSignaler lets the user of this object get a signal when the mocked function is called. + // This is useful for tests that need to know when the mocked function is called. + callBackSignaler: ( + callbackReached: GapicFunctionName, + request?: RequestType + ) => void = () => {}; + + constructor( + err: Error | null = null, + resp: google.datastore.v1.IBeginTransactionResponse = testRunResp + ) { + const namespace = 'run-without-mock'; + const projectId = 'project-id'; + const options = { + projectId, + namespace, + }; + const datastore = new Datastore(options); + const dataClientName = 'DatastoreClient'; + // Create a fresh transaction for each test because transaction state changes after a commit. + this.transaction = datastore.transaction(); + // In this before hook, save the original beginTransaction method in a variable. + // After tests are finished, reassign beginTransaction to the variable. + // This way, mocking beginTransaction in this block doesn't affect other tests. + const gapic = Object.freeze({ + v1: require('../src/v1'), + }); + // Datastore Gapic clients haven't been initialized yet, so we initialize them here. + datastore.clients_.set( + dataClientName, + new gapic.v1[dataClientName](options) + ); + const dataClient = datastore.clients_.get(dataClientName); + // Mock begin transaction + this.mockedBeginTransaction = () => {}; + if (dataClient && dataClient.beginTransaction) { + this.mockedBeginTransaction = dataClient.beginTransaction; + } + if (dataClient && dataClient.beginTransaction) { + dataClient.beginTransaction = ( + request: protos.google.datastore.v1.IBeginTransactionRequest, + options: CallOptions, + callback: Callback< + protos.google.datastore.v1.IBeginTransactionResponse, + | protos.google.datastore.v1.IBeginTransactionRequest + | null + | undefined, + {} | null | undefined + > + ) => { + // Calls a user provided function that will receive this string + // Usually used to track when this code was reached relative to other code + this.callBackSignaler( + GapicFunctionName.BEGIN_TRANSACTION, + request + ); + callback(err, resp); + }; + } + this.dataClient = dataClient; + this.functionsMocked = []; + this.datastore = datastore; + } + + // This mocks out a gapic function to just call the callback received in the Gapic function. + // The callback will send back the error and response arguments provided as parameters. + mockGapicFunction( + functionName: GapicFunctionName, + response: ResponseType, + error: Error | null + ) { + const dataClient = this.dataClient; + // Check here that function hasn't been mocked out already + // Ensures that this mocking object is not being misused. + this.functionsMocked.forEach(fn => { + if (fn.name === functionName) { + throw Error(`${functionName} has already been mocked out`); + } + }); + if (dataClient && dataClient[functionName]) { + this.functionsMocked.push({ + name: functionName, + mockedFunction: dataClient[functionName], + }); + } + if (dataClient && dataClient[functionName]) { + dataClient[functionName] = ( + request: RequestType, + options: CallOptions, + callback: Callback< + ResponseType, + RequestType | null | undefined, + {} | null | undefined + > + ) => { + this.callBackSignaler(functionName, request); + callback(error, response); + }; + } + } + + // This resets beginTransaction from the Gapic layer to what it originally was. + // Resetting beginTransaction ensures other tests don't use the beginTransaction mock. + resetBeginTransaction() { + if (this.dataClient && this.dataClient.beginTransaction) { + this.dataClient.beginTransaction = this.mockedBeginTransaction; + } + } + + // This resets Gapic functions mocked out by the tests to what they originally were. + // Resetting mocked out Gapic functions ensures other tests don't use these mocked out functions. + resetGapicFunctions() { + this.functionsMocked.forEach(functionMocked => { + if (this.dataClient) { + this.dataClient[functionMocked.name] = + functionMocked.mockedFunction; + } + }); + } + } + + let transactionWrapper: MockedTransactionWrapper; + let transaction: Transaction; + + afterEach(() => { + transactionWrapper.resetBeginTransaction(); + transactionWrapper.resetGapicFunctions(); + }); + + describe('sending an error back from the beginTransaction gapic function', () => { + const testErrorMessage = 'test-beginTransaction-error'; + beforeEach(async () => { + transactionWrapper = new MockedTransactionWrapper( + new Error(testErrorMessage), + undefined + ); + }); + it('should send back the error when awaiting a promise', async () => { + try { + await transactionWrapper.transaction.commit(); + assert.fail('The run call should have failed.'); + } catch (error: any) { + assert.strictEqual(error['message'], testErrorMessage); + } + }); + it('should send back the error when using a callback', done => { + const commitCallback: CommitCallback = ( + error: Error | null | undefined, + response?: google.datastore.v1.ICommitResponse + ) => { + try { + assert(error); + assert.strictEqual(error.message, testErrorMessage); + assert.deepStrictEqual(response, undefined); + done(); + } catch (e) { + done(e); + } + }; + transactionWrapper.transaction.commit(commitCallback); + }); + }); + + describe('commit', () => { + // These tests were created to catch regressions for transaction.commit changes. + const testCommitResp = { + mutationResults: [ + { + key: { + path: [ + { + kind: 'some-kind', + }, + ], + }, + }, + ], + }; + const testErrorMessage = 'test-commit-error'; + + beforeEach(async () => { + transactionWrapper = new MockedTransactionWrapper(); + }); + + describe('should pass error back to the user', async () => { + beforeEach(() => { + transactionWrapper.mockGapicFunction( + GapicFunctionName.COMMIT, + testCommitResp, + new Error(testErrorMessage) + ); + }); + + it('should send back the error when awaiting a promise', async () => { + try { + await transactionWrapper.transaction.run(); + await transactionWrapper.transaction.commit(); + assert.fail('The run call should have failed.'); + } catch (error: any) { + assert.strictEqual(error['message'], testErrorMessage); + } + }); + it('should send back the error when using a callback', done => { + const commitCallback: CommitCallback = ( + error: Error | null | undefined, + response?: google.datastore.v1.ICommitResponse + ) => { + try { + assert(error); + assert.strictEqual(error.message, testErrorMessage); + assert.strictEqual(response, testCommitResp); + done(); + } catch (e) { + done(e); + } + }; + transactionWrapper.transaction.run(() => { + transactionWrapper.transaction.commit(commitCallback); + }); + }); + }); + describe('should pass response back to the user', async () => { + beforeEach(() => { + transactionWrapper.mockGapicFunction( + GapicFunctionName.COMMIT, + testCommitResp, + null + ); + }); + it('should send back the response when awaiting a promise', async () => { + await transactionWrapper.transaction.run(); + const [commitResults] = + await transactionWrapper.transaction.commit(); + assert.strictEqual(commitResults, testCommitResp); + }); + it('should send back the response when using a callback', done => { + const commitCallback: CommitCallback = ( + error: Error | null | undefined, + response?: google.datastore.v1.ICommitResponse + ) => { + try { + assert.strictEqual(error, null); + assert.strictEqual(response, testCommitResp); + done(); + } catch (e) { + done(e); + } + }; + transactionWrapper.transaction.run(() => { + transactionWrapper.transaction.commit(commitCallback); + }); + }); + }); + }); + describe('runAggregationQuery', () => { + // These tests were created to catch regressions for transaction.runAggregationQuery changes. + const runAggregationQueryUserResp = [{'average rating': 100}]; + const runAggregationQueryResp = { + batch: { + aggregationResults: [ + { + aggregateProperties: { + 'average rating': { + meaning: 0, + excludeFromIndexes: false, + doubleValue: 100, + valueType: 'doubleValue', + }, + }, + }, + ], + moreResults: + google.datastore.v1.QueryResultBatch.MoreResultsType + .NO_MORE_RESULTS, + readTime: {seconds: '1699390681', nanos: 961667000}, + }, + query: null, + transaction: testRunResp.transaction, + }; + const testErrorMessage = 'test-run-Aggregate-Query-error'; + let aggregate: AggregateQuery; + + beforeEach(async () => { + transactionWrapper = new MockedTransactionWrapper(); + transaction = transactionWrapper.transaction; + const q = transactionWrapper.datastore.createQuery('Character'); + aggregate = transactionWrapper.datastore + .createAggregationQuery(q) + .addAggregation(AggregateField.average('appearances')); + }); + + describe('should pass error back to the user', async () => { + beforeEach(() => { + transactionWrapper.mockGapicFunction( + GapicFunctionName.RUN_AGGREGATION_QUERY, + runAggregationQueryResp, + new Error(testErrorMessage) + ); + }); + + it('should send back the error when awaiting a promise', async () => { + try { + await transaction.run(); + await transaction.runAggregationQuery(aggregate); + assert.fail('The run call should have failed.'); + } catch (error: any) { + assert.strictEqual(error['message'], testErrorMessage); + } + }); + it('should send back the error when using a callback', done => { + const runAggregateQueryCallback: RequestCallback = ( + error: Error | null | undefined, + response?: unknown + ) => { + try { + assert(error); + assert.strictEqual(error.message, testErrorMessage); + assert.deepStrictEqual(response, runAggregationQueryUserResp); + done(); + } catch (e) { + done(e); + } + }; + transaction.run(() => { + transaction.runAggregationQuery( + aggregate, + runAggregateQueryCallback + ); + }); + }); + }); + describe('should pass response back to the user', async () => { + beforeEach(() => { + transactionWrapper.mockGapicFunction( + GapicFunctionName.RUN_AGGREGATION_QUERY, + runAggregationQueryResp, + null + ); + }); + it('should send back the response when awaiting a promise', async () => { + await transaction.run(); + const allResults = + await transaction.runAggregationQuery(aggregate); + const [runAggregateQueryResults] = allResults; + assert.deepStrictEqual( + runAggregateQueryResults, + runAggregationQueryUserResp + ); + }); + it('should send back the response when using a callback', done => { + const runAggregateQueryCallback: CommitCallback = ( + error: Error | null | undefined, + response?: unknown + ) => { + try { + assert.strictEqual(error, null); + assert.deepStrictEqual(response, runAggregationQueryUserResp); + done(); + } catch (e) { + done(e); + } + }; + transaction.run(() => { + transaction.runAggregationQuery( + aggregate, + runAggregateQueryCallback + ); + }); + }); + }); + }); + describe('runQuery', () => { + // These tests were created to catch regressions for transaction.runQuery changes. + const runQueryResp = { + batch: { + entityResults: [], + endCursor: { + type: 'Buffer', + data: Buffer.from(Array.from(Array(100).keys())), + }, + }, + }; + const runQueryUserResp: Entity[] = []; + const runQueryUserInfo: RunQueryInfo = { + moreResults: undefined, + endCursor: '[object Object]', + }; + const testErrorMessage = 'test-run-Query-error'; + let q: Query; + + beforeEach(async () => { + transactionWrapper = new MockedTransactionWrapper(); + transaction = transactionWrapper.transaction; + q = transactionWrapper.datastore.createQuery('Character'); + }); + + describe('should pass error back to the user', async () => { + beforeEach(() => { + transactionWrapper.mockGapicFunction( + GapicFunctionName.RUN_QUERY, + runQueryResp, + new Error(testErrorMessage) + ); + }); + + it('should send back the error when awaiting a promise', async () => { + try { + await transaction.run(); + await transaction.runQuery(q); + assert.fail('The run call should have failed.'); + } catch (error: any) { + assert.strictEqual(error['message'], testErrorMessage); + } + }); + it('should send back the error when using a callback', done => { + const callback: RunQueryCallback = ( + error: Error | null | undefined, + entities?: Entity[], + info?: RunQueryInfo + ) => { + try { + assert(error); + assert.strictEqual(error.message, testErrorMessage); + assert.deepStrictEqual(entities, undefined); + assert.deepStrictEqual(info, undefined); + done(); + } catch (e) { + done(e); + } + }; + transaction.run(() => { + transaction.runQuery(q, callback); + }); + }); + }); + describe('should pass response back to the user', async () => { + beforeEach(() => { + transactionWrapper.mockGapicFunction( + GapicFunctionName.RUN_QUERY, + runQueryResp, + null + ); + }); + it('should send back the response when awaiting a promise', async () => { + await transaction.run(); + const [runQueryResults, info] = await transaction.runQuery(q); + assert.deepStrictEqual(runQueryResults, runQueryUserResp); + assert.deepStrictEqual(info, runQueryUserInfo); + }); + it('should send back the response when using a callback', done => { + const callback: RunQueryCallback = ( + error: Error | null | undefined, + entities?: Entity[], + info?: RunQueryInfo + ) => { + try { + assert.strictEqual(error, null); + assert.deepStrictEqual(entities, runQueryUserResp); + assert.deepStrictEqual(info, runQueryUserInfo); + done(); + } catch (e) { + done(e); + } + }; + transaction.run(() => { + transaction.runQuery(q, callback); + }); + }); + }); + }); + describe('get', () => { + // These tests were created to catch regressions for transaction.get changes. + const getResp = { + found: [ + { + entity: { + key: { + path: [ + { + kind: 'Post', + name: 'post1', + idType: 'name', + }, + ], + partitionId: { + projectId: 'projectId', + databaseId: 'databaseId', + namespaceId: 'namespaceId', + }, + }, + excludeFromIndexes: false, + properties: {}, + }, + }, + ], + missing: [], + deferred: [], + transaction: testRunResp.transaction, + readTime: { + seconds: '1699470605', + nanos: 201398000, + }, + }; + const getUserResp = 'post1'; + const testErrorMessage = 'test-run-Query-error'; + let key: entity.Key; + + beforeEach(async () => { + transactionWrapper = new MockedTransactionWrapper(); + transaction = transactionWrapper.transaction; + key = transactionWrapper.datastore.key(['Company', 'Google']); + }); + + describe('should pass error back to the user', async () => { + beforeEach(() => { + transactionWrapper.mockGapicFunction( + GapicFunctionName.LOOKUP, + getResp, + new Error(testErrorMessage) + ); + }); + + it('should send back the error when awaiting a promise', async () => { + try { + await transaction.run(); + await transaction.get(key); + assert.fail('The run call should have failed.'); + } catch (error: any) { + assert.strictEqual(error['message'], testErrorMessage); + } + }); + it('should send back the error when using a callback', done => { + const callback: GetCallback = ( + err?: Error | null, + entity?: Entities + ) => { + try { + assert(err); + assert.strictEqual(err.message, testErrorMessage); + assert.deepStrictEqual(entity, undefined); + done(); + } catch (e) { + done(e); + } + }; + transaction.run(() => { + transaction.get(key, callback); + }); + }); + }); + describe('should pass response back to the user', async () => { + beforeEach(() => { + transactionWrapper.mockGapicFunction( + GapicFunctionName.LOOKUP, + getResp, + null + ); + }); + it('should send back the response when awaiting a promise', async () => { + await transaction.run(); + const [results] = await transaction.get(key); + const result = results[transactionWrapper.datastore.KEY]; + assert.deepStrictEqual(result.name, getUserResp); + }); + it('should send back the response when using a callback', done => { + const callback: GetCallback = ( + err?: Error | null, + entity?: Entities + ) => { + try { + const result = entity[transactionWrapper.datastore.KEY]; + assert.strictEqual(err, null); + assert.deepStrictEqual(result.name, getUserResp); + done(); + } catch (e) { + done(e); + } + }; + transaction.run(() => { + transaction.get(key, callback); + }); + }); + }); + }); + describe('concurrency', async () => { + // Items in this enum represent different points in time in the user code. + enum UserCodeEvent { + RUN_CALLBACK, + COMMIT_CALLBACK, + GET_CALLBACK, + RUN_QUERY_CALLBACK, + RUN_AGGREGATION_QUERY_CALLBACK, + CUSTOM_EVENT, + } + // A transaction event represents a point in time particular code is reached + // when running code that uses a transaction. + type TransactionEvent = GapicFunctionName | UserCodeEvent; + + // This object is a sample response from 'commit' in the Gapic layer. + const testCommitResp = { + mutationResults: [ + { + key: { + path: [ + { + kind: 'some-kind', + }, + ], + }, + }, + ], + }; + // This object is a sample response from 'lookup' in the Gapic layer. + const testLookupResp = { + found: [ + { + entity: { + key: { + path: [ + { + kind: 'Post', + name: 'post1', + idType: 'name', + }, + ], + partitionId: { + projectId: 'projectId', + databaseId: 'databaseId', + namespaceId: 'namespaceId', + }, + }, + excludeFromIndexes: false, + properties: {}, + }, + }, + ], + missing: [], + deferred: [], + transaction: testRunResp.transaction, + readTime: { + seconds: '1699470605', + nanos: 201398000, + }, + }; + // This object is a sample response from 'runQuery' in the Gapic layer. + const testRunQueryResp = { + batch: { + entityResults: [], + endCursor: { + type: 'Buffer', + data: Buffer.from(Array.from(Array(100).keys())), + }, + }, + }; + // This object is a sample response from 'runAggregationQuery' in the Gapic layer. + const testRunAggregationQueryResp = { + batch: { + aggregationResults: [ + { + aggregateProperties: { + 'average rating': { + meaning: 0, + excludeFromIndexes: false, + doubleValue: 100, + valueType: 'doubleValue', + }, + }, + }, + ], + moreResults: + google.datastore.v1.QueryResultBatch.MoreResultsType + .NO_MORE_RESULTS, + readTime: {seconds: '1699390681', nanos: 961667000}, + }, + query: null, + transaction: testRunResp.transaction, + }; + let transactionWrapper: MockedTransactionWrapper; + let transaction: Transaction; + + beforeEach(async () => { + transactionWrapper = new MockedTransactionWrapper(); + transaction = transactionWrapper.transaction; + }); + + afterEach(() => { + transactionWrapper.resetBeginTransaction(); + transactionWrapper.resetGapicFunctions(); + }); + + type GapicRequestData = { + call: GapicFunctionName; + request?: RequestType; + }; + + /** + * This object is used for testing the order that different events occur. + * The events can include user code reached, gapic code reached and callbacks called. + * + * @param {MockedTransactionWrapper} [transactionWrapper] A TransactionWrapper instance. + * @param {mocha.Done} [done] A function for signalling the test is complete. + * @param {TransactionEvent[]} [expectedOrder] The order events are expected to occur. + * @param {MockedTransactionWrapper} [transactionWrapper] A TransactionWrapper instance. + */ + class TransactionOrderTester { + /** + * expectedRequests equal the request data in the order they are expected to + * be passed into the Gapic layer. + * @private + */ + readonly #expectedRequests?: GapicRequestData[]; + /** + * requests are the actual order of the requests that are passed into the + * gapic layer + * @private + */ + readonly #requests: GapicRequestData[] = []; + /** + * expectedEventOrder is the order the test expects different events to occur + * such as a callback being called, Gapic functions being called or user + * code being run. + */ + readonly #expectedEventOrder: TransactionEvent[] = []; + /** + * eventOrder is the order events actually occur in the test and will be compared with + * expectedEventOrder. + * @private + */ + #eventOrder: TransactionEvent[] = []; + // A transaction wrapper object is used to contain the transaction and mocked Gapic functions. + #transactionWrapper: MockedTransactionWrapper; + // Stores the mocha done function so that it can be called from this object. + readonly #done: mocha.Done; + + /** + * Each time an event occurs this function is called to check to see if all + * events happened that were supposed to happen. If all events in the test + * happened then this function passes tests if the events happened in the + * right order. + */ + #checkForCompletion() { + if (this.#eventOrder.length >= this.#expectedEventOrder.length) { + try { + assert.deepStrictEqual( + this.#eventOrder, + this.#expectedEventOrder + ); + if (this.#expectedRequests) { + assert.deepStrictEqual( + this.#requests, + this.#expectedRequests + ); + } + this.#done(); + } catch (e) { + this.#done(e); + } + } + } + + constructor( + transactionWrapper: MockedTransactionWrapper, + done: mocha.Done, + expectedOrder: TransactionEvent[], + expectedRequests?: { + call: GapicFunctionName; + request?: RequestType; + }[] + ) { + this.#expectedEventOrder = expectedOrder; + this.#expectedRequests = expectedRequests; + this.#done = done; + transactionWrapper.callBackSignaler = ( + call: GapicFunctionName, + request?: RequestType + ) => { + try { + this.#requests.push({call, request}); + this.#eventOrder.push(call); + this.#checkForCompletion(); + } catch (e) { + done(e); + } + }; + this.#transactionWrapper = transactionWrapper; + } + + /** + * Returns a callback that will record an event so that order of events + * can be compared later. + * + * @param {UserCodeEvent} [event] The event that should be recorded. + */ + push(event: UserCodeEvent) { + return () => { + try { + this.#eventOrder.push(event); + this.#checkForCompletion(); + } catch (e) { + this.#done(e); + } + }; + } + } + + describe('should pass response back to the user', async () => { + beforeEach(() => { + transactionWrapper.mockGapicFunction( + GapicFunctionName.COMMIT, + testCommitResp, + null + ); + }); + + it('should call the callbacks in the proper order with run and commit', done => { + const tester = new TransactionOrderTester( + transactionWrapper, + done, + [ + UserCodeEvent.CUSTOM_EVENT, + GapicFunctionName.BEGIN_TRANSACTION, + UserCodeEvent.RUN_CALLBACK, + GapicFunctionName.COMMIT, + UserCodeEvent.COMMIT_CALLBACK, + ] + ); + transaction.run(tester.push(UserCodeEvent.RUN_CALLBACK)); + transaction.commit(tester.push(UserCodeEvent.COMMIT_CALLBACK)); + tester.push(UserCodeEvent.CUSTOM_EVENT)(); + }); + it('should call the callbacks in the proper order with commit', done => { + const tester = new TransactionOrderTester( + transactionWrapper, + done, + [ + UserCodeEvent.CUSTOM_EVENT, + GapicFunctionName.BEGIN_TRANSACTION, + GapicFunctionName.COMMIT, + UserCodeEvent.COMMIT_CALLBACK, + ] + ); + transaction.commit(tester.push(UserCodeEvent.COMMIT_CALLBACK)); + tester.push(UserCodeEvent.CUSTOM_EVENT)(); + }); + it('should call the callbacks in the proper order with two run calls', done => { + const tester = new TransactionOrderTester( + transactionWrapper, + done, + [ + UserCodeEvent.CUSTOM_EVENT, + GapicFunctionName.BEGIN_TRANSACTION, + UserCodeEvent.RUN_CALLBACK, + UserCodeEvent.RUN_CALLBACK, + ] + ); + transaction.run(tester.push(UserCodeEvent.RUN_CALLBACK)); + transaction.run(tester.push(UserCodeEvent.RUN_CALLBACK)); + tester.push(UserCodeEvent.CUSTOM_EVENT)(); + }); + it('should call the callbacks in the proper order with commit and then run', done => { + const tester = new TransactionOrderTester( + transactionWrapper, + done, + [ + UserCodeEvent.CUSTOM_EVENT, + GapicFunctionName.BEGIN_TRANSACTION, + UserCodeEvent.RUN_CALLBACK, + GapicFunctionName.COMMIT, + UserCodeEvent.COMMIT_CALLBACK, + ] + ); + transaction.commit(tester.push(UserCodeEvent.COMMIT_CALLBACK)); + transaction.run(tester.push(UserCodeEvent.RUN_CALLBACK)); + tester.push(UserCodeEvent.CUSTOM_EVENT)(); + }); + }); + describe('should pass response back to the user and check the request', async () => { + let key: entity.Key; + beforeEach(() => { + key = transactionWrapper.datastore.key(['Company', 'Google']); + transactionWrapper.mockGapicFunction( + GapicFunctionName.COMMIT, + testCommitResp, + null + ); + transactionWrapper.mockGapicFunction( + GapicFunctionName.LOOKUP, + testLookupResp, + null + ); + transactionWrapper.mockGapicFunction( + GapicFunctionName.RUN_QUERY, + testRunQueryResp, + null + ); + transactionWrapper.mockGapicFunction( + GapicFunctionName.RUN_AGGREGATION_QUERY, + testRunAggregationQueryResp, + null + ); + }); + const beginTransactionRequest = { + transactionOptions: {}, + projectId: 'project-id', + }; + const commitRequest = { + mode: 'TRANSACTIONAL', + transaction: testRunResp.transaction, + projectId: 'project-id', + mutations: [ + { + upsert: { + properties: {}, + key: { + partitionId: { + namespaceId: 'run-without-mock', + }, + path: [ + { + kind: 'Company', + name: 'Google', + }, + ], + }, + }, + }, + ], + }; + const lookupTransactionRequest = { + keys: [ + { + partitionId: { + namespaceId: 'run-without-mock', + }, + path: [ + { + kind: 'Company', + name: 'Google', + }, + ], + }, + ], + projectId: 'project-id', + readOptions: { + transaction: testRunResp.transaction, + }, + }; + describe('put, commit', () => { + const expectedRequests = [ + { + call: GapicFunctionName.BEGIN_TRANSACTION, + request: beginTransactionRequest, + }, + { + call: GapicFunctionName.COMMIT, + request: commitRequest, + }, + ]; + it('should verify that there is a BeginTransaction call while beginning later', done => { + const tester = new TransactionOrderTester( + transactionWrapper, + done, + [ + GapicFunctionName.BEGIN_TRANSACTION, + GapicFunctionName.COMMIT, + UserCodeEvent.COMMIT_CALLBACK, + ], + expectedRequests + ); + transaction.save({ + key, + data: '', + }); + transaction.commit(tester.push(UserCodeEvent.COMMIT_CALLBACK)); + }); + it('should verify that there is a BeginTransaction call while beginning early', done => { + const tester = new TransactionOrderTester( + transactionWrapper, + done, + [ + GapicFunctionName.BEGIN_TRANSACTION, + UserCodeEvent.RUN_CALLBACK, + GapicFunctionName.COMMIT, + UserCodeEvent.COMMIT_CALLBACK, + ], + expectedRequests + ); + transaction.save({ + key, + data: '', + }); + transaction.run(tester.push(UserCodeEvent.RUN_CALLBACK)); + transaction.commit(tester.push(UserCodeEvent.COMMIT_CALLBACK)); + }); + }); + describe('lookup, lookup, put, commit', () => { + const expectedRequests = [ + { + call: GapicFunctionName.BEGIN_TRANSACTION, + request: beginTransactionRequest, + }, + { + call: GapicFunctionName.COMMIT, + request: commitRequest, + }, + { + call: GapicFunctionName.LOOKUP, + request: lookupTransactionRequest, + }, + { + call: GapicFunctionName.LOOKUP, + request: lookupTransactionRequest, + }, + ]; + it('should verify that there is a BeginTransaction call while beginning later', done => { + const tester = new TransactionOrderTester( + transactionWrapper, + done, + [ + GapicFunctionName.BEGIN_TRANSACTION, + GapicFunctionName.COMMIT, + UserCodeEvent.COMMIT_CALLBACK, + GapicFunctionName.LOOKUP, + GapicFunctionName.LOOKUP, + UserCodeEvent.GET_CALLBACK, + UserCodeEvent.GET_CALLBACK, + ], + expectedRequests + ); + transaction.get(key, tester.push(UserCodeEvent.GET_CALLBACK)); + transaction.get(key, tester.push(UserCodeEvent.GET_CALLBACK)); + transactionWrapper.transaction.save({ + key, + data: '', + }); + transaction.commit(tester.push(UserCodeEvent.COMMIT_CALLBACK)); + }); + it('should verify that there is a BeginTransaction call while beginning early', done => { + const tester = new TransactionOrderTester( + transactionWrapper, + done, + [ + GapicFunctionName.BEGIN_TRANSACTION, + UserCodeEvent.RUN_CALLBACK, + GapicFunctionName.COMMIT, + UserCodeEvent.COMMIT_CALLBACK, + GapicFunctionName.LOOKUP, + GapicFunctionName.LOOKUP, + UserCodeEvent.GET_CALLBACK, + UserCodeEvent.GET_CALLBACK, + ], + expectedRequests + ); + transaction.run(tester.push(UserCodeEvent.RUN_CALLBACK)); + transaction.get(key, tester.push(UserCodeEvent.GET_CALLBACK)); + transaction.get(key, tester.push(UserCodeEvent.GET_CALLBACK)); + transactionWrapper.transaction.save({ + key, + data: '', + }); + transaction.commit(tester.push(UserCodeEvent.COMMIT_CALLBACK)); + }); + }); + }); + }); + }); + + describe('run without setting up transaction id', () => { + // These tests were created so that when transaction.run is restructured we + // can be confident that it works the same way as before. + const testRunResp = { + transaction: Buffer.from(Array.from(Array(100).keys())), + }; + const namespace = 'run-without-mock'; + const projectId = 'project-id'; + const testErrorMessage = 'test-error'; + const options = { + projectId, + namespace, + }; + const datastore = new Datastore(options); + const transactionWithoutMock = datastore.transaction(); + const dataClientName = 'DatastoreClient'; + let dataClient: ClientStub | undefined; + let originalBeginTransactionMethod: Function; + + beforeEach(async () => { + // In this before hook, save the original beginTransaction method in a variable. + // After tests are finished, reassign beginTransaction to the variable. + // This way, mocking beginTransaction in this block doesn't affect other tests. + const gapic = Object.freeze({ + v1: require('../src/v1'), + }); + // Datastore Gapic clients haven't been initialized yet, so we initialize them here. + datastore.clients_.set( + dataClientName, + new gapic.v1[dataClientName](options) + ); + dataClient = datastore.clients_.get(dataClientName); + if (dataClient && dataClient.beginTransaction) { + originalBeginTransactionMethod = dataClient.beginTransaction; + } + }); + + afterEach(() => { + // beginTransaction has likely been mocked out in these tests. + // We should reassign beginTransaction back to its original value for tests outside this block. + if (dataClient && originalBeginTransactionMethod) { + dataClient.beginTransaction = originalBeginTransactionMethod; + } + }); + + function setupBeginTransaction(err: Error | null | undefined) { + if (dataClient) { + dataClient.beginTransaction = ( + request: protos.google.datastore.v1.IBeginTransactionRequest, + options: CallOptions, + callback: Callback< + protos.google.datastore.v1.IBeginTransactionResponse, + | protos.google.datastore.v1.IBeginTransactionRequest + | null + | undefined, + {} | null | undefined + > + ) => { + callback(err, testRunResp); + }; + } + } + + describe('should pass error back to the user', async () => { + beforeEach(() => { + // Mock out begin transaction and send error back to the user + // from the Gapic layer. + setupBeginTransaction(new Error(testErrorMessage)); + }); + + it('should send back the error when awaiting a promise', async () => { + try { + await transactionWithoutMock.run(); + assert.fail('The run call should have failed.'); + } catch (error: any) { + assert.strictEqual(error['message'], testErrorMessage); + } + }); + it('should send back the error when using a callback', done => { + const runCallback: RunCallback = ( + error: Error | null, + transaction: Transaction | null, + response?: google.datastore.v1.IBeginTransactionResponse + ) => { + try { + assert(error); + assert.strictEqual(error.message, testErrorMessage); + assert.strictEqual(transaction, null); + assert.strictEqual(response, testRunResp); + done(); + } catch (e) { + done(e); + } + }; + transactionWithoutMock.run({}, runCallback); + }); + }); + describe('should pass response back to the user', async () => { + beforeEach(() => { + // Mock out begin transaction and send a response + // back to the user from the Gapic layer. + setupBeginTransaction(null); + }); + it('should send back the response when awaiting a promise', async () => { + const [transaction, resp] = await transactionWithoutMock.run(); + assert.strictEqual(transaction, transactionWithoutMock); + assert.strictEqual(resp, testRunResp); + }); + it('should send back the response when using a callback', done => { + const runCallback: RunCallback = ( + error: Error | null, + transaction: Transaction | null, + response?: google.datastore.v1.IBeginTransactionResponse + ) => { + try { + assert.strictEqual(error, null); + assert.deepStrictEqual(response, testRunResp); + assert.strictEqual(transaction, transactionWithoutMock); + done(); + } catch (e) { + done(e); + } + }; + transactionWithoutMock.run({}, runCallback); + }); + }); + }); + describe('commit', () => { - beforeEach(() => { + beforeEach(done => { transaction.id = TRANSACTION_ID; + transaction.request_ = (config, callback) => { + callback(null, { + transaction: Buffer.from(Array.from(Array(100).keys())), + }); + // Delay to give the transaction mutex the opportunity to unlock before running tests. + setImmediate(() => { + done(); + }); + }; + transaction.run(); }); afterEach(() => { From 0ba1281efe16ef0b725937627445c32c36b9f705 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 12 Mar 2024 11:52:16 +0100 Subject: [PATCH 3/6] fix(deps): update dependency async-mutex to ^0.5.0 (#1240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [async-mutex](https://togithub.com/DirtyHairy/async-mutex) | [`^0.4.0` -> `^0.5.0`](https://renovatebot.com/diffs/npm/async-mutex/0.4.1/0.5.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/async-mutex/0.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/async-mutex/0.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/async-mutex/0.4.1/0.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/async-mutex/0.4.1/0.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
DirtyHairy/async-mutex (async-mutex) ### [`v0.5.0`](https://togithub.com/DirtyHairy/async-mutex/blob/HEAD/CHANGELOG.md#050) [Compare Source](https://togithub.com/DirtyHairy/async-mutex/compare/v0.4.1...v0.5.0) - Support priority queueing for mutexes and semaphores. A huge "thank you" goes to [@​dmurvihill](https://togithub.com/dmurvihill) who added this feature. - Update dependencies.
--- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/nodejs-datastore). --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8423fb7b..08868222 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "dependencies": { "@google-cloud/promisify": "^4.0.0", "arrify": "^2.0.1", - "async-mutex": "^0.4.0", + "async-mutex": "^0.5.0", "concat-stream": "^2.0.0", "extend": "^3.0.2", "google-gax": "^4.0.5", From 8da7c398b6b5c278a3864e5da05ea75a1b0c4c10 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 12 Mar 2024 11:58:16 +0100 Subject: [PATCH 4/6] chore(deps): update dependency gapic-tools to ^0.4.0 (#1238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [gapic-tools](https://togithub.com/googleapis/gax-nodejs) ([source](https://togithub.com/googleapis/gax-nodejs/tree/HEAD/gapic-tools)) | [`^0.3.0` -> `^0.4.0`](https://renovatebot.com/diffs/npm/gapic-tools/0.3.0/0.4.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/gapic-tools/0.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/gapic-tools/0.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/gapic-tools/0.3.0/0.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/gapic-tools/0.3.0/0.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/gax-nodejs (gapic-tools) ### [`v0.4.0`](https://togithub.com/googleapis/gax-nodejs/releases/tag/gapic-tools-v0.4.0): gapic-tools: v0.4.0 [Compare Source](https://togithub.com/googleapis/gax-nodejs/compare/gapic-tools-v0.3.0...gapic-tools-v0.4.0) ##### Features - allow passing --keep-case and --force-number to compileProtos ([#​1561](https://togithub.com/googleapis/gax-nodejs/issues/1561)) ([004d112](https://togithub.com/googleapis/gax-nodejs/commit/004d112445f528a6cb143676e8b397b37137adf3))
--- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/nodejs-datastore). --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 08868222..3318a43e 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@types/sinon": "^17.0.0", "async": "^3.2.4", "c8": "^9.0.0", - "gapic-tools": "^0.3.0", + "gapic-tools": "^0.4.0", "gts": "^5.0.0", "js-yaml": "^4.0.0", "jsdoc": "^4.0.0", From 6c409d5c922288bd8286917b266cdb553cfd43cf Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:22:35 -0400 Subject: [PATCH 5/6] feat: add new types ExplainOptions, ExplainMetrics, PlanSummary, ExecutionStats (#1241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add new types ExplainOptions, ExplainMetrics, PlanSummary, ExecutionStats feat: add ExplainOptions field to RunQueryRequest feat: add ExplainMetrics field to RunQueryResponse feat: add ExplainOptions field to RunAggregationQueryRequest feat: add ExplainMetrics field to RunAggregationQueryResponse PiperOrigin-RevId: 615158168 Source-Link: https://github.com/googleapis/googleapis/commit/4d535ac0538bb2d4b406250d7ec10b25a17a54cf Source-Link: https://github.com/googleapis/googleapis-gen/commit/02e272ded538b0f97832bfad47decbc3dc65a89a Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMDJlMjcyZGVkNTM4YjBmOTc4MzJiZmFkNDdkZWNiYzNkYzY1YTg5YSJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- protos/google/datastore/v1/datastore.proto | 19 + .../google/datastore/v1/query_profile.proto | 90 +- protos/protos.d.ts | 436 +++++++ protos/protos.js | 1087 +++++++++++++++++ protos/protos.json | 76 +- .../v1/datastore.run_aggregation_query.js | 5 + samples/generated/v1/datastore.run_query.js | 5 + .../snippet_metadata_google.datastore.v1.json | 12 +- src/v1/datastore_client.ts | 6 + src/v1/datastore_proto_list.json | 3 +- 10 files changed, 1698 insertions(+), 41 deletions(-) diff --git a/protos/google/datastore/v1/datastore.proto b/protos/google/datastore/v1/datastore.proto index 533988d7..1a3fbdd1 100644 --- a/protos/google/datastore/v1/datastore.proto +++ b/protos/google/datastore/v1/datastore.proto @@ -23,6 +23,7 @@ import "google/api/routing.proto"; import "google/datastore/v1/aggregation_result.proto"; import "google/datastore/v1/entity.proto"; import "google/datastore/v1/query.proto"; +import "google/datastore/v1/query_profile.proto"; import "google/protobuf/timestamp.proto"; option csharp_namespace = "Google.Cloud.Datastore.V1"; @@ -232,6 +233,10 @@ message RunQueryRequest { // The GQL query to run. This query must be a non-aggregation query. GqlQuery gql_query = 7; } + + // Optional. Explain options for the query. If set, additional query + // statistics will be returned. If not, only query results will be returned. + ExplainOptions explain_options = 12 [(google.api.field_behavior) = OPTIONAL]; } // The response for @@ -251,6 +256,11 @@ message RunQueryResponse { // was set in // [RunQueryRequest.read_options][google.datastore.v1.RunQueryRequest.read_options]. bytes transaction = 5; + + // Query explain metrics. This is only present when the + // [RunQueryRequest.explain_options][google.datastore.v1.RunQueryRequest.explain_options] + // is provided, and it is sent only once with the last response in the stream. + ExplainMetrics explain_metrics = 9; } // The request for @@ -282,6 +292,10 @@ message RunAggregationQueryRequest { // The GQL query to run. This query must be an aggregation query. GqlQuery gql_query = 7; } + + // Optional. Explain options for the query. If set, additional query + // statistics will be returned. If not, only query results will be returned. + ExplainOptions explain_options = 11 [(google.api.field_behavior) = OPTIONAL]; } // The response for @@ -301,6 +315,11 @@ message RunAggregationQueryResponse { // was set in // [RunAggregationQueryRequest.read_options][google.datastore.v1.RunAggregationQueryRequest.read_options]. bytes transaction = 5; + + // Query explain metrics. This is only present when the + // [RunAggregationQueryRequest.explain_options][google.datastore.v1.RunAggregationQueryRequest.explain_options] + // is provided, and it is sent only once with the last response in the stream. + ExplainMetrics explain_metrics = 9; } // The request for diff --git a/protos/google/datastore/v1/query_profile.proto b/protos/google/datastore/v1/query_profile.proto index 05c1cf95..01c9fc25 100644 --- a/protos/google/datastore/v1/query_profile.proto +++ b/protos/google/datastore/v1/query_profile.proto @@ -16,6 +16,8 @@ syntax = "proto3"; package google.datastore.v1; +import "google/api/field_behavior.proto"; +import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; option csharp_namespace = "Google.Cloud.Datastore.V1"; @@ -28,48 +30,62 @@ option ruby_package = "Google::Cloud::Datastore::V1"; // Specification of the Datastore Query Profile fields. -// The mode in which the query request must be processed. -enum QueryMode { - // The default mode. Only the query results are returned. - NORMAL = 0; +// Explain options for the query. +message ExplainOptions { + // Optional. Whether to execute this query. + // + // When false (the default), the query will be planned, returning only + // metrics from the planning stages. + // + // When true, the query will be planned and executed, returning the full + // query results along with both planning and execution stage metrics. + bool analyze = 1 [(google.api.field_behavior) = OPTIONAL]; +} - // This mode returns only the query plan, without any results or execution - // statistics information. - PLAN = 1; +// Explain metrics for the query. +message ExplainMetrics { + // Planning phase information for the query. + PlanSummary plan_summary = 1; - // This mode returns both the query plan and the execution statistics along - // with the results. - PROFILE = 2; + // Aggregated stats from the execution of the query. Only present when + // [ExplainOptions.analyze][google.datastore.v1.ExplainOptions.analyze] is set + // to true. + ExecutionStats execution_stats = 2; } -// Plan for the query. -message QueryPlan { - // Planning phase information for the query. It will include: - // - // { - // "indexes_used": [ - // {"query_scope": "Collection", "properties": "(foo ASC, __name__ ASC)"}, - // {"query_scope": "Collection", "properties": "(bar ASC, __name__ ASC)"} - // ] - // } - google.protobuf.Struct plan_info = 1; +// Planning phase information for the query. +message PlanSummary { + // The indexes selected for the query. For example: + // [ + // {"query_scope": "Collection", "properties": "(foo ASC, __name__ ASC)"}, + // {"query_scope": "Collection", "properties": "(bar ASC, __name__ ASC)"} + // ] + repeated google.protobuf.Struct indexes_used = 1; } -// Planning and execution statistics for the query. -message ResultSetStats { - // Plan for the query. - QueryPlan query_plan = 1; +// Execution statistics for the query. +message ExecutionStats { + // Total number of results returned, including documents, projections, + // aggregation results, keys. + int64 results_returned = 1; - // Aggregated statistics from the execution of the query. - // - // This will only be present when the request specifies `PROFILE` mode. - // For example, a query will return the statistics including: - // - // { - // "results_returned": "20", - // "documents_scanned": "20", - // "indexes_entries_scanned": "10050", - // "total_execution_time": "100.7 msecs" - // } - google.protobuf.Struct query_stats = 2; + // Total time to execute the query in the backend. + google.protobuf.Duration execution_duration = 3; + + // Total billable read operations. + int64 read_operations = 4; + + // Debugging statistics from the execution of the query. Note that the + // debugging stats are subject to change as Firestore evolves. It could + // include: + // { + // "indexes_entries_scanned": "1000", + // "documents_scanned": "20", + // "billing_details" : { + // "documents_billable": "20", + // "index_entries_billable": "1000", + // "min_query_cost": "0" + // } + // } + google.protobuf.Struct debug_stats = 5; } diff --git a/protos/protos.d.ts b/protos/protos.d.ts index 63af45b9..fb52086a 100644 --- a/protos/protos.d.ts +++ b/protos/protos.d.ts @@ -5802,6 +5802,9 @@ export namespace google { /** RunQueryRequest gqlQuery */ gqlQuery?: (google.datastore.v1.IGqlQuery|null); + + /** RunQueryRequest explainOptions */ + explainOptions?: (google.datastore.v1.IExplainOptions|null); } /** Represents a RunQueryRequest. */ @@ -5831,6 +5834,9 @@ export namespace google { /** RunQueryRequest gqlQuery. */ public gqlQuery?: (google.datastore.v1.IGqlQuery|null); + /** RunQueryRequest explainOptions. */ + public explainOptions?: (google.datastore.v1.IExplainOptions|null); + /** RunQueryRequest queryType. */ public queryType?: ("query"|"gqlQuery"); @@ -5923,6 +5929,9 @@ export namespace google { /** RunQueryResponse transaction */ transaction?: (Uint8Array|string|null); + + /** RunQueryResponse explainMetrics */ + explainMetrics?: (google.datastore.v1.IExplainMetrics|null); } /** Represents a RunQueryResponse. */ @@ -5943,6 +5952,9 @@ export namespace google { /** RunQueryResponse transaction. */ public transaction: (Uint8Array|string); + /** RunQueryResponse explainMetrics. */ + public explainMetrics?: (google.datastore.v1.IExplainMetrics|null); + /** * Creates a new RunQueryResponse instance using the specified properties. * @param [properties] Properties to set @@ -6041,6 +6053,9 @@ export namespace google { /** RunAggregationQueryRequest gqlQuery */ gqlQuery?: (google.datastore.v1.IGqlQuery|null); + + /** RunAggregationQueryRequest explainOptions */ + explainOptions?: (google.datastore.v1.IExplainOptions|null); } /** Represents a RunAggregationQueryRequest. */ @@ -6070,6 +6085,9 @@ export namespace google { /** RunAggregationQueryRequest gqlQuery. */ public gqlQuery?: (google.datastore.v1.IGqlQuery|null); + /** RunAggregationQueryRequest explainOptions. */ + public explainOptions?: (google.datastore.v1.IExplainOptions|null); + /** RunAggregationQueryRequest queryType. */ public queryType?: ("aggregationQuery"|"gqlQuery"); @@ -6162,6 +6180,9 @@ export namespace google { /** RunAggregationQueryResponse transaction */ transaction?: (Uint8Array|string|null); + + /** RunAggregationQueryResponse explainMetrics */ + explainMetrics?: (google.datastore.v1.IExplainMetrics|null); } /** Represents a RunAggregationQueryResponse. */ @@ -6182,6 +6203,9 @@ export namespace google { /** RunAggregationQueryResponse transaction. */ public transaction: (Uint8Array|string); + /** RunAggregationQueryResponse explainMetrics. */ + public explainMetrics?: (google.datastore.v1.IExplainMetrics|null); + /** * Creates a new RunAggregationQueryResponse instance using the specified properties. * @param [properties] Properties to set @@ -8005,6 +8029,418 @@ export namespace google { public static getTypeUrl(typeUrlPrefix?: string): string; } } + + /** Properties of an ExplainOptions. */ + interface IExplainOptions { + + /** ExplainOptions analyze */ + analyze?: (boolean|null); + } + + /** Represents an ExplainOptions. */ + class ExplainOptions implements IExplainOptions { + + /** + * Constructs a new ExplainOptions. + * @param [properties] Properties to set + */ + constructor(properties?: google.datastore.v1.IExplainOptions); + + /** ExplainOptions analyze. */ + public analyze: boolean; + + /** + * Creates a new ExplainOptions instance using the specified properties. + * @param [properties] Properties to set + * @returns ExplainOptions instance + */ + public static create(properties?: google.datastore.v1.IExplainOptions): google.datastore.v1.ExplainOptions; + + /** + * Encodes the specified ExplainOptions message. Does not implicitly {@link google.datastore.v1.ExplainOptions.verify|verify} messages. + * @param message ExplainOptions message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: google.datastore.v1.IExplainOptions, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified ExplainOptions message, length delimited. Does not implicitly {@link google.datastore.v1.ExplainOptions.verify|verify} messages. + * @param message ExplainOptions message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: google.datastore.v1.IExplainOptions, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an ExplainOptions message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns ExplainOptions + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): google.datastore.v1.ExplainOptions; + + /** + * Decodes an ExplainOptions message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns ExplainOptions + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): google.datastore.v1.ExplainOptions; + + /** + * Verifies an ExplainOptions message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates an ExplainOptions message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns ExplainOptions + */ + public static fromObject(object: { [k: string]: any }): google.datastore.v1.ExplainOptions; + + /** + * Creates a plain object from an ExplainOptions message. Also converts values to other types if specified. + * @param message ExplainOptions + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.datastore.v1.ExplainOptions, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this ExplainOptions to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for ExplainOptions + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of an ExplainMetrics. */ + interface IExplainMetrics { + + /** ExplainMetrics planSummary */ + planSummary?: (google.datastore.v1.IPlanSummary|null); + + /** ExplainMetrics executionStats */ + executionStats?: (google.datastore.v1.IExecutionStats|null); + } + + /** Represents an ExplainMetrics. */ + class ExplainMetrics implements IExplainMetrics { + + /** + * Constructs a new ExplainMetrics. + * @param [properties] Properties to set + */ + constructor(properties?: google.datastore.v1.IExplainMetrics); + + /** ExplainMetrics planSummary. */ + public planSummary?: (google.datastore.v1.IPlanSummary|null); + + /** ExplainMetrics executionStats. */ + public executionStats?: (google.datastore.v1.IExecutionStats|null); + + /** + * Creates a new ExplainMetrics instance using the specified properties. + * @param [properties] Properties to set + * @returns ExplainMetrics instance + */ + public static create(properties?: google.datastore.v1.IExplainMetrics): google.datastore.v1.ExplainMetrics; + + /** + * Encodes the specified ExplainMetrics message. Does not implicitly {@link google.datastore.v1.ExplainMetrics.verify|verify} messages. + * @param message ExplainMetrics message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: google.datastore.v1.IExplainMetrics, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified ExplainMetrics message, length delimited. Does not implicitly {@link google.datastore.v1.ExplainMetrics.verify|verify} messages. + * @param message ExplainMetrics message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: google.datastore.v1.IExplainMetrics, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an ExplainMetrics message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns ExplainMetrics + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): google.datastore.v1.ExplainMetrics; + + /** + * Decodes an ExplainMetrics message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns ExplainMetrics + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): google.datastore.v1.ExplainMetrics; + + /** + * Verifies an ExplainMetrics message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates an ExplainMetrics message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns ExplainMetrics + */ + public static fromObject(object: { [k: string]: any }): google.datastore.v1.ExplainMetrics; + + /** + * Creates a plain object from an ExplainMetrics message. Also converts values to other types if specified. + * @param message ExplainMetrics + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.datastore.v1.ExplainMetrics, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this ExplainMetrics to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for ExplainMetrics + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a PlanSummary. */ + interface IPlanSummary { + + /** PlanSummary indexesUsed */ + indexesUsed?: (google.protobuf.IStruct[]|null); + } + + /** Represents a PlanSummary. */ + class PlanSummary implements IPlanSummary { + + /** + * Constructs a new PlanSummary. + * @param [properties] Properties to set + */ + constructor(properties?: google.datastore.v1.IPlanSummary); + + /** PlanSummary indexesUsed. */ + public indexesUsed: google.protobuf.IStruct[]; + + /** + * Creates a new PlanSummary instance using the specified properties. + * @param [properties] Properties to set + * @returns PlanSummary instance + */ + public static create(properties?: google.datastore.v1.IPlanSummary): google.datastore.v1.PlanSummary; + + /** + * Encodes the specified PlanSummary message. Does not implicitly {@link google.datastore.v1.PlanSummary.verify|verify} messages. + * @param message PlanSummary message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: google.datastore.v1.IPlanSummary, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified PlanSummary message, length delimited. Does not implicitly {@link google.datastore.v1.PlanSummary.verify|verify} messages. + * @param message PlanSummary message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: google.datastore.v1.IPlanSummary, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a PlanSummary message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns PlanSummary + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): google.datastore.v1.PlanSummary; + + /** + * Decodes a PlanSummary message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns PlanSummary + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): google.datastore.v1.PlanSummary; + + /** + * Verifies a PlanSummary message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a PlanSummary message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns PlanSummary + */ + public static fromObject(object: { [k: string]: any }): google.datastore.v1.PlanSummary; + + /** + * Creates a plain object from a PlanSummary message. Also converts values to other types if specified. + * @param message PlanSummary + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.datastore.v1.PlanSummary, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this PlanSummary to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for PlanSummary + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of an ExecutionStats. */ + interface IExecutionStats { + + /** ExecutionStats resultsReturned */ + resultsReturned?: (number|Long|string|null); + + /** ExecutionStats executionDuration */ + executionDuration?: (google.protobuf.IDuration|null); + + /** ExecutionStats readOperations */ + readOperations?: (number|Long|string|null); + + /** ExecutionStats debugStats */ + debugStats?: (google.protobuf.IStruct|null); + } + + /** Represents an ExecutionStats. */ + class ExecutionStats implements IExecutionStats { + + /** + * Constructs a new ExecutionStats. + * @param [properties] Properties to set + */ + constructor(properties?: google.datastore.v1.IExecutionStats); + + /** ExecutionStats resultsReturned. */ + public resultsReturned: (number|Long|string); + + /** ExecutionStats executionDuration. */ + public executionDuration?: (google.protobuf.IDuration|null); + + /** ExecutionStats readOperations. */ + public readOperations: (number|Long|string); + + /** ExecutionStats debugStats. */ + public debugStats?: (google.protobuf.IStruct|null); + + /** + * Creates a new ExecutionStats instance using the specified properties. + * @param [properties] Properties to set + * @returns ExecutionStats instance + */ + public static create(properties?: google.datastore.v1.IExecutionStats): google.datastore.v1.ExecutionStats; + + /** + * Encodes the specified ExecutionStats message. Does not implicitly {@link google.datastore.v1.ExecutionStats.verify|verify} messages. + * @param message ExecutionStats message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: google.datastore.v1.IExecutionStats, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified ExecutionStats message, length delimited. Does not implicitly {@link google.datastore.v1.ExecutionStats.verify|verify} messages. + * @param message ExecutionStats message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: google.datastore.v1.IExecutionStats, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an ExecutionStats message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns ExecutionStats + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): google.datastore.v1.ExecutionStats; + + /** + * Decodes an ExecutionStats message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns ExecutionStats + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): google.datastore.v1.ExecutionStats; + + /** + * Verifies an ExecutionStats message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates an ExecutionStats message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns ExecutionStats + */ + public static fromObject(object: { [k: string]: any }): google.datastore.v1.ExecutionStats; + + /** + * Creates a plain object from an ExecutionStats message. Also converts values to other types if specified. + * @param message ExecutionStats + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.datastore.v1.ExecutionStats, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this ExecutionStats to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for ExecutionStats + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } } } diff --git a/protos/protos.js b/protos/protos.js index 52b16ae1..e9dcfef2 100644 --- a/protos/protos.js +++ b/protos/protos.js @@ -14732,6 +14732,7 @@ * @property {google.datastore.v1.IReadOptions|null} [readOptions] RunQueryRequest readOptions * @property {google.datastore.v1.IQuery|null} [query] RunQueryRequest query * @property {google.datastore.v1.IGqlQuery|null} [gqlQuery] RunQueryRequest gqlQuery + * @property {google.datastore.v1.IExplainOptions|null} [explainOptions] RunQueryRequest explainOptions */ /** @@ -14797,6 +14798,14 @@ */ RunQueryRequest.prototype.gqlQuery = null; + /** + * RunQueryRequest explainOptions. + * @member {google.datastore.v1.IExplainOptions|null|undefined} explainOptions + * @memberof google.datastore.v1.RunQueryRequest + * @instance + */ + RunQueryRequest.prototype.explainOptions = null; + // OneOf field names bound to virtual getters and setters var $oneOfFields; @@ -14847,6 +14856,8 @@ writer.uint32(/* id 8, wireType 2 =*/66).string(message.projectId); if (message.databaseId != null && Object.hasOwnProperty.call(message, "databaseId")) writer.uint32(/* id 9, wireType 2 =*/74).string(message.databaseId); + if (message.explainOptions != null && Object.hasOwnProperty.call(message, "explainOptions")) + $root.google.datastore.v1.ExplainOptions.encode(message.explainOptions, writer.uint32(/* id 12, wireType 2 =*/98).fork()).ldelim(); return writer; }; @@ -14905,6 +14916,10 @@ message.gqlQuery = $root.google.datastore.v1.GqlQuery.decode(reader, reader.uint32()); break; } + case 12: { + message.explainOptions = $root.google.datastore.v1.ExplainOptions.decode(reader, reader.uint32()); + break; + } default: reader.skipType(tag & 7); break; @@ -14975,6 +14990,11 @@ return "gqlQuery." + error; } } + if (message.explainOptions != null && message.hasOwnProperty("explainOptions")) { + var error = $root.google.datastore.v1.ExplainOptions.verify(message.explainOptions); + if (error) + return "explainOptions." + error; + } return null; }; @@ -15014,6 +15034,11 @@ throw TypeError(".google.datastore.v1.RunQueryRequest.gqlQuery: object expected"); message.gqlQuery = $root.google.datastore.v1.GqlQuery.fromObject(object.gqlQuery); } + if (object.explainOptions != null) { + if (typeof object.explainOptions !== "object") + throw TypeError(".google.datastore.v1.RunQueryRequest.explainOptions: object expected"); + message.explainOptions = $root.google.datastore.v1.ExplainOptions.fromObject(object.explainOptions); + } return message; }; @@ -15035,6 +15060,7 @@ object.partitionId = null; object.projectId = ""; object.databaseId = ""; + object.explainOptions = null; } if (message.readOptions != null && message.hasOwnProperty("readOptions")) object.readOptions = $root.google.datastore.v1.ReadOptions.toObject(message.readOptions, options); @@ -15054,6 +15080,8 @@ object.projectId = message.projectId; if (message.databaseId != null && message.hasOwnProperty("databaseId")) object.databaseId = message.databaseId; + if (message.explainOptions != null && message.hasOwnProperty("explainOptions")) + object.explainOptions = $root.google.datastore.v1.ExplainOptions.toObject(message.explainOptions, options); return object; }; @@ -15095,6 +15123,7 @@ * @property {google.datastore.v1.IQueryResultBatch|null} [batch] RunQueryResponse batch * @property {google.datastore.v1.IQuery|null} [query] RunQueryResponse query * @property {Uint8Array|null} [transaction] RunQueryResponse transaction + * @property {google.datastore.v1.IExplainMetrics|null} [explainMetrics] RunQueryResponse explainMetrics */ /** @@ -15136,6 +15165,14 @@ */ RunQueryResponse.prototype.transaction = $util.newBuffer([]); + /** + * RunQueryResponse explainMetrics. + * @member {google.datastore.v1.IExplainMetrics|null|undefined} explainMetrics + * @memberof google.datastore.v1.RunQueryResponse + * @instance + */ + RunQueryResponse.prototype.explainMetrics = null; + /** * Creates a new RunQueryResponse instance using the specified properties. * @function create @@ -15166,6 +15203,8 @@ $root.google.datastore.v1.Query.encode(message.query, writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); if (message.transaction != null && Object.hasOwnProperty.call(message, "transaction")) writer.uint32(/* id 5, wireType 2 =*/42).bytes(message.transaction); + if (message.explainMetrics != null && Object.hasOwnProperty.call(message, "explainMetrics")) + $root.google.datastore.v1.ExplainMetrics.encode(message.explainMetrics, writer.uint32(/* id 9, wireType 2 =*/74).fork()).ldelim(); return writer; }; @@ -15212,6 +15251,10 @@ message.transaction = reader.bytes(); break; } + case 9: { + message.explainMetrics = $root.google.datastore.v1.ExplainMetrics.decode(reader, reader.uint32()); + break; + } default: reader.skipType(tag & 7); break; @@ -15260,6 +15303,11 @@ if (message.transaction != null && message.hasOwnProperty("transaction")) if (!(message.transaction && typeof message.transaction.length === "number" || $util.isString(message.transaction))) return "transaction: buffer expected"; + if (message.explainMetrics != null && message.hasOwnProperty("explainMetrics")) { + var error = $root.google.datastore.v1.ExplainMetrics.verify(message.explainMetrics); + if (error) + return "explainMetrics." + error; + } return null; }; @@ -15290,6 +15338,11 @@ $util.base64.decode(object.transaction, message.transaction = $util.newBuffer($util.base64.length(object.transaction)), 0); else if (object.transaction.length >= 0) message.transaction = object.transaction; + if (object.explainMetrics != null) { + if (typeof object.explainMetrics !== "object") + throw TypeError(".google.datastore.v1.RunQueryResponse.explainMetrics: object expected"); + message.explainMetrics = $root.google.datastore.v1.ExplainMetrics.fromObject(object.explainMetrics); + } return message; }; @@ -15316,6 +15369,7 @@ if (options.bytes !== Array) object.transaction = $util.newBuffer(object.transaction); } + object.explainMetrics = null; } if (message.batch != null && message.hasOwnProperty("batch")) object.batch = $root.google.datastore.v1.QueryResultBatch.toObject(message.batch, options); @@ -15323,6 +15377,8 @@ object.query = $root.google.datastore.v1.Query.toObject(message.query, options); if (message.transaction != null && message.hasOwnProperty("transaction")) object.transaction = options.bytes === String ? $util.base64.encode(message.transaction, 0, message.transaction.length) : options.bytes === Array ? Array.prototype.slice.call(message.transaction) : message.transaction; + if (message.explainMetrics != null && message.hasOwnProperty("explainMetrics")) + object.explainMetrics = $root.google.datastore.v1.ExplainMetrics.toObject(message.explainMetrics, options); return object; }; @@ -15367,6 +15423,7 @@ * @property {google.datastore.v1.IReadOptions|null} [readOptions] RunAggregationQueryRequest readOptions * @property {google.datastore.v1.IAggregationQuery|null} [aggregationQuery] RunAggregationQueryRequest aggregationQuery * @property {google.datastore.v1.IGqlQuery|null} [gqlQuery] RunAggregationQueryRequest gqlQuery + * @property {google.datastore.v1.IExplainOptions|null} [explainOptions] RunAggregationQueryRequest explainOptions */ /** @@ -15432,6 +15489,14 @@ */ RunAggregationQueryRequest.prototype.gqlQuery = null; + /** + * RunAggregationQueryRequest explainOptions. + * @member {google.datastore.v1.IExplainOptions|null|undefined} explainOptions + * @memberof google.datastore.v1.RunAggregationQueryRequest + * @instance + */ + RunAggregationQueryRequest.prototype.explainOptions = null; + // OneOf field names bound to virtual getters and setters var $oneOfFields; @@ -15482,6 +15547,8 @@ writer.uint32(/* id 8, wireType 2 =*/66).string(message.projectId); if (message.databaseId != null && Object.hasOwnProperty.call(message, "databaseId")) writer.uint32(/* id 9, wireType 2 =*/74).string(message.databaseId); + if (message.explainOptions != null && Object.hasOwnProperty.call(message, "explainOptions")) + $root.google.datastore.v1.ExplainOptions.encode(message.explainOptions, writer.uint32(/* id 11, wireType 2 =*/90).fork()).ldelim(); return writer; }; @@ -15540,6 +15607,10 @@ message.gqlQuery = $root.google.datastore.v1.GqlQuery.decode(reader, reader.uint32()); break; } + case 11: { + message.explainOptions = $root.google.datastore.v1.ExplainOptions.decode(reader, reader.uint32()); + break; + } default: reader.skipType(tag & 7); break; @@ -15610,6 +15681,11 @@ return "gqlQuery." + error; } } + if (message.explainOptions != null && message.hasOwnProperty("explainOptions")) { + var error = $root.google.datastore.v1.ExplainOptions.verify(message.explainOptions); + if (error) + return "explainOptions." + error; + } return null; }; @@ -15649,6 +15725,11 @@ throw TypeError(".google.datastore.v1.RunAggregationQueryRequest.gqlQuery: object expected"); message.gqlQuery = $root.google.datastore.v1.GqlQuery.fromObject(object.gqlQuery); } + if (object.explainOptions != null) { + if (typeof object.explainOptions !== "object") + throw TypeError(".google.datastore.v1.RunAggregationQueryRequest.explainOptions: object expected"); + message.explainOptions = $root.google.datastore.v1.ExplainOptions.fromObject(object.explainOptions); + } return message; }; @@ -15670,6 +15751,7 @@ object.partitionId = null; object.projectId = ""; object.databaseId = ""; + object.explainOptions = null; } if (message.readOptions != null && message.hasOwnProperty("readOptions")) object.readOptions = $root.google.datastore.v1.ReadOptions.toObject(message.readOptions, options); @@ -15689,6 +15771,8 @@ object.projectId = message.projectId; if (message.databaseId != null && message.hasOwnProperty("databaseId")) object.databaseId = message.databaseId; + if (message.explainOptions != null && message.hasOwnProperty("explainOptions")) + object.explainOptions = $root.google.datastore.v1.ExplainOptions.toObject(message.explainOptions, options); return object; }; @@ -15730,6 +15814,7 @@ * @property {google.datastore.v1.IAggregationResultBatch|null} [batch] RunAggregationQueryResponse batch * @property {google.datastore.v1.IAggregationQuery|null} [query] RunAggregationQueryResponse query * @property {Uint8Array|null} [transaction] RunAggregationQueryResponse transaction + * @property {google.datastore.v1.IExplainMetrics|null} [explainMetrics] RunAggregationQueryResponse explainMetrics */ /** @@ -15771,6 +15856,14 @@ */ RunAggregationQueryResponse.prototype.transaction = $util.newBuffer([]); + /** + * RunAggregationQueryResponse explainMetrics. + * @member {google.datastore.v1.IExplainMetrics|null|undefined} explainMetrics + * @memberof google.datastore.v1.RunAggregationQueryResponse + * @instance + */ + RunAggregationQueryResponse.prototype.explainMetrics = null; + /** * Creates a new RunAggregationQueryResponse instance using the specified properties. * @function create @@ -15801,6 +15894,8 @@ $root.google.datastore.v1.AggregationQuery.encode(message.query, writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); if (message.transaction != null && Object.hasOwnProperty.call(message, "transaction")) writer.uint32(/* id 5, wireType 2 =*/42).bytes(message.transaction); + if (message.explainMetrics != null && Object.hasOwnProperty.call(message, "explainMetrics")) + $root.google.datastore.v1.ExplainMetrics.encode(message.explainMetrics, writer.uint32(/* id 9, wireType 2 =*/74).fork()).ldelim(); return writer; }; @@ -15847,6 +15942,10 @@ message.transaction = reader.bytes(); break; } + case 9: { + message.explainMetrics = $root.google.datastore.v1.ExplainMetrics.decode(reader, reader.uint32()); + break; + } default: reader.skipType(tag & 7); break; @@ -15895,6 +15994,11 @@ if (message.transaction != null && message.hasOwnProperty("transaction")) if (!(message.transaction && typeof message.transaction.length === "number" || $util.isString(message.transaction))) return "transaction: buffer expected"; + if (message.explainMetrics != null && message.hasOwnProperty("explainMetrics")) { + var error = $root.google.datastore.v1.ExplainMetrics.verify(message.explainMetrics); + if (error) + return "explainMetrics." + error; + } return null; }; @@ -15925,6 +16029,11 @@ $util.base64.decode(object.transaction, message.transaction = $util.newBuffer($util.base64.length(object.transaction)), 0); else if (object.transaction.length >= 0) message.transaction = object.transaction; + if (object.explainMetrics != null) { + if (typeof object.explainMetrics !== "object") + throw TypeError(".google.datastore.v1.RunAggregationQueryResponse.explainMetrics: object expected"); + message.explainMetrics = $root.google.datastore.v1.ExplainMetrics.fromObject(object.explainMetrics); + } return message; }; @@ -15951,6 +16060,7 @@ if (options.bytes !== Array) object.transaction = $util.newBuffer(object.transaction); } + object.explainMetrics = null; } if (message.batch != null && message.hasOwnProperty("batch")) object.batch = $root.google.datastore.v1.AggregationResultBatch.toObject(message.batch, options); @@ -15958,6 +16068,8 @@ object.query = $root.google.datastore.v1.AggregationQuery.toObject(message.query, options); if (message.transaction != null && message.hasOwnProperty("transaction")) object.transaction = options.bytes === String ? $util.base64.encode(message.transaction, 0, message.transaction.length) : options.bytes === Array ? Array.prototype.slice.call(message.transaction) : message.transaction; + if (message.explainMetrics != null && message.hasOwnProperty("explainMetrics")) + object.explainMetrics = $root.google.datastore.v1.ExplainMetrics.toObject(message.explainMetrics, options); return object; }; @@ -20311,6 +20423,981 @@ return TransactionOptions; })(); + v1.ExplainOptions = (function() { + + /** + * Properties of an ExplainOptions. + * @memberof google.datastore.v1 + * @interface IExplainOptions + * @property {boolean|null} [analyze] ExplainOptions analyze + */ + + /** + * Constructs a new ExplainOptions. + * @memberof google.datastore.v1 + * @classdesc Represents an ExplainOptions. + * @implements IExplainOptions + * @constructor + * @param {google.datastore.v1.IExplainOptions=} [properties] Properties to set + */ + function ExplainOptions(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * ExplainOptions analyze. + * @member {boolean} analyze + * @memberof google.datastore.v1.ExplainOptions + * @instance + */ + ExplainOptions.prototype.analyze = false; + + /** + * Creates a new ExplainOptions instance using the specified properties. + * @function create + * @memberof google.datastore.v1.ExplainOptions + * @static + * @param {google.datastore.v1.IExplainOptions=} [properties] Properties to set + * @returns {google.datastore.v1.ExplainOptions} ExplainOptions instance + */ + ExplainOptions.create = function create(properties) { + return new ExplainOptions(properties); + }; + + /** + * Encodes the specified ExplainOptions message. Does not implicitly {@link google.datastore.v1.ExplainOptions.verify|verify} messages. + * @function encode + * @memberof google.datastore.v1.ExplainOptions + * @static + * @param {google.datastore.v1.IExplainOptions} message ExplainOptions message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + ExplainOptions.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.analyze != null && Object.hasOwnProperty.call(message, "analyze")) + writer.uint32(/* id 1, wireType 0 =*/8).bool(message.analyze); + return writer; + }; + + /** + * Encodes the specified ExplainOptions message, length delimited. Does not implicitly {@link google.datastore.v1.ExplainOptions.verify|verify} messages. + * @function encodeDelimited + * @memberof google.datastore.v1.ExplainOptions + * @static + * @param {google.datastore.v1.IExplainOptions} message ExplainOptions message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + ExplainOptions.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an ExplainOptions message from the specified reader or buffer. + * @function decode + * @memberof google.datastore.v1.ExplainOptions + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {google.datastore.v1.ExplainOptions} ExplainOptions + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + ExplainOptions.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.google.datastore.v1.ExplainOptions(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.analyze = reader.bool(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an ExplainOptions message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof google.datastore.v1.ExplainOptions + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {google.datastore.v1.ExplainOptions} ExplainOptions + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + ExplainOptions.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an ExplainOptions message. + * @function verify + * @memberof google.datastore.v1.ExplainOptions + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + ExplainOptions.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.analyze != null && message.hasOwnProperty("analyze")) + if (typeof message.analyze !== "boolean") + return "analyze: boolean expected"; + return null; + }; + + /** + * Creates an ExplainOptions message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.datastore.v1.ExplainOptions + * @static + * @param {Object.} object Plain object + * @returns {google.datastore.v1.ExplainOptions} ExplainOptions + */ + ExplainOptions.fromObject = function fromObject(object) { + if (object instanceof $root.google.datastore.v1.ExplainOptions) + return object; + var message = new $root.google.datastore.v1.ExplainOptions(); + if (object.analyze != null) + message.analyze = Boolean(object.analyze); + return message; + }; + + /** + * Creates a plain object from an ExplainOptions message. Also converts values to other types if specified. + * @function toObject + * @memberof google.datastore.v1.ExplainOptions + * @static + * @param {google.datastore.v1.ExplainOptions} message ExplainOptions + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + ExplainOptions.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) + object.analyze = false; + if (message.analyze != null && message.hasOwnProperty("analyze")) + object.analyze = message.analyze; + return object; + }; + + /** + * Converts this ExplainOptions to JSON. + * @function toJSON + * @memberof google.datastore.v1.ExplainOptions + * @instance + * @returns {Object.} JSON object + */ + ExplainOptions.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for ExplainOptions + * @function getTypeUrl + * @memberof google.datastore.v1.ExplainOptions + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + ExplainOptions.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.datastore.v1.ExplainOptions"; + }; + + return ExplainOptions; + })(); + + v1.ExplainMetrics = (function() { + + /** + * Properties of an ExplainMetrics. + * @memberof google.datastore.v1 + * @interface IExplainMetrics + * @property {google.datastore.v1.IPlanSummary|null} [planSummary] ExplainMetrics planSummary + * @property {google.datastore.v1.IExecutionStats|null} [executionStats] ExplainMetrics executionStats + */ + + /** + * Constructs a new ExplainMetrics. + * @memberof google.datastore.v1 + * @classdesc Represents an ExplainMetrics. + * @implements IExplainMetrics + * @constructor + * @param {google.datastore.v1.IExplainMetrics=} [properties] Properties to set + */ + function ExplainMetrics(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * ExplainMetrics planSummary. + * @member {google.datastore.v1.IPlanSummary|null|undefined} planSummary + * @memberof google.datastore.v1.ExplainMetrics + * @instance + */ + ExplainMetrics.prototype.planSummary = null; + + /** + * ExplainMetrics executionStats. + * @member {google.datastore.v1.IExecutionStats|null|undefined} executionStats + * @memberof google.datastore.v1.ExplainMetrics + * @instance + */ + ExplainMetrics.prototype.executionStats = null; + + /** + * Creates a new ExplainMetrics instance using the specified properties. + * @function create + * @memberof google.datastore.v1.ExplainMetrics + * @static + * @param {google.datastore.v1.IExplainMetrics=} [properties] Properties to set + * @returns {google.datastore.v1.ExplainMetrics} ExplainMetrics instance + */ + ExplainMetrics.create = function create(properties) { + return new ExplainMetrics(properties); + }; + + /** + * Encodes the specified ExplainMetrics message. Does not implicitly {@link google.datastore.v1.ExplainMetrics.verify|verify} messages. + * @function encode + * @memberof google.datastore.v1.ExplainMetrics + * @static + * @param {google.datastore.v1.IExplainMetrics} message ExplainMetrics message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + ExplainMetrics.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.planSummary != null && Object.hasOwnProperty.call(message, "planSummary")) + $root.google.datastore.v1.PlanSummary.encode(message.planSummary, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); + if (message.executionStats != null && Object.hasOwnProperty.call(message, "executionStats")) + $root.google.datastore.v1.ExecutionStats.encode(message.executionStats, writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); + return writer; + }; + + /** + * Encodes the specified ExplainMetrics message, length delimited. Does not implicitly {@link google.datastore.v1.ExplainMetrics.verify|verify} messages. + * @function encodeDelimited + * @memberof google.datastore.v1.ExplainMetrics + * @static + * @param {google.datastore.v1.IExplainMetrics} message ExplainMetrics message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + ExplainMetrics.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an ExplainMetrics message from the specified reader or buffer. + * @function decode + * @memberof google.datastore.v1.ExplainMetrics + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {google.datastore.v1.ExplainMetrics} ExplainMetrics + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + ExplainMetrics.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.google.datastore.v1.ExplainMetrics(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.planSummary = $root.google.datastore.v1.PlanSummary.decode(reader, reader.uint32()); + break; + } + case 2: { + message.executionStats = $root.google.datastore.v1.ExecutionStats.decode(reader, reader.uint32()); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an ExplainMetrics message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof google.datastore.v1.ExplainMetrics + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {google.datastore.v1.ExplainMetrics} ExplainMetrics + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + ExplainMetrics.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an ExplainMetrics message. + * @function verify + * @memberof google.datastore.v1.ExplainMetrics + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + ExplainMetrics.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.planSummary != null && message.hasOwnProperty("planSummary")) { + var error = $root.google.datastore.v1.PlanSummary.verify(message.planSummary); + if (error) + return "planSummary." + error; + } + if (message.executionStats != null && message.hasOwnProperty("executionStats")) { + var error = $root.google.datastore.v1.ExecutionStats.verify(message.executionStats); + if (error) + return "executionStats." + error; + } + return null; + }; + + /** + * Creates an ExplainMetrics message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.datastore.v1.ExplainMetrics + * @static + * @param {Object.} object Plain object + * @returns {google.datastore.v1.ExplainMetrics} ExplainMetrics + */ + ExplainMetrics.fromObject = function fromObject(object) { + if (object instanceof $root.google.datastore.v1.ExplainMetrics) + return object; + var message = new $root.google.datastore.v1.ExplainMetrics(); + if (object.planSummary != null) { + if (typeof object.planSummary !== "object") + throw TypeError(".google.datastore.v1.ExplainMetrics.planSummary: object expected"); + message.planSummary = $root.google.datastore.v1.PlanSummary.fromObject(object.planSummary); + } + if (object.executionStats != null) { + if (typeof object.executionStats !== "object") + throw TypeError(".google.datastore.v1.ExplainMetrics.executionStats: object expected"); + message.executionStats = $root.google.datastore.v1.ExecutionStats.fromObject(object.executionStats); + } + return message; + }; + + /** + * Creates a plain object from an ExplainMetrics message. Also converts values to other types if specified. + * @function toObject + * @memberof google.datastore.v1.ExplainMetrics + * @static + * @param {google.datastore.v1.ExplainMetrics} message ExplainMetrics + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + ExplainMetrics.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.planSummary = null; + object.executionStats = null; + } + if (message.planSummary != null && message.hasOwnProperty("planSummary")) + object.planSummary = $root.google.datastore.v1.PlanSummary.toObject(message.planSummary, options); + if (message.executionStats != null && message.hasOwnProperty("executionStats")) + object.executionStats = $root.google.datastore.v1.ExecutionStats.toObject(message.executionStats, options); + return object; + }; + + /** + * Converts this ExplainMetrics to JSON. + * @function toJSON + * @memberof google.datastore.v1.ExplainMetrics + * @instance + * @returns {Object.} JSON object + */ + ExplainMetrics.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for ExplainMetrics + * @function getTypeUrl + * @memberof google.datastore.v1.ExplainMetrics + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + ExplainMetrics.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.datastore.v1.ExplainMetrics"; + }; + + return ExplainMetrics; + })(); + + v1.PlanSummary = (function() { + + /** + * Properties of a PlanSummary. + * @memberof google.datastore.v1 + * @interface IPlanSummary + * @property {Array.|null} [indexesUsed] PlanSummary indexesUsed + */ + + /** + * Constructs a new PlanSummary. + * @memberof google.datastore.v1 + * @classdesc Represents a PlanSummary. + * @implements IPlanSummary + * @constructor + * @param {google.datastore.v1.IPlanSummary=} [properties] Properties to set + */ + function PlanSummary(properties) { + this.indexesUsed = []; + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * PlanSummary indexesUsed. + * @member {Array.} indexesUsed + * @memberof google.datastore.v1.PlanSummary + * @instance + */ + PlanSummary.prototype.indexesUsed = $util.emptyArray; + + /** + * Creates a new PlanSummary instance using the specified properties. + * @function create + * @memberof google.datastore.v1.PlanSummary + * @static + * @param {google.datastore.v1.IPlanSummary=} [properties] Properties to set + * @returns {google.datastore.v1.PlanSummary} PlanSummary instance + */ + PlanSummary.create = function create(properties) { + return new PlanSummary(properties); + }; + + /** + * Encodes the specified PlanSummary message. Does not implicitly {@link google.datastore.v1.PlanSummary.verify|verify} messages. + * @function encode + * @memberof google.datastore.v1.PlanSummary + * @static + * @param {google.datastore.v1.IPlanSummary} message PlanSummary message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + PlanSummary.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.indexesUsed != null && message.indexesUsed.length) + for (var i = 0; i < message.indexesUsed.length; ++i) + $root.google.protobuf.Struct.encode(message.indexesUsed[i], writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); + return writer; + }; + + /** + * Encodes the specified PlanSummary message, length delimited. Does not implicitly {@link google.datastore.v1.PlanSummary.verify|verify} messages. + * @function encodeDelimited + * @memberof google.datastore.v1.PlanSummary + * @static + * @param {google.datastore.v1.IPlanSummary} message PlanSummary message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + PlanSummary.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a PlanSummary message from the specified reader or buffer. + * @function decode + * @memberof google.datastore.v1.PlanSummary + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {google.datastore.v1.PlanSummary} PlanSummary + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + PlanSummary.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.google.datastore.v1.PlanSummary(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (!(message.indexesUsed && message.indexesUsed.length)) + message.indexesUsed = []; + message.indexesUsed.push($root.google.protobuf.Struct.decode(reader, reader.uint32())); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a PlanSummary message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof google.datastore.v1.PlanSummary + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {google.datastore.v1.PlanSummary} PlanSummary + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + PlanSummary.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a PlanSummary message. + * @function verify + * @memberof google.datastore.v1.PlanSummary + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + PlanSummary.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.indexesUsed != null && message.hasOwnProperty("indexesUsed")) { + if (!Array.isArray(message.indexesUsed)) + return "indexesUsed: array expected"; + for (var i = 0; i < message.indexesUsed.length; ++i) { + var error = $root.google.protobuf.Struct.verify(message.indexesUsed[i]); + if (error) + return "indexesUsed." + error; + } + } + return null; + }; + + /** + * Creates a PlanSummary message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.datastore.v1.PlanSummary + * @static + * @param {Object.} object Plain object + * @returns {google.datastore.v1.PlanSummary} PlanSummary + */ + PlanSummary.fromObject = function fromObject(object) { + if (object instanceof $root.google.datastore.v1.PlanSummary) + return object; + var message = new $root.google.datastore.v1.PlanSummary(); + if (object.indexesUsed) { + if (!Array.isArray(object.indexesUsed)) + throw TypeError(".google.datastore.v1.PlanSummary.indexesUsed: array expected"); + message.indexesUsed = []; + for (var i = 0; i < object.indexesUsed.length; ++i) { + if (typeof object.indexesUsed[i] !== "object") + throw TypeError(".google.datastore.v1.PlanSummary.indexesUsed: object expected"); + message.indexesUsed[i] = $root.google.protobuf.Struct.fromObject(object.indexesUsed[i]); + } + } + return message; + }; + + /** + * Creates a plain object from a PlanSummary message. Also converts values to other types if specified. + * @function toObject + * @memberof google.datastore.v1.PlanSummary + * @static + * @param {google.datastore.v1.PlanSummary} message PlanSummary + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + PlanSummary.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) + object.indexesUsed = []; + if (message.indexesUsed && message.indexesUsed.length) { + object.indexesUsed = []; + for (var j = 0; j < message.indexesUsed.length; ++j) + object.indexesUsed[j] = $root.google.protobuf.Struct.toObject(message.indexesUsed[j], options); + } + return object; + }; + + /** + * Converts this PlanSummary to JSON. + * @function toJSON + * @memberof google.datastore.v1.PlanSummary + * @instance + * @returns {Object.} JSON object + */ + PlanSummary.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for PlanSummary + * @function getTypeUrl + * @memberof google.datastore.v1.PlanSummary + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + PlanSummary.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.datastore.v1.PlanSummary"; + }; + + return PlanSummary; + })(); + + v1.ExecutionStats = (function() { + + /** + * Properties of an ExecutionStats. + * @memberof google.datastore.v1 + * @interface IExecutionStats + * @property {number|Long|null} [resultsReturned] ExecutionStats resultsReturned + * @property {google.protobuf.IDuration|null} [executionDuration] ExecutionStats executionDuration + * @property {number|Long|null} [readOperations] ExecutionStats readOperations + * @property {google.protobuf.IStruct|null} [debugStats] ExecutionStats debugStats + */ + + /** + * Constructs a new ExecutionStats. + * @memberof google.datastore.v1 + * @classdesc Represents an ExecutionStats. + * @implements IExecutionStats + * @constructor + * @param {google.datastore.v1.IExecutionStats=} [properties] Properties to set + */ + function ExecutionStats(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * ExecutionStats resultsReturned. + * @member {number|Long} resultsReturned + * @memberof google.datastore.v1.ExecutionStats + * @instance + */ + ExecutionStats.prototype.resultsReturned = $util.Long ? $util.Long.fromBits(0,0,false) : 0; + + /** + * ExecutionStats executionDuration. + * @member {google.protobuf.IDuration|null|undefined} executionDuration + * @memberof google.datastore.v1.ExecutionStats + * @instance + */ + ExecutionStats.prototype.executionDuration = null; + + /** + * ExecutionStats readOperations. + * @member {number|Long} readOperations + * @memberof google.datastore.v1.ExecutionStats + * @instance + */ + ExecutionStats.prototype.readOperations = $util.Long ? $util.Long.fromBits(0,0,false) : 0; + + /** + * ExecutionStats debugStats. + * @member {google.protobuf.IStruct|null|undefined} debugStats + * @memberof google.datastore.v1.ExecutionStats + * @instance + */ + ExecutionStats.prototype.debugStats = null; + + /** + * Creates a new ExecutionStats instance using the specified properties. + * @function create + * @memberof google.datastore.v1.ExecutionStats + * @static + * @param {google.datastore.v1.IExecutionStats=} [properties] Properties to set + * @returns {google.datastore.v1.ExecutionStats} ExecutionStats instance + */ + ExecutionStats.create = function create(properties) { + return new ExecutionStats(properties); + }; + + /** + * Encodes the specified ExecutionStats message. Does not implicitly {@link google.datastore.v1.ExecutionStats.verify|verify} messages. + * @function encode + * @memberof google.datastore.v1.ExecutionStats + * @static + * @param {google.datastore.v1.IExecutionStats} message ExecutionStats message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + ExecutionStats.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.resultsReturned != null && Object.hasOwnProperty.call(message, "resultsReturned")) + writer.uint32(/* id 1, wireType 0 =*/8).int64(message.resultsReturned); + if (message.executionDuration != null && Object.hasOwnProperty.call(message, "executionDuration")) + $root.google.protobuf.Duration.encode(message.executionDuration, writer.uint32(/* id 3, wireType 2 =*/26).fork()).ldelim(); + if (message.readOperations != null && Object.hasOwnProperty.call(message, "readOperations")) + writer.uint32(/* id 4, wireType 0 =*/32).int64(message.readOperations); + if (message.debugStats != null && Object.hasOwnProperty.call(message, "debugStats")) + $root.google.protobuf.Struct.encode(message.debugStats, writer.uint32(/* id 5, wireType 2 =*/42).fork()).ldelim(); + return writer; + }; + + /** + * Encodes the specified ExecutionStats message, length delimited. Does not implicitly {@link google.datastore.v1.ExecutionStats.verify|verify} messages. + * @function encodeDelimited + * @memberof google.datastore.v1.ExecutionStats + * @static + * @param {google.datastore.v1.IExecutionStats} message ExecutionStats message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + ExecutionStats.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an ExecutionStats message from the specified reader or buffer. + * @function decode + * @memberof google.datastore.v1.ExecutionStats + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {google.datastore.v1.ExecutionStats} ExecutionStats + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + ExecutionStats.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.google.datastore.v1.ExecutionStats(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.resultsReturned = reader.int64(); + break; + } + case 3: { + message.executionDuration = $root.google.protobuf.Duration.decode(reader, reader.uint32()); + break; + } + case 4: { + message.readOperations = reader.int64(); + break; + } + case 5: { + message.debugStats = $root.google.protobuf.Struct.decode(reader, reader.uint32()); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an ExecutionStats message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof google.datastore.v1.ExecutionStats + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {google.datastore.v1.ExecutionStats} ExecutionStats + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + ExecutionStats.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an ExecutionStats message. + * @function verify + * @memberof google.datastore.v1.ExecutionStats + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + ExecutionStats.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.resultsReturned != null && message.hasOwnProperty("resultsReturned")) + if (!$util.isInteger(message.resultsReturned) && !(message.resultsReturned && $util.isInteger(message.resultsReturned.low) && $util.isInteger(message.resultsReturned.high))) + return "resultsReturned: integer|Long expected"; + if (message.executionDuration != null && message.hasOwnProperty("executionDuration")) { + var error = $root.google.protobuf.Duration.verify(message.executionDuration); + if (error) + return "executionDuration." + error; + } + if (message.readOperations != null && message.hasOwnProperty("readOperations")) + if (!$util.isInteger(message.readOperations) && !(message.readOperations && $util.isInteger(message.readOperations.low) && $util.isInteger(message.readOperations.high))) + return "readOperations: integer|Long expected"; + if (message.debugStats != null && message.hasOwnProperty("debugStats")) { + var error = $root.google.protobuf.Struct.verify(message.debugStats); + if (error) + return "debugStats." + error; + } + return null; + }; + + /** + * Creates an ExecutionStats message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.datastore.v1.ExecutionStats + * @static + * @param {Object.} object Plain object + * @returns {google.datastore.v1.ExecutionStats} ExecutionStats + */ + ExecutionStats.fromObject = function fromObject(object) { + if (object instanceof $root.google.datastore.v1.ExecutionStats) + return object; + var message = new $root.google.datastore.v1.ExecutionStats(); + if (object.resultsReturned != null) + if ($util.Long) + (message.resultsReturned = $util.Long.fromValue(object.resultsReturned)).unsigned = false; + else if (typeof object.resultsReturned === "string") + message.resultsReturned = parseInt(object.resultsReturned, 10); + else if (typeof object.resultsReturned === "number") + message.resultsReturned = object.resultsReturned; + else if (typeof object.resultsReturned === "object") + message.resultsReturned = new $util.LongBits(object.resultsReturned.low >>> 0, object.resultsReturned.high >>> 0).toNumber(); + if (object.executionDuration != null) { + if (typeof object.executionDuration !== "object") + throw TypeError(".google.datastore.v1.ExecutionStats.executionDuration: object expected"); + message.executionDuration = $root.google.protobuf.Duration.fromObject(object.executionDuration); + } + if (object.readOperations != null) + if ($util.Long) + (message.readOperations = $util.Long.fromValue(object.readOperations)).unsigned = false; + else if (typeof object.readOperations === "string") + message.readOperations = parseInt(object.readOperations, 10); + else if (typeof object.readOperations === "number") + message.readOperations = object.readOperations; + else if (typeof object.readOperations === "object") + message.readOperations = new $util.LongBits(object.readOperations.low >>> 0, object.readOperations.high >>> 0).toNumber(); + if (object.debugStats != null) { + if (typeof object.debugStats !== "object") + throw TypeError(".google.datastore.v1.ExecutionStats.debugStats: object expected"); + message.debugStats = $root.google.protobuf.Struct.fromObject(object.debugStats); + } + return message; + }; + + /** + * Creates a plain object from an ExecutionStats message. Also converts values to other types if specified. + * @function toObject + * @memberof google.datastore.v1.ExecutionStats + * @static + * @param {google.datastore.v1.ExecutionStats} message ExecutionStats + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + ExecutionStats.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + if ($util.Long) { + var long = new $util.Long(0, 0, false); + object.resultsReturned = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; + } else + object.resultsReturned = options.longs === String ? "0" : 0; + object.executionDuration = null; + if ($util.Long) { + var long = new $util.Long(0, 0, false); + object.readOperations = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; + } else + object.readOperations = options.longs === String ? "0" : 0; + object.debugStats = null; + } + if (message.resultsReturned != null && message.hasOwnProperty("resultsReturned")) + if (typeof message.resultsReturned === "number") + object.resultsReturned = options.longs === String ? String(message.resultsReturned) : message.resultsReturned; + else + object.resultsReturned = options.longs === String ? $util.Long.prototype.toString.call(message.resultsReturned) : options.longs === Number ? new $util.LongBits(message.resultsReturned.low >>> 0, message.resultsReturned.high >>> 0).toNumber() : message.resultsReturned; + if (message.executionDuration != null && message.hasOwnProperty("executionDuration")) + object.executionDuration = $root.google.protobuf.Duration.toObject(message.executionDuration, options); + if (message.readOperations != null && message.hasOwnProperty("readOperations")) + if (typeof message.readOperations === "number") + object.readOperations = options.longs === String ? String(message.readOperations) : message.readOperations; + else + object.readOperations = options.longs === String ? $util.Long.prototype.toString.call(message.readOperations) : options.longs === Number ? new $util.LongBits(message.readOperations.low >>> 0, message.readOperations.high >>> 0).toNumber() : message.readOperations; + if (message.debugStats != null && message.hasOwnProperty("debugStats")) + object.debugStats = $root.google.protobuf.Struct.toObject(message.debugStats, options); + return object; + }; + + /** + * Converts this ExecutionStats to JSON. + * @function toJSON + * @memberof google.datastore.v1.ExecutionStats + * @instance + * @returns {Object.} JSON object + */ + ExecutionStats.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for ExecutionStats + * @function getTypeUrl + * @memberof google.datastore.v1.ExecutionStats + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + ExecutionStats.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.datastore.v1.ExecutionStats"; + }; + + return ExecutionStats; + })(); + return v1; })(); diff --git a/protos/protos.json b/protos/protos.json index 2fdb4bfd..44145135 100644 --- a/protos/protos.json +++ b/protos/protos.json @@ -613,7 +613,7 @@ "csharp_namespace": "Google.Cloud.Datastore.V1", "go_package": "google.golang.org/genproto/googleapis/datastore/v1;datastore", "java_multiple_files": true, - "java_outer_classname": "DatastoreProto", + "java_outer_classname": "QueryProfileProto", "java_package": "com.google.datastore.v1", "php_namespace": "Google\\Cloud\\Datastore\\V1", "ruby_package": "Google::Cloud::Datastore::V1" @@ -1525,6 +1525,13 @@ "gqlQuery": { "type": "GqlQuery", "id": 7 + }, + "explainOptions": { + "type": "ExplainOptions", + "id": 12, + "options": { + "(google.api.field_behavior)": "OPTIONAL" + } } } }, @@ -1541,6 +1548,10 @@ "transaction": { "type": "bytes", "id": 5 + }, + "explainMetrics": { + "type": "ExplainMetrics", + "id": 9 } } }, @@ -1580,6 +1591,13 @@ "gqlQuery": { "type": "GqlQuery", "id": 7 + }, + "explainOptions": { + "type": "ExplainOptions", + "id": 11, + "options": { + "(google.api.field_behavior)": "OPTIONAL" + } } } }, @@ -1596,6 +1614,10 @@ "transaction": { "type": "bytes", "id": 5 + }, + "explainMetrics": { + "type": "ExplainMetrics", + "id": 9 } } }, @@ -1919,6 +1941,58 @@ } } } + }, + "ExplainOptions": { + "fields": { + "analyze": { + "type": "bool", + "id": 1, + "options": { + "(google.api.field_behavior)": "OPTIONAL" + } + } + } + }, + "ExplainMetrics": { + "fields": { + "planSummary": { + "type": "PlanSummary", + "id": 1 + }, + "executionStats": { + "type": "ExecutionStats", + "id": 2 + } + } + }, + "PlanSummary": { + "fields": { + "indexesUsed": { + "rule": "repeated", + "type": "google.protobuf.Struct", + "id": 1 + } + } + }, + "ExecutionStats": { + "fields": { + "resultsReturned": { + "type": "int64", + "id": 1 + }, + "executionDuration": { + "type": "google.protobuf.Duration", + "id": 3 + }, + "readOperations": { + "type": "int64", + "id": 4 + }, + "debugStats": { + "type": "google.protobuf.Struct", + "id": 5 + } + } } } } diff --git a/samples/generated/v1/datastore.run_aggregation_query.js b/samples/generated/v1/datastore.run_aggregation_query.js index c7e9e9e4..fcc244c1 100644 --- a/samples/generated/v1/datastore.run_aggregation_query.js +++ b/samples/generated/v1/datastore.run_aggregation_query.js @@ -57,6 +57,11 @@ function main(projectId) { * The GQL query to run. This query must be an aggregation query. */ // const gqlQuery = {} + /** + * Optional. Explain options for the query. If set, additional query + * statistics will be returned. If not, only query results will be returned. + */ + // const explainOptions = {} // Imports the Datastore library const {DatastoreClient} = require('@google-cloud/datastore').v1; diff --git a/samples/generated/v1/datastore.run_query.js b/samples/generated/v1/datastore.run_query.js index cb9cffee..2a5f9c18 100644 --- a/samples/generated/v1/datastore.run_query.js +++ b/samples/generated/v1/datastore.run_query.js @@ -57,6 +57,11 @@ function main(projectId) { * The GQL query to run. This query must be a non-aggregation query. */ // const gqlQuery = {} + /** + * Optional. Explain options for the query. If set, additional query + * statistics will be returned. If not, only query results will be returned. + */ + // const explainOptions = {} // Imports the Datastore library const {DatastoreClient} = require('@google-cloud/datastore').v1; diff --git a/samples/generated/v1/snippet_metadata_google.datastore.v1.json b/samples/generated/v1/snippet_metadata_google.datastore.v1.json index 27ec516f..36dda688 100644 --- a/samples/generated/v1/snippet_metadata_google.datastore.v1.json +++ b/samples/generated/v1/snippet_metadata_google.datastore.v1.json @@ -74,7 +74,7 @@ "segments": [ { "start": 25, - "end": 78, + "end": 83, "type": "FULL" } ], @@ -106,6 +106,10 @@ { "name": "gql_query", "type": ".google.datastore.v1.GqlQuery" + }, + { + "name": "explain_options", + "type": ".google.datastore.v1.ExplainOptions" } ], "resultType": ".google.datastore.v1.RunQueryResponse", @@ -134,7 +138,7 @@ "segments": [ { "start": 25, - "end": 78, + "end": 83, "type": "FULL" } ], @@ -166,6 +170,10 @@ { "name": "gql_query", "type": ".google.datastore.v1.GqlQuery" + }, + { + "name": "explain_options", + "type": ".google.datastore.v1.ExplainOptions" } ], "resultType": ".google.datastore.v1.RunAggregationQueryResponse", diff --git a/src/v1/datastore_client.ts b/src/v1/datastore_client.ts index d482632e..84e1f092 100644 --- a/src/v1/datastore_client.ts +++ b/src/v1/datastore_client.ts @@ -536,6 +536,9 @@ export class DatastoreClient { * The query to run. * @param {google.datastore.v1.GqlQuery} request.gqlQuery * The GQL query to run. This query must be a non-aggregation query. + * @param {google.datastore.v1.ExplainOptions} [request.explainOptions] + * Optional. Explain options for the query. If set, additional query + * statistics will be returned. If not, only query results will be returned. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. @@ -653,6 +656,9 @@ export class DatastoreClient { * The query to run. * @param {google.datastore.v1.GqlQuery} request.gqlQuery * The GQL query to run. This query must be an aggregation query. + * @param {google.datastore.v1.ExplainOptions} [request.explainOptions] + * Optional. Explain options for the query. If set, additional query + * statistics will be returned. If not, only query results will be returned. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. diff --git a/src/v1/datastore_proto_list.json b/src/v1/datastore_proto_list.json index 87f7c47c..9bbbda2f 100644 --- a/src/v1/datastore_proto_list.json +++ b/src/v1/datastore_proto_list.json @@ -2,5 +2,6 @@ "../../protos/google/datastore/v1/aggregation_result.proto", "../../protos/google/datastore/v1/datastore.proto", "../../protos/google/datastore/v1/entity.proto", - "../../protos/google/datastore/v1/query.proto" + "../../protos/google/datastore/v1/query.proto", + "../../protos/google/datastore/v1/query_profile.proto" ] From 1aa0fbf5ae5a525d08bfb9bb8128665110013cca Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:37:44 -0400 Subject: [PATCH 6/6] chore(main): release 8.6.0 (#1237) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(main): release 8.6.0 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Owl Bot --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- samples/package.json | 2 +- src/entity.ts | 9 ++++----- src/transaction.ts | 4 ++-- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 561e183a..b3d50ac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://www.npmjs.com/package/@google-cloud/datastore?activeTab=versions +## [8.6.0](https://github.com/googleapis/nodejs-datastore/compare/v8.5.0...v8.6.0) (2024-03-25) + + +### Features + +* Add new types ExplainOptions, ExplainMetrics, PlanSummary, ExecutionStats ([#1241](https://github.com/googleapis/nodejs-datastore/issues/1241)) ([6c409d5](https://github.com/googleapis/nodejs-datastore/commit/6c409d5c922288bd8286917b266cdb553cfd43cf)) +* Nodejs transaction redesign feature branch ([#1235](https://github.com/googleapis/nodejs-datastore/issues/1235)) ([1585d4a](https://github.com/googleapis/nodejs-datastore/commit/1585d4a4e1b4b16d198307a3e97ffcf156d000b1)) + + +### Bug Fixes + +* **deps:** Update dependency async-mutex to ^0.5.0 ([#1240](https://github.com/googleapis/nodejs-datastore/issues/1240)) ([0ba1281](https://github.com/googleapis/nodejs-datastore/commit/0ba1281efe16ef0b725937627445c32c36b9f705)) + ## [8.5.0](https://github.com/googleapis/nodejs-datastore/compare/v8.4.0...v8.5.0) (2024-02-06) diff --git a/package.json b/package.json index 3318a43e..a5805e41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@google-cloud/datastore", - "version": "8.5.0", + "version": "8.6.0", "description": "Cloud Datastore Client Library for Node.js", "keywords": [ "google apis client", diff --git a/samples/package.json b/samples/package.json index ca6ecd7a..c6f5d7e5 100644 --- a/samples/package.json +++ b/samples/package.json @@ -14,7 +14,7 @@ "test": "mocha --timeout=600000" }, "dependencies": { - "@google-cloud/datastore": "^8.5.0", + "@google-cloud/datastore": "^8.6.0", "sinon": "^17.0.0" }, "devDependencies": { diff --git a/src/entity.ts b/src/entity.ts index 2c939656..c88c005d 100644 --- a/src/entity.ts +++ b/src/entity.ts @@ -181,11 +181,10 @@ export namespace entity { try { return this.typeCastFunction!(this.value); } catch (error) { - ( - error as Error - ).message = `integerTypeCastFunction threw an error:\n\n - ${ - (error as Error).message - }`; + (error as Error).message = + `integerTypeCastFunction threw an error:\n\n - ${ + (error as Error).message + }`; throw error; } } else { diff --git a/src/transaction.ts b/src/transaction.ts index 0b7883b6..e0378c73 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -173,8 +173,8 @@ class Transaction extends DatastoreRequest { typeof gaxOptionsOrCallback === 'function' ? gaxOptionsOrCallback : typeof cb === 'function' - ? cb - : () => {}; + ? cb + : () => {}; const gaxOptions = typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {}; // This ensures that the transaction is started before calling runCommit