IGNITE-10022: JS, PHP thin clients: a more meaningful exception when ENUM type is not registered
This closes #5187 Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/2ad04930 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/2ad04930 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/2ad04930 Branch: refs/heads/ignite-627 Commit: 2ad04930dd5854c4272fb347858292e464daa83d Parents: 862c926 Author: ekaterina-nbl <ekaterina.vergiz...@nobitlost.com> Authored: Mon Oct 29 15:49:33 2018 +0300 Committer: Igor Sapego <isap...@apache.org> Committed: Mon Oct 29 15:49:33 2018 +0300 ---------------------------------------------------------------------- modules/platforms/nodejs/lib/EnumItem.js | 17 ++- modules/platforms/nodejs/lib/Errors.js | 12 ++ .../nodejs/lib/internal/BinaryUtils.js | 4 + modules/platforms/nodejs/spec/TestingHelper.js | 7 ++ .../spec/cache/CachePutGetDiffTypes.spec.js | 39 ++++++ .../Internal/Binary/BinaryCommunicator.php | 27 +++-- .../Ignite/Internal/Binary/BinaryUtils.php | 12 ++ modules/platforms/php/tests/CachePutGetTest.php | 119 +++++++++++++++++++ 8 files changed, 221 insertions(+), 16 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/2ad04930/modules/platforms/nodejs/lib/EnumItem.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/lib/EnumItem.js b/modules/platforms/nodejs/lib/EnumItem.js index 1d1725e..5e80da9 100644 --- a/modules/platforms/nodejs/lib/EnumItem.js +++ b/modules/platforms/nodejs/lib/EnumItem.js @@ -17,6 +17,7 @@ 'use strict'; +const Util = require('util'); const ArgumentChecker = require('./internal/ArgumentChecker'); const Errors = require('./Errors'); @@ -157,14 +158,18 @@ class EnumItem { * @ignore */ async _write(communicator, buffer) { + const type = await this._getType(communicator, this._typeId); + if (!type || !type._isEnum) { + throw Errors.IgniteClientError.enumSerializationError( + true, Util.format('enum type id "%d" is not registered', this._typeId)); + } buffer.writeInteger(this._typeId); if (this._ordinal !== null) { buffer.writeInteger(this._ordinal); return; } else if (this._name !== null || this._value !== null) { - const type = await this._getType(communicator, this._typeId); - if (type._isEnum && type._enumValues) { + if (type._enumValues) { for (let i = 0; i < type._enumValues.length; i++) { if (this._name === type._enumValues[i][0] || this._value === type._enumValues[i][1]) { @@ -185,8 +190,12 @@ class EnumItem { this._typeId = buffer.readInteger(); this._ordinal = buffer.readInteger(); const type = await this._getType(communicator, this._typeId); - if (!type._isEnum || !type._enumValues || type._enumValues.length <= this._ordinal) { - throw new Errors.IgniteClientError('EnumItem can not be deserialized: type mismatch'); + if (!type || !type._isEnum) { + throw Errors.IgniteClientError.enumSerializationError( + false, Util.format('enum type id "%d" is not registered', this._typeId)); + } + else if (!type._enumValues || type._enumValues.length <= this._ordinal) { + throw Errors.IgniteClientError.enumSerializationError(false, 'type mismatch'); } this._name = type._enumValues[this._ordinal][0]; this._value = type._enumValues[this._ordinal][1]; http://git-wip-us.apache.org/repos/asf/ignite/blob/2ad04930/modules/platforms/nodejs/lib/Errors.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/lib/Errors.js b/modules/platforms/nodejs/lib/Errors.js index 57a7a8c..89baf38 100644 --- a/modules/platforms/nodejs/lib/Errors.js +++ b/modules/platforms/nodejs/lib/Errors.js @@ -83,6 +83,18 @@ class IgniteClientError extends Error { } return new IgniteClientError(msg); } + + /** + * EnumItem serialization/deserialization errors. + * @ignore + */ + static enumSerializationError(serialize, message = null) { + let msg = serialize ? 'Enum item can not be serialized' : 'Enum item can not be deserialized'; + if (message) { + msg = msg + ': ' + message; + } + return new IgniteClientError(msg); + } } /** http://git-wip-us.apache.org/repos/asf/ignite/blob/2ad04930/modules/platforms/nodejs/lib/internal/BinaryUtils.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/lib/internal/BinaryUtils.js b/modules/platforms/nodejs/lib/internal/BinaryUtils.js index 2619df7..fe1e403 100644 --- a/modules/platforms/nodejs/lib/internal/BinaryUtils.js +++ b/modules/platforms/nodejs/lib/internal/BinaryUtils.js @@ -497,6 +497,10 @@ class BinaryUtils { expectedTypeCode === BinaryUtils.TYPE_CODE.COMPLEX_OBJECT) { return; } + else if (expectedTypeCode === BinaryUtils.TYPE_CODE.ENUM && + actualTypeCode === BinaryUtils.TYPE_CODE.BINARY_ENUM) { + return; + } else if (actualTypeCode !== expectedTypeCode) { throw Errors.IgniteClientError.typeCastError(actualTypeCode, expectedTypeCode); } http://git-wip-us.apache.org/repos/asf/ignite/blob/2ad04930/modules/platforms/nodejs/spec/TestingHelper.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/spec/TestingHelper.js b/modules/platforms/nodejs/spec/TestingHelper.js index 79a5336..78df0cb 100644 --- a/modules/platforms/nodejs/spec/TestingHelper.js +++ b/modules/platforms/nodejs/spec/TestingHelper.js @@ -232,6 +232,13 @@ class TestingHelper { TestingHelper.checkError(error, Errors.IgniteClientError, done) } + static checkEnumItemSerializationError(error, done) { + if (!(error instanceof Errors.IgniteClientError) || + error.message.indexOf('Enum item can not be serialized') < 0) { + done.fail('unexpected error: ' + error); + } + } + static checkError(error, errorType, done) { if (!(error instanceof errorType)) { done.fail('unexpected error: ' + error); http://git-wip-us.apache.org/repos/asf/ignite/blob/2ad04930/modules/platforms/nodejs/spec/cache/CachePutGetDiffTypes.spec.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/spec/cache/CachePutGetDiffTypes.spec.js b/modules/platforms/nodejs/spec/cache/CachePutGetDiffTypes.spec.js index 28a9ae3..a6e1bba 100644 --- a/modules/platforms/nodejs/spec/cache/CachePutGetDiffTypes.spec.js +++ b/modules/platforms/nodejs/spec/cache/CachePutGetDiffTypes.spec.js @@ -543,6 +543,45 @@ describe('cache put get test suite >', () => { catch(error => done.fail(error)); }); + it('put enum items', (done) => { + Promise.resolve(). + then(async () => { + const fakeTypeId = 12345; + const enumItem1 = new EnumItem(fakeTypeId); + enumItem1.setOrdinal(1); + await putEnumItem(enumItem1, null, done); + await putEnumItem(enumItem1, ObjectType.PRIMITIVE_TYPE.ENUM, done); + const enumItem2 = new EnumItem(fakeTypeId); + enumItem2.setName('name'); + await putEnumItem(enumItem2, null, done); + await putEnumItem(enumItem2, ObjectType.PRIMITIVE_TYPE.ENUM, done); + const enumItem3 = new EnumItem(fakeTypeId); + enumItem3.setValue(2); + await putEnumItem(enumItem3, null, done); + await putEnumItem(enumItem3, ObjectType.PRIMITIVE_TYPE.ENUM, done); + }). + then(done). + catch(error => done.fail(error)); + }); + + async function putEnumItem(value, valueType, done) { + const cache = igniteClient.getCache(CACHE_NAME). + setKeyType(null). + setValueType(valueType); + const key = new Date(); + // Enums registration is not supported by the client, therefore put EnumItem must throw IgniteClientError + try { + await cache.put(key, value); + done.fail('put EnumItem must throw IgniteClientError'); + } + catch (err) { + TestingHelper.checkEnumItemSerializationError(err, done); + } + finally { + await cache.removeAll(); + } + } + async function putGetPrimitiveValues(keyType, valueType, key, value, modificator) { const cache = await igniteClient.getCache(CACHE_NAME). setKeyType(keyType). http://git-wip-us.apache.org/repos/asf/ignite/blob/2ad04930/modules/platforms/php/src/Apache/Ignite/Internal/Binary/BinaryCommunicator.php ---------------------------------------------------------------------- diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Binary/BinaryCommunicator.php b/modules/platforms/php/src/Apache/Ignite/Internal/Binary/BinaryCommunicator.php index 520063c..781a730 100644 --- a/modules/platforms/php/src/Apache/Ignite/Internal/Binary/BinaryCommunicator.php +++ b/modules/platforms/php/src/Apache/Ignite/Internal/Binary/BinaryCommunicator.php @@ -265,8 +265,10 @@ class BinaryCommunicator $ordinal = $buffer->readInteger(); $enumItem->setOrdinal($ordinal); $type = $this->typeStorage->getType($enumItem->getTypeId()); - if (!$type->isEnum() || !$type->getEnumValues() || count($type->getEnumValues()) <= $ordinal) { - BinaryUtils::serializationError(false, 'EnumItem can not be deserialized: type mismatch'); + if (!$type || !$type->isEnum()) { + BinaryUtils::enumSerializationError(false, sprintf('enum type id "%d" is not registered', $enumItem->getTypeId())); + } elseif (!$type->getEnumValues() || count($type->getEnumValues()) <= $ordinal) { + BinaryUtils::enumSerializationError(false, 'type mismatch'); } $enumValues = $type->getEnumValues(); $enumItem->setName($enumValues[$ordinal][0]); @@ -396,21 +398,22 @@ class BinaryCommunicator private function writeEnum(MessageBuffer $buffer, EnumItem $enumValue): void { + $type = $this->typeStorage->getType($enumValue->getTypeId()); + if (!$type || !$type->isEnum()) { + BinaryUtils::enumSerializationError(true, sprintf('enum type id "%d" is not registered', $enumValue->getTypeId())); + } $buffer->writeInteger($enumValue->getTypeId()); if ($enumValue->getOrdinal() !== null) { $buffer->writeInteger($enumValue->getOrdinal()); return; } elseif ($enumValue->getName() !== null || $enumValue->getValue() !== null) { - $type = $this->typeStorage->getType($enumValue->getTypeId()); - if ($type && $type->isEnum()) { - $enumValues = $type->getEnumValues(); - if ($enumValues) { - for ($i = 0; $i < count($enumValues); $i++) { - if ($enumValue->getName() === $enumValues[$i][0] || - $enumValue->getValue() === $enumValues[$i][1]) { - $buffer->writeInteger($i); - return; - } + $enumValues = $type->getEnumValues(); + if ($enumValues) { + for ($i = 0; $i < count($enumValues); $i++) { + if ($enumValue->getName() === $enumValues[$i][0] || + $enumValue->getValue() === $enumValues[$i][1]) { + $buffer->writeInteger($i); + return; } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/2ad04930/modules/platforms/php/src/Apache/Ignite/Internal/Binary/BinaryUtils.php ---------------------------------------------------------------------- diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Binary/BinaryUtils.php b/modules/platforms/php/src/Apache/Ignite/Internal/Binary/BinaryUtils.php index ad0bf56..e9ff2f1 100644 --- a/modules/platforms/php/src/Apache/Ignite/Internal/Binary/BinaryUtils.php +++ b/modules/platforms/php/src/Apache/Ignite/Internal/Binary/BinaryUtils.php @@ -203,6 +203,9 @@ class BinaryUtils $actualTypeCode === ObjectType::BINARY_OBJECT && $expectedTypeCode === ObjectType::COMPLEX_OBJECT) { return; + } elseif ($expectedTypeCode === ObjectType::ENUM && + $actualTypeCode === ObjectType::BINARY_ENUM) { + return; } elseif ($actualTypeCode !== $expectedTypeCode) { BinaryUtils::typeCastError($actualTypeCode, $expectedTypeCode); } @@ -419,6 +422,15 @@ class BinaryUtils throw new ClientException($msg); } + public static function enumSerializationError(bool $serialize, string $message = null): void + { + $msg = $serialize ? 'Enum item can not be serialized' : 'Enum item can not be deserialized'; + if ($message) { + $msg = $msg . ': ' . $message; + } + throw new ClientException($msg); + } + public static function typeCastError($fromType, $toType): void { throw new ClientException(sprintf('Type "%s" can not be cast to %s', http://git-wip-us.apache.org/repos/asf/ignite/blob/2ad04930/modules/platforms/php/tests/CachePutGetTest.php ---------------------------------------------------------------------- diff --git a/modules/platforms/php/tests/CachePutGetTest.php b/modules/platforms/php/tests/CachePutGetTest.php index 9d15ab2..c3ff1e9 100644 --- a/modules/platforms/php/tests/CachePutGetTest.php +++ b/modules/platforms/php/tests/CachePutGetTest.php @@ -18,14 +18,20 @@ namespace Apache\Ignite\Tests; +use \DateTime; use Ds\Map; use Ds\Set; use PHPUnit\Framework\TestCase; +use Apache\Ignite\Type\ObjectType; use Apache\Ignite\Type\MapObjectType; use Apache\Ignite\Type\CollectionObjectType; use Apache\Ignite\Type\ObjectArrayType; use Apache\Ignite\Type\ComplexObjectType; use Apache\Ignite\Data\BinaryObject; +use Apache\Ignite\Data\Date; +use Apache\Ignite\Data\Timestamp; +use Apache\Ignite\Data\EnumItem; +use Apache\Ignite\Exception\ClientException; class TstComplObjectWithPrimitiveFields { @@ -490,6 +496,119 @@ final class CachePutGetTestCase extends TestCase $this->putGetObjectArrays(new ObjectArrayType(new ObjectArrayType(new ComplexObjectType())), $array); } + public function testPutGetDateTime(): void + { + $this->putGetDate("Y-m-d H:i:s", "2018-10-19 18:31:13", 0); + $this->putGetDate("Y-m-d H:i:s", "2018-10-19 18:31:13", 29726); + $this->putGetDate("Y-m-d H:i:s", "2018-10-19 18:31:13", 999999); + + $this->putGetTimestamp("Y-m-d H:i:s", "2018-10-19 18:31:13", 0); + $this->putGetTimestamp("Y-m-d H:i:s", "2018-10-19 18:31:13", 29726000); + $this->putGetTimestamp("Y-m-d H:i:s", "2018-10-19 18:31:13", 999999999); + + $this->putGetTimestampFromDateTime("Y-m-d H:i:s", "2018-10-19 18:31:13", 0); + $this->putGetTimestampFromDateTime("Y-m-d H:i:s", "2018-10-19 18:31:13", 29726); + $this->putGetTimestampFromDateTime("Y-m-d H:i:s", "2018-10-19 18:31:13", 999999); + } + + public function testPutEnumItems(): void + { + $fakeTypeId = 12345; + $enumItem1 = new EnumItem($fakeTypeId); + $enumItem1->setOrdinal(1); + $this->putEnumItem($enumItem1, null); + $this->putEnumItem($enumItem1, ObjectType::ENUM); + $enumItem2 = new EnumItem($fakeTypeId); + $enumItem2->setName('name'); + $this->putEnumItem($enumItem2, null); + $this->putEnumItem($enumItem2, ObjectType::ENUM); + $enumItem3 = new EnumItem($fakeTypeId); + $enumItem3->setOrdinal(2); + $this->putEnumItem($enumItem3, null); + $this->putEnumItem($enumItem3, ObjectType::ENUM); + } + + private function putEnumItem($value, $valueType): void + { + $key = microtime(); + self::$cache-> + setKeyType(null)-> + setValueType($valueType); + // Enums registration is not supported by the client, therefore put EnumItem must throw ClientException + try { + self::$cache->put($key, $value); + $this->fail('put EnumItem must throw ClientException'); + } catch (ClientException $e) { + $this->assertContains('Enum item can not be serialized', $e->getMessage()); + } finally { + self::$cache->removeAll(); + } + } + + private function putGetDate(string $format, string $dateString, int $micros): void + { + $key = microtime(); + self::$cache-> + setKeyType(null)-> + setValueType(ObjectType::DATE); + try { + $dt = DateTime::createFromFormat("$format.u", sprintf("%s.%06d", $dateString, $micros)); + $iDate = Date::fromDateTime($dt); + self::$cache->put($key, $iDate); + $result = self::$cache->get($key); + + $this->assertEquals(sprintf("%06d", intval($micros / 1000) * 1000), $result->toDateTime()->format('u')); + $this->assertEquals($dateString, $result->toDateTime()->format($format)); + } finally { + self::$cache->removeAll(); + } + } + + private function putGetTimestamp(string $format, string $dateString, int $nanos): void + { + $key = microtime(); + self::$cache-> + setKeyType(null)-> + setValueType(ObjectType::TIMESTAMP); + + try { + $millis = intval($nanos / 1000000); + $nanosInMillis = $nanos % 1000000; + self::$cache->put($key, + new Timestamp( + DateTime::createFromFormat($format, $dateString)->getTimestamp() * 1000 + $millis, + $nanosInMillis + ) + ); + $result = self::$cache->get($key); + + $this->assertEquals($nanos % 1000000, $result->getNanos()); + $this->assertEquals($dateString, $result->toDateTime()->format($format)); + } finally { + self::$cache->removeAll(); + } + } + + private function putGetTimestampFromDateTime(string $format, string $dateString, $micros): void + { + $key = microtime(); + self::$cache-> + setKeyType(null)-> + setValueType(ObjectType::TIMESTAMP); + + try { + self::$cache->put($key, Timestamp::fromDateTime( + DateTime::createFromFormat("$format.u", sprintf("%s.%06d", $dateString, $micros)) + )); + $result = self::$cache->get($key); + + $this->assertEquals(intval($micros / 1000) * 1000, $result->toDateTime()->format('u')); + $this->assertEquals($dateString, $result->toDateTime()->format($format)); + } finally { + self::$cache->removeAll(); + } + } + private function putGetObjectArrays(?ObjectArrayType $arrayType, array $value): void { $key = microtime();