Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/components/sql-editor/hooks/useEditorTabs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useCallback, useMemo, useEffect } from 'react'
import type { ObjectType } from '../ObjectTree' // Used for SchemaTab.objectType
import { createId } from '@/lib/create-id'

export interface QueryTab {
type: 'query'
Expand Down Expand Up @@ -430,7 +431,7 @@ export function useEditorTabs(connectionId: string) {
title += ' (cancelled)'
}
return {
id: crypto.randomUUID(),
id: createId(),
title,
result,
sql: options?.sql,
Expand Down
11 changes: 11 additions & 0 deletions src/lib/create-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function createId(): string {
if (
typeof globalThis !== 'undefined' &&
globalThis.crypto &&
typeof globalThis.crypto.randomUUID === 'function'
) {
return globalThis.crypto.randomUUID()
}

return `id-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fallback IDs can collide The fallback ID only combines the current millisecond with Math.random(). When two IDs are created in the same millisecond and Math.random() repeats or has weak entropy, callers receive duplicate IDs. Result tabs use these IDs as React keys and as keys into displayRowsMap, so duplicate IDs can make two result tabs share row state or make tab selection point at the wrong result. Staged changes also remove entries by matching ID, so a duplicate can remove more than one change.

Suggested change
return `id-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`
const random = Math.random().toString(36).slice(2, 11)
return `id-${Date.now()}-${random}-${createId.counter++}`

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The program runs on the client side, and the scenario described above is very unlikely to happen. If you really want to avoid this situation completely, you can use the third-party library uuid.

}
7 changes: 4 additions & 3 deletions src/lib/staged-changes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ColumnMetadata } from '@/components/sql-editor/hooks/useEditorTabs'
import { createId } from '@/lib/create-id'

export type StagedChangeType = 'delete' | 'update' | 'insert'

Expand Down Expand Up @@ -138,7 +139,7 @@ export function createStagedDelete(
}

return {
id: crypto.randomUUID(),
id: createId(),
type: 'delete',
tables,
rowCount: selectedRows.length,
Expand Down Expand Up @@ -263,7 +264,7 @@ export function createStagedInsert(
}

return {
id: crypto.randomUUID(),
id: createId(),
type: 'insert',
tables,
rowCount: rows.length,
Expand Down Expand Up @@ -323,7 +324,7 @@ export function createStagedUpdate(
}

return {
id: crypto.randomUUID(),
id: createId(),
type: 'update',
tables,
rowCount: 1,
Expand Down