Skip to content

Commit 29bf14b

Browse files
committed
feat: add overwriteAlias parameter to backup.restore
1 parent bff4dd3 commit 29bf14b

File tree

5 files changed

+115
-4
lines changed

5 files changed

+115
-4
lines changed

src/backup/backupRestorer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default class BackupRestorer extends CommandBase {
2626
private statusGetter: BackupRestoreStatusGetter;
2727
private waitForCompletion?: boolean;
2828
private config?: RestoreConfig;
29+
private overwriteAlias?: boolean;
2930

3031
constructor(client: Connection, statusGetter: BackupRestoreStatusGetter) {
3132
super(client);
@@ -65,6 +66,11 @@ export default class BackupRestorer extends CommandBase {
6566
return this;
6667
}
6768

69+
withOverwriteAlias(overwriteAlias: boolean) {
70+
this.overwriteAlias = overwriteAlias;
71+
return this;
72+
}
73+
6874
withConfig(cfg: RestoreConfig) {
6975
this.config = cfg;
7076
return this;
@@ -89,6 +95,7 @@ export default class BackupRestorer extends CommandBase {
8995
config: this.config,
9096
include: this.includeClassNames,
9197
exclude: this.excludeClassNames,
98+
overwriteAlias: this.overwriteAlias,
9299
} as BackupRestoreRequest;
93100

94101
if (this.waitForCompletion) {

src/collections/backup/client.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ export const backup = (connection: Connection) => {
163163
if (args.excludeCollections) {
164164
builder = builder.withExcludeClassNames(...args.excludeCollections);
165165
}
166+
if (args.config?.overwriteAlias) {
167+
builder = builder.withOverwriteAlias(args.config?.overwriteAlias);
168+
}
166169
if (args.config) {
167170
builder = builder.withConfig({
168171
CPUPercentage: args.config.cpuPercentage,
@@ -197,9 +200,9 @@ export const backup = (connection: Connection) => {
197200
}
198201
return status
199202
? {
200-
...parseResponse(res),
201-
...status,
202-
}
203+
...parseResponse(res),
204+
...status,
205+
}
203206
: parseResponse(res);
204207
},
205208
};

src/collections/backup/collection.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Backend } from '../../backup/index.js';
22
import Connection from '../../connection/index.js';
3-
import { WeaviateInvalidInputError } from '../../errors.js';
43
import { backup } from './client.js';
54
import { BackupReturn, BackupStatusArgs, BackupStatusReturn } from './types.js';
65

src/collections/backup/integration.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
/* eslint-disable no-await-in-loop */
44
import { Backend } from '../../backup/index.js';
55
import weaviate, { Collection, WeaviateClient } from '../../index.js';
6+
import { requireAtLeast } from '../../../test/version';
7+
import { WeaviateBackupFailed } from '../../errors.js';
68

79
// These must run sequentially because Weaviate is not capable of running multiple backups at the same time
810
describe('Integration testing of backups', () => {
@@ -88,6 +90,23 @@ describe('Integration testing of backups', () => {
8890
backend: res.backend as 'filesystem',
8991
});
9092
expect(status).not.toBe('SUCCESS'); // can be 'STARTED' or 'TRANSFERRING' depending on the speed of the test machine
93+
94+
// wait to complete so that other tests can run without colliding with Weaviate's lack of simultaneous backups
95+
let wait = true;
96+
while (wait) {
97+
const { status, error } = await collection.backup.getCreateStatus({
98+
backupId: res.id as string,
99+
backend: res.backend as Backend,
100+
});
101+
if (status === 'SUCCESS') {
102+
wait = false;
103+
}
104+
if (status === 'FAILED') {
105+
throw new Error(`Backup creation failed: ${error}`);
106+
}
107+
await new Promise((resolve) => setTimeout(resolve, 1000));
108+
}
109+
91110
return collection;
92111
};
93112

@@ -98,6 +117,87 @@ describe('Integration testing of backups', () => {
98117
.then(getCollection)
99118
.then(testCollectionWaitForCompletion)
100119
.then(testCollectionNoWaitForCompletion));
120+
121+
requireAtLeast(1, 32, 3).describe('overwrite alias', () => {
122+
123+
test('overwriteAlias=true', async () => {
124+
const client = await clientPromise;
125+
126+
const things = await client.collections.create({ name: "ThingsTrue" });
127+
await client.alias.create({ collection: things.name, alias: `${things.name}Alias` });
128+
129+
const backup = await client.backup.create({
130+
backend: 'filesystem',
131+
backupId: randomBackupId(),
132+
includeCollections: [things.name],
133+
waitForCompletion: true,
134+
});
135+
136+
await client.collections.delete(things.name);
137+
await client.alias.delete(`${things.name}Alias`);
138+
139+
// Change alias to point to a different collection
140+
const inventory = await client.collections.create({ name: "InventoryTrue" });
141+
await client.alias.create({ collection: inventory.name, alias: `${things.name}Alias` });
142+
143+
144+
// Restore backup with overwriteAlias=true
145+
await client.backup.restore({
146+
backend: 'filesystem',
147+
backupId: backup.id,
148+
includeCollections: [things.name],
149+
waitForCompletion: true,
150+
config: { overwriteAlias: true },
151+
});
152+
153+
// Assert: alias points to the original collection
154+
const alias = await client.alias.get(`${things.name}Alias`);
155+
expect(alias.collection).toEqual(things.name);
156+
});
157+
158+
test('overwriteAlias=false', async () => {
159+
const client = await clientPromise;
160+
161+
const things = await client.collections.create({ name: "ThingsFalse" });
162+
await client.alias.create({ collection: things.name, alias: `${things.name}Alias` });
163+
164+
const backup = await client.backup.create({
165+
backend: 'filesystem',
166+
backupId: randomBackupId(),
167+
includeCollections: [things.name],
168+
waitForCompletion: true,
169+
});
170+
171+
await client.collections.delete(things.name);
172+
await client.alias.delete(`${things.name}Alias`);
173+
174+
// Change alias to point to a different collection
175+
const inventory = await client.collections.create({ name: "InventoryFalse" });
176+
await client.alias.create({ collection: inventory.name, alias: `${things.name}Alias` });
177+
178+
179+
// Restore backup with overwriteAlias=true
180+
const restored = client.backup.restore({
181+
backend: 'filesystem',
182+
backupId: backup.id,
183+
includeCollections: [things.name],
184+
waitForCompletion: true,
185+
config: { overwriteAlias: false },
186+
});
187+
188+
// Assert: fails with "alias already exists" error
189+
await expect(restored).rejects.toThrowError(WeaviateBackupFailed);
190+
});
191+
192+
it('cleanup', async () => {
193+
await clientPromise.then(async c => {
194+
await Promise.all(["ThingsTrue", "ThingsFalse", "InventoryTrue", "InventoryFalse"]
195+
.map(name => c.collections.delete(name).catch(e => { })));
196+
await c.alias.delete("ThingsFalseAlias").catch(e => { });
197+
await c.alias.delete("ThingsTrueAlias").catch(e => { });
198+
});
199+
})
200+
});
101201
});
102202

103203
function randomBackupId() {

src/collections/backup/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export type BackupConfigCreate = {
3737
export type BackupConfigRestore = {
3838
/** The percentage of CPU to use for the backuop restoration job. */
3939
cpuPercentage?: number;
40+
/**Allows ovewriting the collection alias if there is a conflict. */
41+
overwriteAlias?: boolean;
4042
};
4143

4244
/** The arguments required to create and restore backups. */

0 commit comments

Comments
 (0)