From bd0cf96a5870011f476873a7e51b1af4686a97f2 Mon Sep 17 00:00:00 2001 From: DaxServer Date: Sun, 28 Sep 2025 19:33:26 +0200 Subject: [PATCH 1/3] feat(replace-operation): ensure column is string-like before replace - Checks if the column type is string-like (VARCHAR, TEXT, BLOB) - Converts the column to VARCHAR if it's not a string-like type - This ensures the replace operation works correctly for non-string columns feat(project-store): Add pagination state and refresh current page - Adds current offset and limit to the project store - Adds a `refreshCurrentPage` action to fetch the current page again - This allows the UI to easily refresh the current page without losing pagination state fix(replace-dialog): Handle replace operation errors properly - Emits the `replace-completed` event with the affected rows count - Displays a generic error message if the replace operation fails - Removes unnecessary check for `affectedRows` being undefined or null --- .../src/services/replace-operation.service.ts | 45 ++++ ...lace-operation.alter-table.service.test.ts | 221 ++++++++++++++++++ .../components/ColumnHeaderMenu.vue | 6 + .../components/DataTabPanel.vue | 3 +- .../components/ReplaceDialog.vue | 39 ++-- .../stores/project.store.ts | 15 ++ 6 files changed, 315 insertions(+), 14 deletions(-) create mode 100644 backend/tests/services/replace-operation.alter-table.service.test.ts diff --git a/backend/src/services/replace-operation.service.ts b/backend/src/services/replace-operation.service.ts index 6379a84..9340770 100644 --- a/backend/src/services/replace-operation.service.ts +++ b/backend/src/services/replace-operation.service.ts @@ -18,6 +18,9 @@ export class ReplaceOperationService { async performReplace(params: ReplaceOperationParams): Promise { const { table, column, find, replace, caseSensitive, wholeWord } = params + // Check if column is string-like, if not, convert it first + await this.ensureColumnIsStringType(table, column) + // Count rows that will be affected before the update const affectedRows = await this.countAffectedRows(table, column, find, caseSensitive, wholeWord) @@ -154,4 +157,46 @@ export class ReplaceOperationService { private escapeRegex(str: string): string { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } + + /** + * Gets the column type from the table schema + */ + private async getColumnType(table: string, column: string): Promise { + const result = await this.db.runAndReadAll(`PRAGMA table_info("${table}")`) + const columns = result.getRowObjectsJson() as Array<{ + cid: number + name: string + type: string + pk: boolean + notnull: boolean + dflt_value: string | null + }> + + const columnInfo = columns.find((col) => col.name === column) + if (!columnInfo) { + throw new Error(`Column '${column}' not found in table '${table}'`) + } + + return columnInfo.type.toUpperCase() + } + + /** + * Checks if a column type is string-like (VARCHAR, TEXT, BLOB) + */ + private isStringLikeType(columnType: string): boolean { + const stringTypes = ['VARCHAR', 'TEXT', 'BLOB', 'CHAR', 'BPCHAR'] + return stringTypes.some((type) => columnType.includes(type)) + } + + /** + * Ensures the column is a string-like type, converting it if necessary + */ + private async ensureColumnIsStringType(table: string, column: string): Promise { + const columnType = await this.getColumnType(table, column) + + if (!this.isStringLikeType(columnType)) { + // Convert the column to VARCHAR + await this.db.run(`ALTER TABLE "${table}" ALTER "${column}" TYPE VARCHAR`) + } + } } diff --git a/backend/tests/services/replace-operation.alter-table.service.test.ts b/backend/tests/services/replace-operation.alter-table.service.test.ts new file mode 100644 index 0000000..89e681d --- /dev/null +++ b/backend/tests/services/replace-operation.alter-table.service.test.ts @@ -0,0 +1,221 @@ +import { closeDb, getDb, initializeDb } from '@backend/plugins/database' +import { ReplaceOperationService } from '@backend/services/replace-operation.service' +import { afterEach, beforeEach, describe, expect, test } from 'bun:test' + +describe('non-string column datatype conversion', () => { + beforeEach(async () => { + await initializeDb(':memory:') + }) + + afterEach(async () => { + await closeDb() + }) + + const testCases = [ + { + name: 'INTEGER', + tableSql: ` + CREATE TABLE test ( + id INTEGER, + age INTEGER, + name VARCHAR + ) + `, + insertSql: [ + `INSERT INTO test (id, age, name) VALUES (1, 25, 'John')`, + `INSERT INTO test (id, age, name) VALUES (2, 30, 'Jane')`, + `INSERT INTO test (id, age, name) VALUES (3, 25, 'Bob')`, + ], + columnName: 'age', + initialType: 'INTEGER', + find: '25', + replace: '35', + expectedAffectedRows: 2, + expectedResults: ['35', '30', '35'], + }, + { + name: 'DOUBLE', + tableSql: ` + CREATE TABLE test ( + id INTEGER, + price DOUBLE, + product VARCHAR + ) + `, + insertSql: [ + `INSERT INTO test (id, price, product) VALUES (1, 19.99, 'Apple')`, + `INSERT INTO test (id, price, product) VALUES (2, 25.50, 'Banana')`, + `INSERT INTO test (id, price, product) VALUES (3, 19.99, 'Cherry')`, + ], + columnName: 'price', + initialType: 'DOUBLE', + find: '19.99', + replace: '29.99', + expectedAffectedRows: 2, + expectedResults: ['29.99', '25.5', '29.99'], + }, + { + name: 'BOOLEAN', + tableSql: ` + CREATE TABLE test ( + id INTEGER, + is_active BOOLEAN, + username VARCHAR + ) + `, + insertSql: [ + `INSERT INTO test (id, is_active, username) VALUES (1, true, 'user1')`, + `INSERT INTO test (id, is_active, username) VALUES (2, false, 'user2')`, + `INSERT INTO test (id, is_active, username) VALUES (3, true, 'user3')`, + ], + columnName: 'is_active', + initialType: 'BOOLEAN', + find: 'true', + replace: 'active', + expectedAffectedRows: 2, + expectedResults: ['active', 'false', 'active'], + }, + { + name: 'DATE', + tableSql: ` + CREATE TABLE test ( + id INTEGER, + created_date DATE, + status VARCHAR + ) + `, + insertSql: [ + `INSERT INTO test (id, created_date, status) VALUES (1, '2023-01-15', 'active')`, + `INSERT INTO test (id, created_date, status) VALUES (2, '2023-02-20', 'inactive')`, + `INSERT INTO test (id, created_date, status) VALUES (3, '2023-01-15', 'pending')`, + ], + columnName: 'created_date', + initialType: 'DATE', + find: '2023-01-15', + replace: '2023-03-01', + expectedAffectedRows: 2, + expectedResults: ['2023-03-01', '2023-02-20', '2023-03-01'], + }, + { + name: 'VARCHAR', + tableSql: ` + CREATE TABLE test ( + id INTEGER, + description VARCHAR(255), + category VARCHAR + ) + `, + insertSql: [ + `INSERT INTO test (id, description, category) VALUES (1, 'test item', 'A')`, + `INSERT INTO test (id, description, category) VALUES (2, 'another test', 'B')`, + ], + columnName: 'description', + initialType: 'VARCHAR', + find: 'test', + replace: 'sample', + expectedAffectedRows: 2, + expectedResults: ['sample item', 'another sample'], + }, + { + name: 'JSON', + tableSql: ` + CREATE TABLE test ( + id INTEGER, + metadata JSON, + name VARCHAR + ) + `, + insertSql: [ + `INSERT INTO test (id, metadata, name) VALUES (1, '{"status": "active", "priority": "high"}', 'Item1')`, + `INSERT INTO test (id, metadata, name) VALUES (2, '{"status": "inactive", "priority": "low"}', 'Item2')`, + `INSERT INTO test (id, metadata, name) VALUES (3, '{"status": "active", "priority": "medium"}', 'Item3')`, + ], + columnName: 'metadata', + initialType: 'JSON', + find: '"active"', + replace: '"pending"', + expectedAffectedRows: 2, + expectedResults: ['{"status": "pending", "priority": "high"}', '{"status": "inactive", "priority": "low"}', '{"status": "pending", "priority": "medium"}'], + }, + { + name: 'JSON', + tableSql: ` + CREATE TABLE test ( + id INTEGER, + metadata JSON, + name VARCHAR + ) + `, + insertSql: [ + `INSERT INTO test (id, metadata, name) VALUES (1, '{"status": "active", "priority": "high"}', 'Item1')`, + `INSERT INTO test (id, metadata, name) VALUES (2, '{"status": "inactive", "priority": "low"}', 'Item2')`, + `INSERT INTO test (id, metadata, name) VALUES (3, '{"status": "active", "priority": "medium"}', 'Item3')`, + ], + columnName: 'metadata', + initialType: 'JSON', + find: 'active', + replace: 'pending', + expectedAffectedRows: 3, + expectedResults: ['{"status": "pending", "priority": "high"}', '{"status": "inpending", "priority": "low"}', '{"status": "pending", "priority": "medium"}'], + }, + ] + + test.each(testCases)( + 'should $name column and perform replace', + async ({ + tableSql, + insertSql, + columnName, + initialType, + find, + replace, + expectedAffectedRows, + expectedResults, + }) => { + const db = getDb() + const service = new ReplaceOperationService(db) + + await db.run(tableSql) + + // Insert test data + for (const sql of insertSql) { + await db.run(sql) + } + + // Verify initial column type + const initialTypeResult = (await db.runAndReadAll(`PRAGMA table_info("test")`)).getRowObjectsJson() as Array<{ + name: string + type: string + }> + const column = initialTypeResult.find((col) => col.name === columnName) + expect(column).toBeDefined() + expect(column!.type).toBe(initialType) + + // Perform replace operation + const affectedRows = await service.performReplace({ + table: 'test', + column: columnName, + find, + replace, + caseSensitive: false, + wholeWord: false, + }) + + expect(affectedRows).toBe(expectedAffectedRows) + + // Verify column type + const finalType = (await db.runAndReadAll(`PRAGMA table_info("test")`)).getRowObjectsJson() as Array<{ + name: string + type: string + }> + const columnAfter = finalType.find((col) => col.name === columnName) + expect(columnAfter).toBeDefined() + expect(columnAfter!.type).toBe('VARCHAR') + + // Verify data was replaced correctly + const result = await db.runAndReadAll(`SELECT ${columnName} FROM "test" ORDER BY id`) + const values = result.getRowObjectsJson().map((row) => row[columnName]) + expect(values).toEqual(expectedResults) + }, + ) +}) diff --git a/frontend/src/features/data-processing/components/ColumnHeaderMenu.vue b/frontend/src/features/data-processing/components/ColumnHeaderMenu.vue index 368033b..f243473 100644 --- a/frontend/src/features/data-processing/components/ColumnHeaderMenu.vue +++ b/frontend/src/features/data-processing/components/ColumnHeaderMenu.vue @@ -5,14 +5,20 @@ const props = defineProps<{ isPrimaryKey: boolean }>() +const emit = defineEmits<{ + replaceCompleted: [projectId: string] +}>() + const { showSuccess } = useErrorHandling() const menu = ref() const isOpen = ref(false) const showReplaceDialog = ref(false) +const projectId = useRouteParams('id') as Ref const handleReplaceCompleted = (affectedRows: number) => { showSuccess(`Replace completed: ${affectedRows} rows affected`) + emit('replaceCompleted', projectId.value) } const menuItems = ref([ diff --git a/frontend/src/features/data-processing/components/DataTabPanel.vue b/frontend/src/features/data-processing/components/DataTabPanel.vue index b24103b..d4a43e6 100644 --- a/frontend/src/features/data-processing/components/DataTabPanel.vue +++ b/frontend/src/features/data-processing/components/DataTabPanel.vue @@ -3,7 +3,7 @@ const projectId = useRouteParams('id') as Ref const projectStore = useProjectStore() const { meta, isLoading, data, columns } = storeToRefs(projectStore) -const { fetchProject, clearProject } = projectStore +const { fetchProject, refreshCurrentPage, clearProject } = projectStore const { processHtml } = useHtml() const totalRecords = computed(() => meta.value.total) @@ -63,6 +63,7 @@ onUnmounted(() => clearProject()) :column-field="col.field" :column-header="col.header" :is-primary-key="col.pk" + @replace-completed="() => refreshCurrentPage(projectId)" /> {{ col.header }} diff --git a/frontend/src/features/data-processing/components/ReplaceDialog.vue b/frontend/src/features/data-processing/components/ReplaceDialog.vue index 0b9c413..7974330 100644 --- a/frontend/src/features/data-processing/components/ReplaceDialog.vue +++ b/frontend/src/features/data-processing/components/ReplaceDialog.vue @@ -41,16 +41,9 @@ const handleReplace = async () => { return } - if (!data?.affectedRows) { - showError([{ code: 'NOT_FOUND', message: 'Replace operation failed' }]) - return - } - - if (data?.affectedRows !== undefined && data?.affectedRows !== null) { - emit('replace-completed', data.affectedRows) - } + emit('replace-completed', data.affectedRows) } catch (error) { - console.error('Replace operation failed:', error) + showError([{ code: 'INTERNAL_SERVER_ERROR', message: error as string }]) } finally { isLoading.value = false closeDialog() @@ -88,7 +81,12 @@ const handleVisibleChange = (visible: boolean) => { >
- + {
- + { binary :disabled="isLoading" /> - +
@@ -125,7 +133,12 @@ const handleVisibleChange = (visible: boolean) => { binary :disabled="isLoading" /> - +
diff --git a/frontend/src/features/project-management/stores/project.store.ts b/frontend/src/features/project-management/stores/project.store.ts index 2b71e57..eda5cbe 100644 --- a/frontend/src/features/project-management/stores/project.store.ts +++ b/frontend/src/features/project-management/stores/project.store.ts @@ -17,10 +17,18 @@ export const useProjectStore = defineStore('project', () => { const isLoading = ref(false) const columns = ref([]) + // Current pagination state + const currentOffset = ref(0) + const currentLimit = ref(25) + // Actions const fetchProject = async (projectId: string, offset = 0, limit = 25) => { isLoading.value = true + // Store current pagination state + currentOffset.value = offset + currentLimit.value = limit + const { data: rows, error } = await api.project({ projectId }).get({ query: { offset, limit } }) if (error) { @@ -36,6 +44,10 @@ export const useProjectStore = defineStore('project', () => { isLoading.value = false } + const refreshCurrentPage = async (projectId: string) => { + await fetchProject(projectId, currentOffset.value, currentLimit.value) + } + const clearProject = () => { data.value = [] meta.value = { @@ -78,9 +90,12 @@ export const useProjectStore = defineStore('project', () => { isLoading, columns, columnsForSchema, + currentOffset, + currentLimit, // Actions fetchProject, + refreshCurrentPage, clearProject, } }) From c10c1bd3ebc28e2a4898bcd24c0d6bacc3202454 Mon Sep 17 00:00:00 2001 From: DaxServer Date: Sun, 28 Sep 2025 20:03:59 +0200 Subject: [PATCH 2/3] fix: apply code review suggestions --- .../src/services/replace-operation.service.ts | 25 ++++- ...lace-operation.alter-table.service.test.ts | 100 ++++++++++++++++-- .../components/ColumnHeaderMenu.vue | 15 +-- .../components/ReplaceDialog.vue | 3 +- .../shared/composables/useErrorHandling.ts | 10 ++ 5 files changed, 135 insertions(+), 18 deletions(-) diff --git a/backend/src/services/replace-operation.service.ts b/backend/src/services/replace-operation.service.ts index 9340770..8495e26 100644 --- a/backend/src/services/replace-operation.service.ts +++ b/backend/src/services/replace-operation.service.ts @@ -18,14 +18,21 @@ export class ReplaceOperationService { async performReplace(params: ReplaceOperationParams): Promise { const { table, column, find, replace, caseSensitive, wholeWord } = params + // Get the original column type before any modifications + const originalColumnType = await this.getColumnType(table, column) + // Check if column is string-like, if not, convert it first - await this.ensureColumnIsStringType(table, column) + const wasConverted = await this.ensureColumnIsStringType(table, column) // Count rows that will be affected before the update const affectedRows = await this.countAffectedRows(table, column, find, caseSensitive, wholeWord) // Only proceed if there are rows to update if (affectedRows === 0) { + // Revert column type if it was converted and no rows were affected + if (wasConverted) { + await this.changeColumnType(table, column, originalColumnType) + } return 0 } @@ -185,18 +192,30 @@ export class ReplaceOperationService { */ private isStringLikeType(columnType: string): boolean { const stringTypes = ['VARCHAR', 'TEXT', 'BLOB', 'CHAR', 'BPCHAR'] + return stringTypes.some((type) => columnType.includes(type)) } /** * Ensures the column is a string-like type, converting it if necessary + * Returns true if the column was converted, false otherwise */ - private async ensureColumnIsStringType(table: string, column: string): Promise { + private async ensureColumnIsStringType(table: string, column: string): Promise { const columnType = await this.getColumnType(table, column) if (!this.isStringLikeType(columnType)) { // Convert the column to VARCHAR - await this.db.run(`ALTER TABLE "${table}" ALTER "${column}" TYPE VARCHAR`) + await this.changeColumnType(table, column, 'VARCHAR') + return true } + + return false + } + + /** + * Changes the column type to the specified type + */ + private async changeColumnType(table: string, column: string, newType: string): Promise { + await this.db.run(`ALTER TABLE "${table}" ALTER "${column}" TYPE ${newType}`) } } diff --git a/backend/tests/services/replace-operation.alter-table.service.test.ts b/backend/tests/services/replace-operation.alter-table.service.test.ts index 89e681d..cc67065 100644 --- a/backend/tests/services/replace-operation.alter-table.service.test.ts +++ b/backend/tests/services/replace-operation.alter-table.service.test.ts @@ -135,7 +135,11 @@ describe('non-string column datatype conversion', () => { find: '"active"', replace: '"pending"', expectedAffectedRows: 2, - expectedResults: ['{"status": "pending", "priority": "high"}', '{"status": "inactive", "priority": "low"}', '{"status": "pending", "priority": "medium"}'], + expectedResults: [ + '{"status": "pending", "priority": "high"}', + '{"status": "inactive", "priority": "low"}', + '{"status": "pending", "priority": "medium"}', + ], }, { name: 'JSON', @@ -156,7 +160,78 @@ describe('non-string column datatype conversion', () => { find: 'active', replace: 'pending', expectedAffectedRows: 3, - expectedResults: ['{"status": "pending", "priority": "high"}', '{"status": "inpending", "priority": "low"}', '{"status": "pending", "priority": "medium"}'], + expectedResults: [ + '{"status": "pending", "priority": "high"}', + '{"status": "inpending", "priority": "low"}', + '{"status": "pending", "priority": "medium"}', + ], + }, + // Test cases for zero affected rows - column type should be reverted + { + name: 'INTEGER with zero affected rows', + tableSql: ` + CREATE TABLE test ( + id INTEGER, + age INTEGER, + name VARCHAR + ) + `, + insertSql: [ + `INSERT INTO test (id, age, name) VALUES (1, 25, 'John')`, + `INSERT INTO test (id, age, name) VALUES (2, 30, 'Jane')`, + `INSERT INTO test (id, age, name) VALUES (3, 35, 'Bob')`, + ], + columnName: 'age', + initialType: 'INTEGER', + find: '99', // This value doesn't exist in the data + replace: '100', + expectedAffectedRows: 0, + expectedResults: [25, 30, 35], // Data should remain unchanged + expectTypeReverted: true, // Special flag to indicate type should be reverted + }, + { + name: 'DOUBLE with zero affected rows', + tableSql: ` + CREATE TABLE test ( + id INTEGER, + price DOUBLE, + product VARCHAR + ) + `, + insertSql: [ + `INSERT INTO test (id, price, product) VALUES (1, 19.99, 'Apple')`, + `INSERT INTO test (id, price, product) VALUES (2, 25.50, 'Banana')`, + `INSERT INTO test (id, price, product) VALUES (3, 35.00, 'Cherry')`, + ], + columnName: 'price', + initialType: 'DOUBLE', + find: '99.99', // This value doesn't exist in the data + replace: '100.00', + expectedAffectedRows: 0, + expectedResults: [19.99, 25.5, 35.0], // Data should remain unchanged + expectTypeReverted: true, // Special flag to indicate type should be reverted + }, + { + name: 'BOOLEAN with zero affected rows', + tableSql: ` + CREATE TABLE test ( + id INTEGER, + is_active BOOLEAN, + username VARCHAR + ) + `, + insertSql: [ + `INSERT INTO test (id, is_active, username) VALUES (1, true, 'user1')`, + `INSERT INTO test (id, is_active, username) VALUES (2, false, 'user2')`, + `INSERT INTO test (id, is_active, username) VALUES (3, true, 'user3')`, + ], + columnName: 'is_active', + initialType: 'BOOLEAN', + find: 'maybe', // This value doesn't exist in boolean data + replace: 'possibly', + expectedAffectedRows: 0, + expectedResults: [true, false, true], // Data should remain unchanged + expectTypeReverted: true, // Special flag to indicate type should be reverted }, ] @@ -171,6 +246,7 @@ describe('non-string column datatype conversion', () => { replace, expectedAffectedRows, expectedResults, + expectTypeReverted, }) => { const db = getDb() const service = new ReplaceOperationService(db) @@ -183,7 +259,9 @@ describe('non-string column datatype conversion', () => { } // Verify initial column type - const initialTypeResult = (await db.runAndReadAll(`PRAGMA table_info("test")`)).getRowObjectsJson() as Array<{ + const initialTypeResult = ( + await db.runAndReadAll(`PRAGMA table_info("test")`) + ).getRowObjectsJson() as Array<{ name: string type: string }> @@ -203,16 +281,24 @@ describe('non-string column datatype conversion', () => { expect(affectedRows).toBe(expectedAffectedRows) - // Verify column type - const finalType = (await db.runAndReadAll(`PRAGMA table_info("test")`)).getRowObjectsJson() as Array<{ + // Verify column type after operation + const finalType = ( + await db.runAndReadAll(`PRAGMA table_info("test")`) + ).getRowObjectsJson() as Array<{ name: string type: string }> const columnAfter = finalType.find((col) => col.name === columnName) expect(columnAfter).toBeDefined() - expect(columnAfter!.type).toBe('VARCHAR') - // Verify data was replaced correctly + // For zero affected rows with non-string types, expect type to be reverted + if (expectTypeReverted) { + expect(columnAfter!.type).toBe(initialType) + } else { + expect(columnAfter!.type).toBe('VARCHAR') + } + + // Verify data was replaced correctly (or unchanged for zero affected rows) const result = await db.runAndReadAll(`SELECT ${columnName} FROM "test" ORDER BY id`) const values = result.getRowObjectsJson().map((row) => row[columnName]) expect(values).toEqual(expectedResults) diff --git a/frontend/src/features/data-processing/components/ColumnHeaderMenu.vue b/frontend/src/features/data-processing/components/ColumnHeaderMenu.vue index f243473..5973a4b 100644 --- a/frontend/src/features/data-processing/components/ColumnHeaderMenu.vue +++ b/frontend/src/features/data-processing/components/ColumnHeaderMenu.vue @@ -5,20 +5,21 @@ const props = defineProps<{ isPrimaryKey: boolean }>() -const emit = defineEmits<{ - replaceCompleted: [projectId: string] -}>() +const emit = defineEmits(['replaceCompleted']) -const { showSuccess } = useErrorHandling() +const { showSuccess, showWarning } = useErrorHandling() const menu = ref() const isOpen = ref(false) const showReplaceDialog = ref(false) -const projectId = useRouteParams('id') as Ref const handleReplaceCompleted = (affectedRows: number) => { - showSuccess(`Replace completed: ${affectedRows} rows affected`) - emit('replaceCompleted', projectId.value) + if (affectedRows === 0) { + showWarning('Replace completed: No rows were affected') + } else { + showSuccess(`Replace completed: ${affectedRows} rows affected`) + emit('replaceCompleted') + } } const menuItems = ref([ diff --git a/frontend/src/features/data-processing/components/ReplaceDialog.vue b/frontend/src/features/data-processing/components/ReplaceDialog.vue index 7974330..eba4e90 100644 --- a/frontend/src/features/data-processing/components/ReplaceDialog.vue +++ b/frontend/src/features/data-processing/components/ReplaceDialog.vue @@ -43,7 +43,8 @@ const handleReplace = async () => { emit('replace-completed', data.affectedRows) } catch (error) { - showError([{ code: 'INTERNAL_SERVER_ERROR', message: error as string }]) + const message = error instanceof Error ? error.message : String(error) + showError([{ code: 'INTERNAL_SERVER_ERROR', message }]) } finally { isLoading.value = false closeDialog() diff --git a/frontend/src/shared/composables/useErrorHandling.ts b/frontend/src/shared/composables/useErrorHandling.ts index bdd89a6..876d036 100644 --- a/frontend/src/shared/composables/useErrorHandling.ts +++ b/frontend/src/shared/composables/useErrorHandling.ts @@ -30,9 +30,19 @@ export const useErrorHandling = () => { }) } + const showWarning = (text: string): void => { + toast.add({ + severity: 'warn', + summary: 'Warning', + detail: text, + life: 3000, + }) + } + return { showError, showSuccess, showInfo, + showWarning, } } From 4d713ed0a9d9fe128187207b3ccc9a969ad9066d Mon Sep 17 00:00:00 2001 From: DaxServer Date: Sun, 28 Sep 2025 20:18:14 +0200 Subject: [PATCH 3/3] fix: apply code review suggestion --- backend/src/services/replace-operation.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/replace-operation.service.ts b/backend/src/services/replace-operation.service.ts index 8495e26..bb7bda2 100644 --- a/backend/src/services/replace-operation.service.ts +++ b/backend/src/services/replace-operation.service.ts @@ -191,7 +191,7 @@ export class ReplaceOperationService { * Checks if a column type is string-like (VARCHAR, TEXT, BLOB) */ private isStringLikeType(columnType: string): boolean { - const stringTypes = ['VARCHAR', 'TEXT', 'BLOB', 'CHAR', 'BPCHAR'] + const stringTypes = ['VARCHAR', 'TEXT', 'CHAR', 'BPCHAR'] return stringTypes.some((type) => columnType.includes(type)) }