Skip to content

Commit 86bb4ba

Browse files
committed
added checkExists for createCollection
1 parent c922a6c commit 86bb4ba

File tree

9 files changed

+72
-27
lines changed

9 files changed

+72
-27
lines changed

src/api/devops-api-http-client.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// limitations under the License.
1414

1515
import { HttpClient } from '@/src/api/http-client';
16-
import { DEFAULT_TIMEOUT, HTTP_METHODS } from '@/src/api/constants';
16+
import { HTTP_METHODS } from '@/src/api/constants';
1717
import { AxiosError, AxiosResponse } from 'axios';
1818
import { HTTPClientOptions } from '@/src/api/types';
1919
import { HTTP1AuthHeaderFactories, HTTP1Strategy } from '@/src/api/http1';
@@ -48,10 +48,6 @@ export class DevopsApiHttpClient extends HttpClient {
4848
this.requestStrategy = new HTTP1Strategy(HTTP1AuthHeaderFactories.DevopsApi);
4949
}
5050

51-
public multiCallTimeoutManager(timeoutMs: number | undefined) {
52-
return mkTimeoutManager(MultiCallTimeoutManager, timeoutMs);
53-
}
54-
5551
public async request(info: DevopsApiRequestInfo, options: TimeoutOptions | undefined): Promise<AxiosResponse> {
5652
try {
5753
const timeoutManager = options?.timeoutManager ?? mkTimeoutManager(SingleCallTimeoutManager, options?.maxTimeMS);
@@ -73,7 +69,7 @@ export class DevopsApiHttpClient extends HttpClient {
7369
}
7470

7571
public async requestLongRunning(req: DevopsApiRequestInfo, info: LongRunningRequestInfo): Promise<AxiosResponse> {
76-
const timeoutManager = this.multiCallTimeoutManager(info.options?.maxTimeMS);
72+
const timeoutManager = mkTimeoutManager(MultiCallTimeoutManager, info.options?.maxTimeMS);
7773
const resp = await this.request(req, { timeoutManager });
7874

7975
const id = (typeof info.id === 'function')

src/client/data-api-client.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class DataApiClient {
9292
*
9393
* @returns A new {@link Db} instance.
9494
*/
95-
db(endpoint: string, options?: DbSpawnOptions): Db;
95+
public db(endpoint: string, options?: DbSpawnOptions): Db;
9696

9797
/**
9898
* Spawns a new {@link Db} instance using a direct endpoint and given options.
@@ -126,9 +126,9 @@ export class DataApiClient {
126126
*
127127
* @returns A new {@link Db} instance.
128128
*/
129-
db(id: string, region: string, options?: DbSpawnOptions): Db;
129+
public db(id: string, region: string, options?: DbSpawnOptions): Db;
130130

131-
db(endpointOrId: string, regionOrOptions?: string | DbSpawnOptions, maybeOptions?: DbSpawnOptions): Db {
131+
public db(endpointOrId: string, regionOrOptions?: string | DbSpawnOptions, maybeOptions?: DbSpawnOptions): Db {
132132
return mkDb(this.#options, endpointOrId, regionOrOptions, maybeOptions);
133133
}
134134

@@ -154,7 +154,7 @@ export class DataApiClient {
154154
*
155155
* @returns A new {@link AstraAdmin} instance.
156156
*/
157-
admin(options?: AdminSpawnOptions): AstraAdmin {
157+
public admin(options?: AdminSpawnOptions): AstraAdmin {
158158
return mkAdmin(this.#options, options);
159159
}
160160
}

src/data-api/db.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { Collection, extractDbIdFromUrl, SomeDoc } from '@/src/data-api';
15+
import { Collection, CollectionAlreadyExistsError, extractDbIdFromUrl, SomeDoc } from '@/src/data-api';
1616
import { DataApiHttpClient, DEFAULT_DATA_API_PATH, DEFAULT_NAMESPACE, RawDataApiResponse } from '@/src/api';
1717
import {
1818
CollectionName,
@@ -311,8 +311,18 @@ export class Db implements Disposable {
311311
},
312312
};
313313

314-
await this._httpClient.executeCommand(command, options);
314+
const timeoutManager = this._httpClient.multiCallTimeoutManager(options?.maxTimeMS);
315+
const namespace = options?.namespace ?? this.namespace;
315316

317+
if (options?.checkExists !== false) {
318+
const collections = await this.listCollections({ namespace, maxTimeMS: timeoutManager.msRemaining });
319+
320+
if (collections.some(c => c.name === collectionName)) {
321+
throw new CollectionAlreadyExistsError(options?.namespace ?? this.namespace, collectionName);
322+
}
323+
}
324+
325+
await this._httpClient.executeCommand(command, { namespace, timeoutManager });
316326
return this.collection(collectionName, options);
317327
}
318328

src/data-api/errors.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ export class CursorAlreadyInitializedError extends DataAPIError {
188188
}
189189
}
190190

191+
export class CollectionAlreadyExistsError extends DataAPIError {
192+
constructor(readonly namespace: string, readonly collectionName: string) {
193+
super(`Collection '${namespace}.${collectionName}' already exists`);
194+
this.name = 'CollectionAlreadyExistsError';
195+
}
196+
}
197+
191198
/**
192199
* An error representing the *complete* errors for an operation. This is a cohesive error that represents all the
193200
* errors that occurred during a single operation, and should not be thought of as *always* 1:1 with the number of

src/data-api/types/collections/create-collection.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@ export interface CreateCollectionCommand {
3232
* @field defaultId - The default ID for the collection.
3333
* @field namespace - Overrides the namespace for the collection.
3434
* @field maxTimeMS - The maximum time to allow the operation to run.
35+
* @field checkExists - Whether to check if the collection exists before creating it.
3536
*
3637
* @see Db.createCollection
3738
*/
38-
export interface CreateCollectionOptions<Schema extends SomeDoc> extends WithTimeout, CollectionOptions<Schema>, WithNamespace {}
39+
export interface CreateCollectionOptions<Schema extends SomeDoc> extends WithTimeout, CollectionOptions<Schema>, WithNamespace {
40+
/**
41+
* If `true`, runs an additional existence check before creating the collection, failing if the collection with the
42+
* same name already exists, raising an error. Otherwise, the creation is always attempted, and the command's will
43+
* succeed even if the collection with the given name already exists, as long as the options are the exact same.
44+
*/
45+
checkExists?: boolean;
46+
}

src/devops/astra-admin.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export class AstraAdmin {
9393
*
9494
* @returns A new {@link Db} instance.
9595
*/
96-
db(endpoint: string, options?: DbSpawnOptions): Db;
96+
public db(endpoint: string, options?: DbSpawnOptions): Db;
9797

9898
/**
9999
* Spawns a new {@link Db} instance using a direct endpoint and given options.
@@ -127,9 +127,9 @@ export class AstraAdmin {
127127
*
128128
* @returns A new {@link Db} instance.
129129
*/
130-
db(id: string, region: string, options?: DbSpawnOptions): Db;
130+
public db(id: string, region: string, options?: DbSpawnOptions): Db;
131131

132-
db(endpointOrId: string, regionOrOptions?: string | DbSpawnOptions, maybeOptions?: DbSpawnOptions): Db {
132+
public db(endpointOrId: string, regionOrOptions?: string | DbSpawnOptions, maybeOptions?: DbSpawnOptions): Db {
133133
return mkDb(this.#defaultOpts, endpointOrId, regionOrOptions, maybeOptions);
134134
}
135135

@@ -168,7 +168,7 @@ export class AstraAdmin {
168168
*
169169
* @returns A new {@link Db} instance.
170170
*/
171-
dbAdmin(endpoint: string, options?: DbSpawnOptions): AstraDbAdmin;
171+
public dbAdmin(endpoint: string, options?: DbSpawnOptions): AstraDbAdmin;
172172

173173
/**
174174
* Spawns a new {@link Db} instance using a direct endpoint and given options.
@@ -205,9 +205,9 @@ export class AstraAdmin {
205205
*
206206
* @returns A new {@link Db} instance.
207207
*/
208-
dbAdmin(id: string, region: string, options?: DbSpawnOptions): AstraDbAdmin;
208+
public dbAdmin(id: string, region: string, options?: DbSpawnOptions): AstraDbAdmin;
209209

210-
dbAdmin(endpointOrId: string, regionOrOptions?: string | DbSpawnOptions, maybeOptions?: DbSpawnOptions): AstraDbAdmin {
210+
public dbAdmin(endpointOrId: string, regionOrOptions?: string | DbSpawnOptions, maybeOptions?: DbSpawnOptions): AstraDbAdmin {
211211
// @ts-expect-error - calls internal representation of method
212212
return this.db(endpointOrId, regionOrOptions, maybeOptions).admin(this.#defaultOpts.adminOptions);
213213
}

tests/fixtures.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const initTestObjects = async (ctx: Context, useHttp2: boolean = true): P
3636
const db = client.db(process.env.ASTRA_URI!);
3737

3838
const coll = (!collCreated)
39-
? await db.createCollection(DEFAULT_COLLECTION_NAME, { vector: { dimension: 5, metric: 'cosine' } })
39+
? await db.createCollection(DEFAULT_COLLECTION_NAME, { vector: { dimension: 5, metric: 'cosine' }, checkExists: false })
4040
: db.collection(DEFAULT_COLLECTION_NAME);
4141

4242
collCreated = true;

tests/integration/data-api/db.test.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
initTestObjects,
2222
OTHER_NAMESPACE
2323
} from '@/tests/fixtures';
24-
import { DataAPIResponseError, Db } from '@/src/data-api';
24+
import { CollectionAlreadyExistsError, DataAPIResponseError, Db } from '@/src/data-api';
2525
import { DEFAULT_DATA_API_PATH, DEFAULT_NAMESPACE } from '@/src/api';
2626
import { DataApiClient } from '@/src/client';
2727
import process from 'process';
@@ -59,33 +59,45 @@ describe('integration.data-api.db', async () => {
5959
assert.strictEqual(res.namespace, OTHER_NAMESPACE);
6060
});
6161

62-
it('should create collection idempotently', async () => {
62+
it('should throw CollectionAlreadyExistsError if collection already exists', async () => {
63+
await db.createCollection(EPHEMERAL_COLLECTION_NAME);
64+
try {
65+
await db.createCollection(EPHEMERAL_COLLECTION_NAME);
66+
assert.fail('Expected an error');
67+
} catch (e) {
68+
assert.ok(e instanceof CollectionAlreadyExistsError);
69+
assert.strictEqual(e.collectionName, EPHEMERAL_COLLECTION_NAME);
70+
assert.strictEqual(e.namespace, DEFAULT_NAMESPACE);
71+
}
72+
});
73+
74+
it('should create collection idempotently if checkExists is false', async () => {
6375
const res = await db.createCollection(EPHEMERAL_COLLECTION_NAME);
6476
assert.ok(res);
6577
assert.strictEqual(res.collectionName, EPHEMERAL_COLLECTION_NAME);
66-
const res2 = await db.createCollection(EPHEMERAL_COLLECTION_NAME);
78+
const res2 = await db.createCollection(EPHEMERAL_COLLECTION_NAME, { checkExists: false });
6779
assert.ok(res2);
6880
assert.strictEqual(res2.collectionName, EPHEMERAL_COLLECTION_NAME);
6981
});
7082

71-
it('should create collection with same options idempotently', async () => {
83+
it('should create collection with same options idempotently if checkExists is false', async () => {
7284
const res = await db.createCollection(EPHEMERAL_COLLECTION_NAME, { indexing: { deny: ['*'] } });
7385
assert.ok(res);
7486
assert.strictEqual(res.collectionName, EPHEMERAL_COLLECTION_NAME);
7587
assert.strictEqual(res.namespace, DEFAULT_NAMESPACE);
76-
const res2 = await db.createCollection(EPHEMERAL_COLLECTION_NAME, { indexing: { deny: ['*'] } });
88+
const res2 = await db.createCollection(EPHEMERAL_COLLECTION_NAME, { indexing: { deny: ['*'] }, checkExists: false });
7789
assert.ok(res2);
7890
assert.strictEqual(res2.collectionName, EPHEMERAL_COLLECTION_NAME);
7991
assert.strictEqual(res2.namespace, DEFAULT_NAMESPACE);
8092
});
8193

82-
it('should fail creating collection with different options', async () => {
94+
it('should fail creating collection with different options even if checkExists is false', async () => {
8395
const res = await db.createCollection(EPHEMERAL_COLLECTION_NAME, { indexing: { deny: ['*'] } });
8496
assert.ok(res);
8597
assert.strictEqual(res.collectionName, EPHEMERAL_COLLECTION_NAME);
8698
assert.strictEqual(res.namespace, DEFAULT_NAMESPACE);
8799
try {
88-
await db.createCollection(EPHEMERAL_COLLECTION_NAME, { indexing: { allow: ['*'] } });
100+
await db.createCollection(EPHEMERAL_COLLECTION_NAME, { indexing: { allow: ['*'] }, checkExists: false });
89101
assert.fail('Expected an error');
90102
} catch (e) {
91103
assert.ok(e instanceof DataAPIResponseError);

tests/integration/devops/lifecycle.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ describe('integration.devops.lifecycle', async () => {
3535
// }
3636
});
3737

38+
it('sdfsadfsda', async () => {
39+
const admin = client.admin();
40+
41+
const resp = await admin.createDatabase({
42+
name: 'astra-test-db',
43+
cloudProvider: 'GCP',
44+
region: 'us-east1',
45+
});
46+
47+
console.log(resp.id);
48+
});
49+
3850
it('[admin] works', async () => {
3951
try {
4052
const admin = client.admin();

0 commit comments

Comments
 (0)