Skip to content

Commit

Permalink
feat: add compression support for setIfAbsent (#1224)
Browse files Browse the repository at this point in the history
Adds compression support and integration tests for `setIfAbsent`.
  • Loading branch information
malandis authored Apr 10, 2024
1 parent 799a868 commit 04f4981
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CacheClient,
CacheGet,
CacheSet,
CacheSetIfAbsent,
CacheDictionarySetField,
CacheDictionarySetFields,
CacheDictionaryGetField,
Expand Down Expand Up @@ -112,6 +113,67 @@ describe('CompressorFactory', () => {
expect((getResponse as CacheGet.Hit).valueString()).toEqual(testValue);
});
});
describe('Cache.setIfAbsent', () => {
it('should return an error if compress is true but compression is not enabled', async () => {
const setResponse = await cacheClientWithoutCompressorFactory.setIfAbsent(
cacheName,
randomString(),
testValue,
{
compress: true,
}
);
expectWithMessage(() => {
expect(setResponse).toBeInstanceOf(CacheSetIfAbsent.Error);
expect((setResponse as CacheSetIfAbsent.Error).toString()).toEqual(
'Invalid argument passed to Momento client: Compressor is not set, but `CacheClient.setIfAbsent` was called with the `compress` option; please install @gomomento/sdk-nodejs-compression and call `Configuration.withCompressionStrategy` to enable compression.'
);
}, `Expected CacheClient.setIfAbsent to return an error if compression is specified without compressor set, but got: ${setResponse.toString()}`);
});
it('should compress the value if compress is true', async () => {
const cacheClient = cacheClientWithDefaultCompressorFactory;
const key = randomString();
const setResponse = await cacheClient.setIfAbsent(
cacheName,
key,
testValue,
{
compress: true,
}
);
expectWithMessage(() => {
expect(setResponse).toBeInstanceOf(CacheSetIfAbsent.Stored);
}, `Expected CacheClient.setIfAbsent to be a success with compression specified, got: '${setResponse.toString()}'`);

const getResponse = await cacheClient.get(cacheName, key);
expectWithMessage(() => {
expect(getResponse).toBeInstanceOf(CacheGet.Hit);
}, `Expected CacheClient.get to be a hit after CacheClient.setIfAbsent with compression specified, got: '${getResponse.toString()}'`);

expect((getResponse as CacheGet.Hit).valueUint8Array()).toEqual(
testValueCompressed
);
});
it('should not compress the value if compress is not specified', async () => {
const cacheClient = cacheClientWithDefaultCompressorFactory;
const key = randomString();
const setResponse = await cacheClient.setIfAbsent(
cacheName,
key,
testValue
);
expectWithMessage(() => {
expect(setResponse).toBeInstanceOf(CacheSetIfAbsent.Stored);
}, `Expected CacheClient.setIfAbsent to be a success with no compression specified, got: '${setResponse.toString()}'`);

const getResponse = await cacheClient.get(cacheName, key);
expectWithMessage(() => {
expect(getResponse).toBeInstanceOf(CacheGet.Hit);
}, `Expected CacheClient.get to be a hit after CacheClient.setIfAbsent with no compression specified, got: '${getResponse.toString()}'`);

expect((getResponse as CacheGet.Hit).valueString()).toEqual(testValue);
});
});
describe('CacheClient.get', () => {
it('should not return an error if decompress is true and compression is not enabled and it is a miss', async () => {
const getResponse = await cacheClientWithoutCompressorFactory.get(
Expand Down
27 changes: 25 additions & 2 deletions packages/client-sdk-nodejs/src/internal/cache-data-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ import {
DictionarySetFieldsCallOptions,
GetCallOptions,
SetCallOptions,
SetIfAbsentCallOptions,
ttlOrFromCacheTtl,
} from '@gomomento/sdk-core/dist/src/utils';
import grpcCache = cache.cache_client;
Expand Down Expand Up @@ -785,8 +786,9 @@ export class CacheDataClient implements IDataClient {
cacheName: string,
key: string | Uint8Array,
value: string | Uint8Array,
ttl?: number
options?: SetIfAbsentCallOptions
): Promise<CacheSetIfAbsent.Response> {
const ttl = options?.ttl;
try {
validateCacheName(cacheName);
if (ttl !== undefined) {
Expand All @@ -806,10 +808,31 @@ export class CacheDataClient implements IDataClient {
ttl?.toString() ?? 'null'
}`
);

let encodedValue = this.convert(value);
if (options?.compress) {
this.logger.trace(
'CacheClient.setIfAbsent; compression enabled, calling value compressor'
);
if (this.valueCompressor === undefined) {
return this.cacheServiceErrorMapper.returnOrThrowError(
new InvalidArgumentError(
'Compressor is not set, but `CacheClient.setIfAbsent` was called with the `compress` option; please install @gomomento/sdk-nodejs-compression and call `Configuration.withCompressionStrategy` to enable compression.'
),
err => new CacheSetIfAbsent.Error(err)
);
}
encodedValue = await this.valueCompressor.compress(
this.configuration.getCompressionStrategy()?.compressionLevel ??
CompressionLevel.Balanced,
encodedValue
);
}

const result = await this.sendSetIfAbsent(
cacheName,
this.convert(key),
this.convert(value),
encodedValue,
ttl ? ttl * 1000 : this.defaultTtlSeconds * 1000
);
this.logger.trace(`'setIfAbsent' request result: ${result.toString()}`);
Expand Down
4 changes: 3 additions & 1 deletion packages/client-sdk-web/src/internal/cache-data-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ import {
DictionarySetFieldCallOptions,
DictionarySetFieldsCallOptions,
ttlOrFromCacheTtl,
SetIfAbsentCallOptions,
} from '@gomomento/sdk-core/dist/src/utils';

export interface DataClientProps {
Expand Down Expand Up @@ -456,8 +457,9 @@ export class CacheDataClient<
cacheName: string,
key: string | Uint8Array,
field: string | Uint8Array,
ttl?: number
options?: SetIfAbsentCallOptions
): Promise<CacheSetIfAbsent.Response> {
const ttl = options?.ttl;
try {
validateCacheName(cacheName);
if (ttl !== undefined) {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/clients/ICacheClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import {
DictionaryFetchCallOptions,
DictionarySetFieldCallOptions,
DictionarySetFieldsCallOptions,
SetIfAbsentCallOptions,
} from '../utils';
import {IControlClient, IPingClient} from '../internal/clients';
import {IMomentoCache} from './IMomentoCache';
Expand All @@ -80,7 +81,7 @@ import {IMomentoCache} from './IMomentoCache';
export type SetOptions = SetCallOptions;
export type GetOptions = GetCallOptions;
export type SetIfNotExistsOptions = ScalarCallOptions;
export type SetIfAbsentOptions = ScalarCallOptions;
export type SetIfAbsentOptions = SetIfAbsentCallOptions;
export type SetIfPresentOptions = ScalarCallOptions;
export type SetIfEqualOptions = ScalarCallOptions;
export type SetIfNotEqualOptions = ScalarCallOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ export abstract class AbstractCacheClient implements ICacheClient {
* @param {SetIfAbsentOptions} [options]
* @param {number} [options.ttl] - The time to live for the item in the cache.
* Uses the client's default TTL if this is not supplied.
* @param {boolean} [options.compress=false] - Whether to compress the value. Defaults to false.
* @returns {Promise<CacheSetIfAbsent.Response>} -
* {@link CacheSetIfAbsent.Stored} on storing the new value.
* {@link CacheSetIfAbsent.NotStored} on not storing the new value.
Expand All @@ -748,7 +749,7 @@ export abstract class AbstractCacheClient implements ICacheClient {
options?: SetIfAbsentOptions
): Promise<CacheSetIfAbsent.Response> {
const client = this.getNextDataClient();
return await client.setIfAbsent(cacheName, key, field, options?.ttl);
return await client.setIfAbsent(cacheName, key, field, options);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/internal/clients/cache/IDataClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
DictionaryGetFieldsCallOptions,
DictionarySetFieldCallOptions,
DictionarySetFieldsCallOptions,
SetIfAbsentCallOptions,
} from '../../../utils';

export interface IDataClient {
Expand Down Expand Up @@ -97,7 +98,7 @@ export interface IDataClient {
cacheName: string,
key: string | Uint8Array,
field: string | Uint8Array,
ttl?: number
options?: SetIfAbsentCallOptions
): Promise<CacheSetIfAbsent.Response>;
setIfPresent(
cacheName: string,
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/utils/cache-call-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export interface SetCallOptions
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface GetCallOptions extends DecompressableCallOptions {}

export interface SetIfAbsentCallOptions
extends ScalarCallOptions,
CompressableCallOptions {}

export interface CollectionCallOptions {
/**
* The length of the TTL and whether it should be refreshed when the collection is modified.
Expand Down

0 comments on commit 04f4981

Please sign in to comment.