fix(spanner): do not drop Spanner and GAX options on Database and Transaction methods#9381
fix(spanner): do not drop Spanner and GAX options on Database and Transaction methods#9381cy-yun wants to merge 8 commits into
Conversation
727ef6b to
19a0ff1
Compare
bshaffer
left a comment
There was a problem hiding this comment.
Looks great! A few thoughts/suggestions
| 'parameters', | ||
| 'types', | ||
| 'queryOptions', | ||
| 'sessionOptions', |
There was a problem hiding this comment.
Where is sessionOptions coming from? I don't see it in ExecuteSqlRequest or in Operation::execute.
I did a brief search and I think it might be possible that this option is no longer supported
There was a problem hiding this comment.
I added it because it was in the docs above. But you're right that it's not in the ExecuteSqlRequest. I'll remove it from everywhere.
| 'retrySettings', | ||
| 'timeoutMillis', | ||
| 'transportOptions' | ||
| ], $options); |
There was a problem hiding this comment.
I know we said pluckArray works, but we also discussed potentially adding OptionsValidator::stripUnknownOptions so we could do something like this:
$options = $this->stripUnknownOptions(
$options,
['parameters', 'types']
CallOptions::class,
ExecuteSqlRequest::class,
);How that I'm seeing the size of these lists, and the number of their occurrences, what are your thoughts on going that direction instead?
There was a problem hiding this comment.
I like the stripUnknownOptions approach more as well. It's more elegant, extendable, and maintainable. I'll make that change.
| new ExecuteBatchDmlRequest(), | ||
| CallOptions::class | ||
| CallOptions::class, | ||
| [], |
There was a problem hiding this comment.
That empty array acts as the 4th parameter ($transactionOptions) in order to pass the ['route-to-leader'] configuration to the 5th parameter ($additionalOptions). It's solely for parameter positioning.
There was a problem hiding this comment.
I'm confused because validateOptions uses variable parameters, and $transactionOptions is not a parameter.
google-cloud-php/Core/src/ApiHelperTrait.php
Line 283 in a9ffd8f
What seems to be happening is you're defining an empty set of parameters via [], and then saving them in an empty variable $_. This should instead look like this:
[$dmlRequest, $callOptions, $rtl] = $this->validateOptions(
$options,
new ExecuteBatchDmlRequest(),
['route-to-leader']
);| } | ||
| if (isset($commitOptions['requestOptions']['requestTag'])) { | ||
| unset($commitOptions['requestOptions']['requestTag']); | ||
| if (empty($commitOptions['requestOptions'])) { |
There was a problem hiding this comment.
nit: this is a bit confusing as written, as empty also returns true for non-existant keys
| if (empty($commitOptions['requestOptions'])) { | |
| if (0 === count($commitOptions['requestOptions'])) { |
6f60e2a to
35386e0
Compare
35386e0 to
c7e2ee6
Compare
18d66ba to
3ae10c6
Compare
bshaffer
left a comment
There was a problem hiding this comment.
All small nits and suggestions! Looking really good.
| new ExecuteBatchDmlRequest(), | ||
| CallOptions::class | ||
| CallOptions::class, | ||
| [], |
There was a problem hiding this comment.
I'm confused because validateOptions uses variable parameters, and $transactionOptions is not a parameter.
google-cloud-php/Core/src/ApiHelperTrait.php
Line 283 in a9ffd8f
What seems to be happening is you're defining an empty set of parameters via [], and then saving them in an empty variable $_. This should instead look like this:
[$dmlRequest, $callOptions, $rtl] = $this->validateOptions(
$options,
new ExecuteBatchDmlRequest(),
['route-to-leader']
);| public function executeUpdateBatch(array $statements, array $options = []): BatchDmlResult | ||
| { | ||
| $options = $this->buildUpdateOptions($options); | ||
| $options = $this->buildUpdateOptions($options, \Google\Cloud\Spanner\V1\ExecuteBatchDmlRequest::class); |
| $commitOptions = (new \Google\Cloud\Core\OptionsValidator())->stripUnknownOptions( | ||
| $options, | ||
| \Google\ApiCore\Options\CallOptions::class, | ||
| \Google\Cloud\Spanner\V1\CommitRequest::class |
There was a problem hiding this comment.
nit: import these classes
| $commitOptions = $this->pluckArray( | ||
| ['returnCommitStats', 'maxCommitDelay'], | ||
| $options | ||
| $commitOptions = (new \Google\Cloud\Core\OptionsValidator())->stripUnknownOptions( |
There was a problem hiding this comment.
I think we should either...
- add
stripUnknownOptionstoApiHelperTraitand import it in this class, or - add
$this->optionsValidatorin the constructor
...rather than create this object on the fly each time.
Because we are doing this in both Transaction and TransactionalReadTrait, if we add property $optionsValidator in the trait, we will be able to reference it here in Transaction as well.
Then we need to instantiate the $optionsValidator in Transaction::__construct(), and in SnapshotTrait::initialize() and we should be good to go
| if (isset($options['requestOptions'])) { | ||
| $updateOptions['requestOptions'] = $options['requestOptions']; | ||
| } | ||
| $updateOptions = (new \Google\Cloud\Core\OptionsValidator())->stripUnknownOptions( |
There was a problem hiding this comment.
Same here: if we use ApiHelperTrait, then we can just call $this->stripUnknownOptions
| } elseif ($optionType === CallOptions::class) { | ||
| $callOptionKeys = array_keys((new CallOptions([]))->toArray()); | ||
| $allowedKeys = array_merge($allowedKeys, $callOptionKeys); | ||
| } elseif ($optionType instanceof Message |
There was a problem hiding this comment.
I think we should only support message class name in this case. There's no reason to provide a message instance (as there is for validateOptions).
| } elseif (is_string($optionType)) { | ||
| $allowedKeys[] = $optionType; |
There was a problem hiding this comment.
nit but we probably don't need this... we can just say scalars should always be string[]
| /** | ||
| * @var array | ||
| */ | ||
| private static array $allowedKeysCache = []; |
There was a problem hiding this comment.
This should be at the top of the class between use ArrayTrait; and __construct
| } | ||
|
|
||
| $options = $this->buildUpdateOptions($options); | ||
| $options = $this->buildUpdateOptions($options, \Google\Cloud\Spanner\V1\ExecuteSqlRequest::class); |
Fixes #9378, #9336
This PR addresses an issue where certain Google Cloud Spanner and underlying GAX options (such as
requestOptions,headers,retrySettings,timeoutMillis, andtransportOptions) were being inadvertently dropped by variousDatabaseandTransactionmethods.Previously, methods used
pluckArray()with a highly restrictive and hardcoded list of allowed keys, silently ignoring and discarding valid user-provided configuration. This PR replaces that scattered logic with a centralized, unifiedOptionsValidatorthat preserves all allowed Spanner and GAX options, ensuring they correctly propagate down to the underlyingOperationcalls and API requests.Changes Include:
Core\OptionsValidator: IntroducedstripUnknownOptions()as a centralized utility to automatically strip unknown array keys while safely preserving all recognized GAPIC request properties, common Google Cloud PHP options, and GAX configurations.Spanner\Database: Updated array extractions ininsert,insertBatch,update,updateBatch,insertOrUpdate,insertOrUpdateBatch,replace,replaceBatch,delete,execute, andreadto rely on the unifiedstripUnknownOptions()approach.Spanner\TransactionalReadTrait: Updatedexecute()andread()to pass user configuration through the validator, preservingrequestOptionsandtransportOptions.Spanner\Operation: UpdatedpartitionQuery()andpartitionRead()to use the new option validator.Spanner\Transaction: Updatedcommit()to propagate options via the unified validator while continuing to cleanly ignorerequestTag(which is unsupported by the Spanner backend for commits).Unrelated Unit Test Fix:
Spanner\ValueMapper: Resolved a PHP 8.1+ compatibility issue (Using null as an array offset is deprecated) by explicitly casting potentially null array keys to strings during type annotations.Verification
Core\Tests\Unit\OptionsValidatorTestto comprehensively verify that recognized Spanner properties, Google Cloud options, and GAX options are preserved, while arbitrary invalid keys are correctly stripped.Spanner\Tests\Unit\DatabaseTest,TransactionTest, andOperationTestto concisely verify option propagation across core methods. The tests now explicitly assert that a fabricatedunknownOptionis cleanly filtered out by the validator without crashing.