Skip to content

Commit 227350c

Browse files
committed
Switches "UNIQUE" constraint check to a "SELECT"
1 parent e1c9a8d commit 227350c

File tree

1 file changed

+29
-26
lines changed

1 file changed

+29
-26
lines changed

app/server/lib/DocStorage.ts

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -786,11 +786,7 @@ export class DocStorage implements ISQLiteDB, OnDemandStorage {
786786
storageId?: string,
787787
): Promise<boolean> {
788788
return this.execTransaction(async (db) => {
789-
const isNewFile = await this._addBasicFileRecord(db, fileIdent);
790-
if (isNewFile) {
791-
await this._updateFileRecord(db, fileIdent, fileData, storageId);
792-
}
793-
return isNewFile;
789+
return await this._addFileRecord(db, fileIdent, fileData, storageId);
794790
});
795791
}
796792

@@ -813,17 +809,20 @@ export class DocStorage implements ISQLiteDB, OnDemandStorage {
813809
storageId?: string,
814810
): Promise<boolean> {
815811
return this.execTransaction(async (db) => {
816-
const isNewFile = await this._addBasicFileRecord(db, fileIdent);
817-
await this._updateFileRecord(db, fileIdent, fileData, storageId);
818-
return isNewFile;
812+
const wasAdded = await this._addFileRecord(db, fileIdent, fileData, storageId);
813+
if (!wasAdded) {
814+
await this._updateFileRecord(db, fileIdent, fileData, storageId);
815+
}
816+
return wasAdded;
819817
});
820818
}
821819

822820
/**
823821
* Reads and returns the data for the given attachment.
824822
* @param {string} fileIdent - The unique identifier of a file, as used by attachFileIfNew.
825823
* @param {boolean} includeData - Load file contents from the database, in addition to metadata
826-
* @returns {Promise[FileInfo | null]} - File information, or null if no record exists for that file identifier.
824+
* @returns {Promise[FileInfo | null]} - File information, or null if no record exists for that
825+
* file identifier.
827826
*/
828827
public getFileInfo(fileIdent: string, includeData: boolean = true): Promise<FileInfo | null> {
829828
const columns = includeData ? 'ident, storageId, data' : 'ident, storageId';
@@ -938,9 +937,10 @@ export class DocStorage implements ISQLiteDB, OnDemandStorage {
938937
}
939938

940939
/**
941-
* Unmarshals and decodes data received from db.allMarshal() method (which we added to node-sqlite3).
942-
* The data is a dictionary mapping column ids (including 'id') to arrays of values. This should
943-
* be used for Grist data, which is encoded. For non-Grist data, use `marshal.loads()`.
940+
* Unmarshals and decodes data received from db.allMarshal() method (which we added to
941+
* node-sqlite3). The data is a dictionary mapping column ids (including 'id') to arrays of
942+
* values. This should be used for Grist data, which is encoded. For non-Grist data, use
943+
* `marshal.loads()`.
944944
*
945945
* Note that we do NOT use this when loading data from a document, since the whole point of
946946
* db.allMarshal() is to pass data directly to Python data engine without parsing in Node.
@@ -1446,8 +1446,9 @@ export class DocStorage implements ISQLiteDB, OnDemandStorage {
14461446
}
14471447

14481448
/**
1449-
* Delete attachments from _gristsys_Files that have no matching metadata row in _grist_Attachments.
1450-
* This leaves any attachment files in any remote attachment stores, which will be cleaned up separately.
1449+
* Delete attachments from _gristsys_Files that have no matching metadata row in
1450+
* _grist_Attachments. This leaves any attachment files in any remote attachment stores, which
1451+
* will be cleaned up separately.
14511452
*/
14521453
public async removeUnusedAttachments() {
14531454
const result = await this._getDB().run(`
@@ -1861,18 +1862,18 @@ export class DocStorage implements ISQLiteDB, OnDemandStorage {
18611862
return null;
18621863
}
18631864

1864-
private async _addBasicFileRecord(db: SQLiteDB, fileIdent: string): Promise<boolean> {
1865-
try {
1866-
// Try to insert a new record with the given ident. It'll fail UNIQUE constraint if exists.
1867-
await db.run('INSERT INTO _gristsys_Files (ident, data, storageId) VALUES (?)', fileIdent);
1868-
} catch(err) {
1869-
// If UNIQUE constraint failed, this ident must already exist.
1870-
if (/^(SQLITE_CONSTRAINT: )?UNIQUE constraint failed/.test(err.message)) {
1871-
return false;
1872-
} else {
1873-
throw err;
1874-
}
1865+
// This should be executed inside a transaction.
1866+
private async _addFileRecord(
1867+
db: SQLiteDB, fileIdent: string, fileData?: Buffer, storageId?: string
1868+
): Promise<boolean> {
1869+
const result = await db.get("SELECT 1 AS fileExists from _gristsys_Files WHERE ident = ?", fileIdent);
1870+
if (result?.fileExists) {
1871+
return false;
18751872
}
1873+
await db.run(
1874+
'INSERT INTO main._gristsys_Files (ident, data, storageId) VALUES (?, ?, ?)',
1875+
fileIdent, fileData, storageId
1876+
);
18761877
return true;
18771878
}
18781879

@@ -1881,6 +1882,7 @@ export class DocStorage implements ISQLiteDB, OnDemandStorage {
18811882
): Promise<void> {
18821883
await db.run('UPDATE _gristsys_Files SET data=?, storageId=? WHERE ident=?', fileData, storageId, fileIdent);
18831884
}
1885+
18841886
}
18851887

18861888
interface RebuildResult {
@@ -1905,7 +1907,8 @@ export interface IndexInfo extends IndexColumns {
19051907
}
19061908

19071909
/**
1908-
* Creates an index that allows fast SQL JOIN between _grist_Attachments.fileIdent and _gristsys_Files.ident.
1910+
* Creates an index that allows fast SQL JOIN between _grist_Attachments.fileIdent and
1911+
* _gristsys_Files.ident.
19091912
*/
19101913
export async function createAttachmentsIndex(db: ISQLiteDB) {
19111914
await db.exec(`CREATE INDEX _grist_Attachments_fileIdent ON _grist_Attachments(fileIdent)`);

0 commit comments

Comments
 (0)