Skip to content

Commit f990b13

Browse files
authored
chore: add getFilesByTableName and dropFilesByTableName methods in indexeddb filemanager (#33)
* add: cache time logic * fix: test * update: logic * revert: tests * more changes * add: test * some cleanup * bump: version * update: name * remove: filesForTable * update: file manager type * fix: build * fix: mock manager * update: name
1 parent 5e7e509 commit f990b13

9 files changed

+169
-65
lines changed

meerkat-dbm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devrev/meerkat-dbm",
3-
"version": "0.0.12",
3+
"version": "0.0.13",
44
"dependencies": {
55
"tslib": "^2.3.0",
66
"@duckdb/duckdb-wasm": "^1.28.0",

meerkat-dbm/src/dbm/dbm.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { AsyncDuckDB } from '@duckdb/duckdb-wasm';
22
import log from 'loglevel';
33
import {
44
FileBufferStore,
5+
FileData,
56
FileManagerType,
67
} from '../file-manager/file-manager-type';
78
import { DBM, DBMConstructorOptions } from './dbm';
@@ -49,6 +50,27 @@ export class MockFileManager implements FileManagerType {
4950
}
5051
}
5152
}
53+
54+
async getFilesByTableName(tableName: string): Promise<FileData[]> {
55+
const files: FileData[] = [];
56+
57+
for (const key in this.fileBufferStore) {
58+
if (this.fileBufferStore[key].tableName === tableName) {
59+
files.push({ fileName: key });
60+
}
61+
}
62+
63+
return files;
64+
}
65+
66+
async dropFilesByTableName(
67+
tableName: string,
68+
fileNames: string[]
69+
): Promise<void> {
70+
for (const fileName of fileNames) {
71+
delete this.fileBufferStore[fileName];
72+
}
73+
}
5274
}
5375

5476
const mockDB = {

meerkat-dbm/src/file-manager/file-manager-type.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export interface FileManagerType {
1515
getFileBuffer: (name: string) => Promise<Uint8Array | undefined>;
1616
mountFileBufferByTableNames: (tableName: string[]) => Promise<void>;
1717
unmountFileBufferByTableNames: (tableName: string[]) => Promise<void>;
18+
getFilesByTableName(tableName: string): Promise<FileData[]>;
19+
dropFilesByTableName(tableName: string, fileNames: string[]): Promise<void>;
1820
}
1921

2022
export interface FileManagerConstructorOptions {

meerkat-dbm/src/file-manager/indexed-db/duckdb-database.ts renamed to meerkat-dbm/src/file-manager/indexed-db/duckdb-files-database.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import { File, Table } from '../file-manager-type';
66
* https://dexie.org/docs/Version/Version.stores()#warning
77
*/
88

9-
export class DuckDBDatabase extends Dexie {
9+
export class DuckDBFilesDatabase extends Dexie {
1010
tablesKey: Dexie.Table<Table, string>;
1111
files: Dexie.Table<File, string>;
1212

1313
constructor() {
14-
super('DuckDBDatabase');
14+
super('DuckDBFilesDatabase');
1515

1616
this.version(1).stores({
1717
tablesKey: '&tableName',

meerkat-dbm/src/file-manager/indexed-db/indexed-db-file-manager.spec.ts

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AsyncDuckDB } from '@duckdb/duckdb-wasm';
22
import 'fake-indexeddb/auto';
33
import { InstanceManagerType } from '../../dbm/instance-manager';
44
import { FILE_TYPES } from '../file-manager-type';
5-
import { DuckDBDatabase } from './duckdb-database';
5+
import { DuckDBFilesDatabase } from './duckdb-files-database';
66
import { IndexedDBFileManager } from './indexed-db-file-manager';
77

88
const mockDB = {
@@ -25,7 +25,7 @@ const mockDB = {
2525
describe('IndexedDBFileManager', () => {
2626
let fileManager: IndexedDBFileManager;
2727
let db: AsyncDuckDB;
28-
let indexedDB: DuckDBDatabase;
28+
let indexedDB: DuckDBFilesDatabase;
2929
let instanceManager: InstanceManagerType;
3030

3131
const fileBuffer = {
@@ -65,7 +65,7 @@ describe('IndexedDBFileManager', () => {
6565
});
6666

6767
beforeEach(async () => {
68-
indexedDB = new DuckDBDatabase();
68+
indexedDB = new DuckDBFilesDatabase();
6969
fileManager = new IndexedDBFileManager({
7070
fetchTableFileBuffers: async () => {
7171
return [];
@@ -124,29 +124,20 @@ describe('IndexedDBFileManager', () => {
124124
]);
125125
});
126126

127-
it('should flush the database when initialized', async () => {
128-
// Fetch the stored data in the indexedDB
127+
it('should persist the data when the IndexedDB is reinitialized', async () => {
129128
const tableData = await indexedDB.tablesKey.toArray();
130-
const fileBufferData = await indexedDB.files.toArray();
129+
const fileData = await indexedDB.files.toArray();
131130

132-
/**
133-
* There should be no tablesKey and no file buffers
134-
*/
135-
expect(tableData.length).toBe(0);
136-
expect(fileBufferData.length).toBe(0);
131+
expect(tableData.length).toBe(2);
132+
expect(fileData.length).toBe(3);
137133
});
138134

139135
it('should override the file buffer when registering the same file again', async () => {
140-
// Register single file buffer
141-
await fileManager.registerFileBuffer(fileBuffer);
142-
143-
const tableData = await indexedDB.tablesKey.toArray();
144136
const fileBufferData1 = await indexedDB.files.toArray();
145137

146138
/**
147-
* There should be one file buffer, the buffer should be initial file buffer
139+
* The buffer value should be initial file buffer
148140
*/
149-
expect(fileBufferData1.length).toBe(1);
150141
expect(fileBufferData1[0].buffer).toEqual(fileBuffer.buffer);
151142

152143
// Register the same file with a different buffer
@@ -158,15 +149,56 @@ describe('IndexedDBFileManager', () => {
158149
const fileBufferData2 = await indexedDB.files.toArray();
159150

160151
/**
161-
* There should be one table with one file buffer
152+
* The buffer value should be updated to the new buffer
162153
*/
163-
expect(tableData.length).toBe(1);
164-
expect(tableData[0].files.length).toBe(1);
165-
166-
/**
167-
* There should be one file buffer, the buffer should be updated to the new buffer
168-
*/
169-
expect(fileBufferData2.length).toBe(1);
170154
expect(fileBufferData2[0].buffer).toEqual(new Uint8Array([1]));
171155
});
156+
157+
it('should return the files for a table stored', async () => {
158+
const fileData = await fileManager.getFilesByTableName('taxi1');
159+
160+
expect(fileData).toEqual([
161+
{ fileName: 'taxi1.parquet', fileType: 'parquet' },
162+
{ fileName: 'taxi2.parquet', fileType: 'parquet' },
163+
]);
164+
});
165+
166+
it('should drop the file buffers for a table', async () => {
167+
// Drop a file when there are multiple files
168+
await fileManager.dropFilesByTableName('taxi1', ['taxi1.parquet']);
169+
170+
const tableData1 = await indexedDB.tablesKey.toArray();
171+
const fileBufferData1 = await indexedDB.files.toArray();
172+
173+
// Verify that the file is dropped
174+
expect(tableData1[0]).toEqual({
175+
tableName: 'taxi1',
176+
files: [
177+
{
178+
fileName: 'taxi2.parquet',
179+
fileType: 'parquet',
180+
},
181+
],
182+
});
183+
184+
expect(
185+
fileBufferData1.some((file) => file.fileName !== 'taxi1.parquet')
186+
).toBe(true);
187+
188+
// Drop a file when there is only one file
189+
await fileManager.dropFilesByTableName('taxi1', ['taxi2.parquet']);
190+
191+
const tableData2 = await indexedDB.tablesKey.toArray();
192+
const fileBufferData2 = await indexedDB.files.toArray();
193+
194+
// Verify that the file is dropped
195+
expect(tableData2[0]).toEqual({
196+
tableName: 'taxi1',
197+
files: [],
198+
});
199+
200+
expect(
201+
fileBufferData2.some((file) => file.fileName !== 'taxi2.parquet')
202+
).toBe(true);
203+
});
172204
});

meerkat-dbm/src/file-manager/indexed-db/indexed-db-file-manager.ts

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { InstanceManagerType } from '../../dbm/instance-manager';
22
import { mergeFileBufferStoreIntoTable } from '../../utils/merge-file-buffer-store-into-table';
33
import {
44
FileBufferStore,
5+
FileData,
56
FileManagerConstructorOptions,
67
FileManagerType,
7-
Table,
88
} from '../file-manager-type';
9-
import { DuckDBDatabase } from './duckdb-database';
9+
import { DuckDBFilesDatabase } from './duckdb-files-database';
10+
1011
export class IndexedDBFileManager implements FileManagerType {
11-
private indexedDB: DuckDBDatabase;
12-
private tables: Map<string, Table>;
12+
// IndexedDB instance
13+
private indexedDB: DuckDBFilesDatabase;
1314

1415
fetchTableFileBuffers: (tableName: string) => Promise<FileBufferStore[]>;
1516
instanceManager: InstanceManagerType;
@@ -20,8 +21,7 @@ export class IndexedDBFileManager implements FileManagerType {
2021
}: FileManagerConstructorOptions) {
2122
this.fetchTableFileBuffers = fetchTableFileBuffers;
2223
this.instanceManager = instanceManager;
23-
this.indexedDB = new DuckDBDatabase();
24-
this.tables = new Map();
24+
this.indexedDB = new DuckDBFilesDatabase();
2525
}
2626

2727
/**
@@ -33,29 +33,30 @@ export class IndexedDBFileManager implements FileManagerType {
3333
}
3434

3535
async initializeDB(): Promise<void> {
36-
// Clear the database when initialized
37-
await this._flushDB();
36+
return;
3837
}
3938

4039
async bulkRegisterFileBuffer(fileBuffers: FileBufferStore[]): Promise<void> {
4140
const tableNames = Array.from(
4241
new Set(fileBuffers.map((fileBuffer) => fileBuffer.tableName))
4342
);
4443

44+
const currentTableData = await this.indexedDB.tablesKey.toArray();
45+
4546
const updatedTableMap = mergeFileBufferStoreIntoTable(
4647
fileBuffers,
47-
this.tables
48+
currentTableData
4849
);
4950

5051
/**
5152
* Extracts the tables and files data from the tablesMap and fileBuffers
5253
* in format that can be stored in IndexedDB
5354
*/
54-
const tableData = tableNames.map((tableName) => {
55+
const updatedTableData = tableNames.map((tableName) => {
5556
return { tableName, files: updatedTableMap.get(tableName)?.files ?? [] };
5657
});
5758

58-
const fileData = fileBuffers.map((fileBuffer) => {
59+
const newFilesData = fileBuffers.map((fileBuffer) => {
5960
return { buffer: fileBuffer.buffer, fileName: fileBuffer.fileName };
6061
});
6162

@@ -66,11 +67,9 @@ export class IndexedDBFileManager implements FileManagerType {
6667
this.indexedDB.tablesKey,
6768
this.indexedDB.files,
6869
async () => {
69-
await this.indexedDB.tablesKey.bulkPut(tableData);
70-
71-
await this.indexedDB.files.bulkPut(fileData);
70+
await this.indexedDB.tablesKey.bulkPut(updatedTableData);
7271

73-
this.tables = updatedTableMap;
72+
await this.indexedDB.files.bulkPut(newFilesData);
7473
}
7574
)
7675
.catch((error) => {
@@ -90,9 +89,11 @@ export class IndexedDBFileManager implements FileManagerType {
9089
async registerFileBuffer(fileBuffer: FileBufferStore): Promise<void> {
9190
const { buffer, fileName, tableName } = fileBuffer;
9291

92+
const currentTableData = await this.indexedDB.tablesKey.toArray();
93+
9394
const updatedTableMap = mergeFileBufferStoreIntoTable(
9495
[fileBuffer],
95-
this.tables
96+
currentTableData
9697
);
9798

9899
// Update the tables and files table in IndexedDB
@@ -108,14 +109,14 @@ export class IndexedDBFileManager implements FileManagerType {
108109
});
109110

110111
await this.indexedDB.files.put({ fileName, buffer });
111-
112-
this.tables = updatedTableMap;
113112
}
114113
)
115114
.catch((error) => {
116115
console.error(error);
117116
});
117+
118118
const db = await this.instanceManager.getDB();
119+
119120
// Register the file buffer in the DuckDB instance
120121
return db.registerFileBuffer(fileBuffer.fileName, fileBuffer.buffer);
121122
}
@@ -128,9 +129,7 @@ export class IndexedDBFileManager implements FileManagerType {
128129
}
129130

130131
async mountFileBufferByTableNames(tableNames: string[]): Promise<void> {
131-
const tableData = tableNames.map((tableName) => {
132-
return this.tables.get(tableName);
133-
});
132+
const tableData = await this.indexedDB.tablesKey.bulkGet(tableNames);
134133

135134
const promises = tableData.map(async (table) => {
136135
// Retrieve file names for the specified table
@@ -145,6 +144,7 @@ export class IndexedDBFileManager implements FileManagerType {
145144
filesData.map(async (file) => {
146145
if (file) {
147146
const db = await this.instanceManager.getDB();
147+
148148
await db.registerFileBuffer(file.fileName, file.buffer);
149149
}
150150
})
@@ -155,9 +155,7 @@ export class IndexedDBFileManager implements FileManagerType {
155155
}
156156

157157
async unmountFileBufferByTableNames(tableNames: string[]): Promise<void> {
158-
const tableData = tableNames.map((tableName) => {
159-
return this.tables.get(tableName);
160-
});
158+
const tableData = await this.indexedDB.tablesKey.bulkGet(tableNames);
161159

162160
const promises = tableData.map(async (table) => {
163161
// Unregister file buffers from DuckDB for each table
@@ -171,4 +169,38 @@ export class IndexedDBFileManager implements FileManagerType {
171169

172170
await Promise.all(promises);
173171
}
172+
173+
/**
174+
* Get the list of files for the specified table name
175+
*/
176+
async getFilesByTableName(tableName: string): Promise<FileData[]> {
177+
const tableData = await this.indexedDB.tablesKey.get(tableName);
178+
179+
return tableData?.files ?? [];
180+
}
181+
182+
/**
183+
* Drop the specified files by tableName from the IndexedDB
184+
*/
185+
async dropFilesByTableName(
186+
tableName: string,
187+
fileNames: string[]
188+
): Promise<void> {
189+
const tableData = await this.indexedDB.tablesKey.get(tableName);
190+
191+
if (tableData) {
192+
// Retrieve the files that are not dropped
193+
const updatedFiles = tableData.files.filter(
194+
(file) => !fileNames.includes(file.fileName)
195+
);
196+
197+
await this.indexedDB.tablesKey.put({
198+
tableName,
199+
files: updatedFiles,
200+
});
201+
}
202+
203+
// Remove the files from the IndexedDB
204+
await this.indexedDB.files.bulkDelete(fileNames);
205+
}
174206
}

meerkat-dbm/src/file-manager/memory-file-manager.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { InstanceManagerType } from '../dbm/instance-manager';
22
import {
33
FileBufferStore,
4+
FileData,
45
FileManagerConstructorOptions,
56
FileManagerType,
67
} from './file-manager-type';
@@ -44,4 +45,15 @@ export class MemoryDBFileManager implements FileManagerType {
4445
async unmountFileBufferByTableNames(tableNames: string[]): Promise<void> {
4546
// not needed for memory file manager
4647
}
48+
49+
async getFilesByTableName(tableName: string): Promise<FileData[]> {
50+
// not needed for memory file manager
51+
return [];
52+
}
53+
async dropFilesByTableName(
54+
tableName: string,
55+
fileNames: string[]
56+
): Promise<void> {
57+
// not needed for memory file manager
58+
}
4759
}

0 commit comments

Comments
 (0)