Skip to content

Commit

Permalink
feat(http-client): createQueryIndex and updateQueryIndex
Browse files Browse the repository at this point in the history
  • Loading branch information
JesusTheHun committed Sep 3, 2024
1 parent 7a64a85 commit 0c0fed9
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 6 deletions.
40 changes: 40 additions & 0 deletions docs/src/guide/http-client/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,46 @@ declare function getQuerySearchIndexes(
): Promise<HttpClientSearchIndex[]>;
```

### createQueryIndex

Create a query index.

```ts twoslash
import { CouchbaseHttpApiConfig, HttpClientSearchIndex, Keyspace, CreateQueryIndexOptions, CreateQueryIndexResponse } from '@cbjsdev/http-client';
// ---cut-before---
declare function createQueryIndex(
params: CouchbaseHttpApiConfig,
indexName: string,
keyspace: Keyspace,
config: {
keys: string[],
where?: string,
numReplicas?: number
},
options?: CreateQueryIndexOptions
): Promise<CreateQueryIndexResponse>;
```

### updateQueryIndex

Update a query index.

```ts twoslash
import { CouchbaseHttpApiConfig, HttpClientSearchIndex, Keyspace, CreateQueryIndexOptions, CreateQueryIndexResponse } from '@cbjsdev/http-client';
// ---cut-before---
declare function updateQueryIndex(
params: CouchbaseHttpApiConfig,
indexName: string,
keyspace: Keyspace,
config: {
action: 'move' | 'replica_count' | 'drop_replica';
num_replica?: number;
nodes?: string[];
replicaId?: string;
}
): Promise<[]>;
```

## RBAC

### getScope
Expand Down
92 changes: 92 additions & 0 deletions packages/http-client/src/services/query/createQueryIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2023-Present Jonathan MASSUCHETTI <jonathan.massuchetti@dappit.fr>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Keyspace, keyspacePath, quoteIdentifier } from '@cbjsdev/shared';

import { CouchbaseHttpApiConfig } from '../../types.js';
import { ApiQueryResponseBody } from '../../types/Api/index.js';
import { createHttpError } from '../../utils/createHttpError.js';
import { requestExecuteStatement } from './requests/requestExecuteStatement.js';

/**
* Create a query index.
*/
export async function createQueryIndex(
params: CouchbaseHttpApiConfig,
indexName: string,
keyspace: Keyspace,
config: {
/**
* The document keys to index.
*/
keys: string[];
/**
* Only index documents that match this where clause.
*/
where?: string;
/**
* The number of replicas of this index that should be created.
*/
numReplicas?: number;
},
options?: CreateQueryIndexOptions
): Promise<CreateQueryIndexResponse['results']> {
const { keys, where, numReplicas } = config;
const { deferred = false } = options ?? {};

let query = `CREATE INDEX ${quoteIdentifier(indexName)}
ON ${keyspacePath(keyspace)}
(${keys.join(',')})`;

if (where) {
query += ` WHERE ${where} `;
}

const withConfig: { num_replica?: number; defer_build?: boolean } = {};

if (numReplicas) {
withConfig.num_replica = numReplicas;
}

if (deferred) {
withConfig.defer_build = deferred;
}

if (Object.keys(withConfig).length > 0) {
query += ` WITH ${JSON.stringify(withConfig)} `;
}

const response = await requestExecuteStatement(params, query);

if (response.status !== 200) {
throw await createHttpError('GET', response);
}

const body = (await response.json()) as CreateQueryIndexResponse;
return body.results;
}

export type CreateQueryIndexOptions = {
/**
* Specifies whether this index creation should be deferred until a later
* point in time when they can be explicitly built together.
*/
deferred?: boolean;
};

export type CreateQueryIndexResponse = ApiQueryResponseBody<
Array<{ id: string; name: string; state: string }>,
null
>;
2 changes: 2 additions & 0 deletions packages/http-client/src/services/query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ export * from './getQueryBuckets.js';
export * from './getQueryIndexes.js';
export * from './getQueryIndexStats.js';
export * from './getQueryIndexRemainingMutations.js';
export * from './createQueryIndex.js';
export * from './updateQueryIndex.js';
50 changes: 50 additions & 0 deletions packages/http-client/src/services/query/updateQueryIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2023-Present Jonathan MASSUCHETTI <jonathan.massuchetti@dappit.fr>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Keyspace, keyspacePath, quoteIdentifier } from '@cbjsdev/shared';

import { CouchbaseHttpApiConfig } from '../../types.js';
import { ApiQueryResponseBody } from '../../types/Api/index.js';
import { createHttpError } from '../../utils/createHttpError.js';
import { requestExecuteStatement } from './requests/requestExecuteStatement.js';

/**
* Create a query index.
*/
export async function updateQueryIndex(
params: CouchbaseHttpApiConfig,
indexName: string,
keyspace: Keyspace,
config: {
action: 'move' | 'replica_count' | 'drop_replica';
num_replica?: number;
nodes?: string[];
replicaId?: string;
}
): Promise<[]> {
const query = `ALTER INDEX ${quoteIdentifier(indexName)}
ON ${keyspacePath(keyspace)}
WITH ${JSON.stringify(config)}
`;

const response = await requestExecuteStatement(params, query);

if (response.status !== 200) {
throw await createHttpError('GET', response);
}

const body = (await response.json()) as ApiQueryResponseBody<[], null>;
return body.results;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
* limitations under the License.
*/

export type ApiQueryResponseBody<T> = {
export type ApiQueryResponseBody<Result, Signature = { name: 'json' }> = {
requestID: string;
signature: {
name: 'json';
};
results: T;
signature: Signature;
results: Result;
status: 'success' | 'failure';
metrics: {
elapsedTime: `${number}ms`;
Expand Down
8 changes: 7 additions & 1 deletion packages/http-client/src/waitFor/waitForQueryIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ import { waitOptionsModerate } from './options.js';
import { WaitForOptions } from './types.js';

export type WaitForQueryIndexOptions = Pretty<
WaitForOptions & { awaitMutations?: boolean }
WaitForOptions & {
/**
* Wait for the index to have processed all mutations.
* @default true
*/
awaitMutations?: boolean;
}
>;

/**
Expand Down
13 changes: 13 additions & 0 deletions packages/shared/src/couchbase/utils/identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,23 @@ import {
ScopeName,
} from '../clusterTypes/index.js';

/**
* Surround the given string with backquotes.
* @param name
*/
export function quoteIdentifier(name: string) {
return '`' + name + '`';
}

/**
* Return the given path with its identifier quoted.
* @example
* ```ts
* quotePath('a.b.c') // `a`.`b`.`c`
* quotePath('a.`b`.c') // `a`.`b`.`c`
* ```
* @param path
*/
export function quotePath(path: string) {
if (path.length === 0) return '';

Expand Down
44 changes: 44 additions & 0 deletions tests/http-client/tests/query/createQueryIndex.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2023-Present Jonathan MASSUCHETTI.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe } from 'vitest';

import { createQueryIndex } from '@cbjsdev/http-client';
import { createCouchbaseTest, getRandomId } from '@cbjsdev/vitest';

describe('createQueryIndex', { timeout: 40_000 }, async () => {
const test = await createCouchbaseTest();

test('create a collection index', async ({ expect, serverTestContext, apiConfig }) => {
const indexName = `cbjs_${getRandomId()}`;
const ks = {
bucket: serverTestContext.bucket.name,
scope: serverTestContext.scope.name,
collection: serverTestContext.defaultCollection.name,
};

await expect(
createQueryIndex(apiConfig, indexName, ks, { keys: ['name'] })
).resolves.toEqual(
expect.arrayContaining([
{
id: expect.any(String),
name: indexName,
state: 'online',
},
])
);
});
});
42 changes: 42 additions & 0 deletions tests/http-client/tests/query/updateQueryIndex.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2023-Present Jonathan MASSUCHETTI.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe } from 'vitest';

import { createQueryIndex, updateQueryIndex } from '@cbjsdev/http-client';
import { createCouchbaseTest, getRandomId } from '@cbjsdev/vitest';

// requires a multi-node cluster
describe.skip('updateQueryIndex', { timeout: 80_000 }, async () => {
const test = await createCouchbaseTest();

test('update a collection index', async ({ expect, serverTestContext, apiConfig }) => {
const indexName = `cbjs_${getRandomId()}`;
const ks = {
bucket: serverTestContext.bucket.name,
scope: serverTestContext.scope.name,
collection: serverTestContext.defaultCollection.name,
};

await createQueryIndex(apiConfig, indexName, ks, { keys: ['name'] });

await expect(
updateQueryIndex(apiConfig, indexName, ks, {
action: 'replica_count',
num_replica: 1,
})
).resolves.toEqual([]);
});
});

0 comments on commit 0c0fed9

Please sign in to comment.