|
1 | | -import type { DuckDBConnection, DuckDBValue } from '@duckdb/node-api' |
| 1 | +import type { ColumnOperationParams } from '@backend/services/column-operation.service' |
| 2 | +import { ColumnOperationService } from '@backend/services/column-operation.service' |
| 3 | +import type { DuckDBValue } from '@duckdb/node-api' |
2 | 4 |
|
3 | | -export interface ReplaceOperationParams { |
4 | | - table: string |
5 | | - column: string |
| 5 | +interface ReplaceOperationParams extends ColumnOperationParams { |
6 | 6 | find: string |
7 | 7 | replace: string |
8 | 8 | caseSensitive: boolean |
9 | 9 | wholeWord: boolean |
10 | 10 | } |
11 | 11 |
|
12 | | -export class ReplaceOperationService { |
13 | | - constructor(private db: DuckDBConnection) {} |
14 | | - |
15 | | - /** |
16 | | - * Performs a replace operation on a column in a project table |
17 | | - */ |
18 | | - async performReplace(params: ReplaceOperationParams): Promise<number> { |
| 12 | +export class ReplaceOperationService extends ColumnOperationService { |
| 13 | + public async performOperation(params: ReplaceOperationParams): Promise<number> { |
19 | 14 | const { table, column, find, replace, caseSensitive, wholeWord } = params |
20 | 15 |
|
21 | | - // Get the original column type before any modifications |
22 | | - const originalColumnType = await this.getColumnType(table, column) |
23 | | - |
24 | | - // Check if column is string-like, if not, convert it first |
25 | | - const wasConverted = await this.ensureColumnIsStringType(table, column) |
26 | | - |
27 | | - // Count rows that will be affected before the update |
28 | | - const affectedRows = await this.countAffectedRows(table, column, find, caseSensitive, wholeWord) |
29 | | - |
30 | | - // Only proceed if there are rows to update |
31 | | - if (affectedRows === 0) { |
32 | | - // Revert column type if it was converted and no rows were affected |
33 | | - if (wasConverted) { |
34 | | - await this.changeColumnType(table, column, originalColumnType) |
35 | | - } |
36 | | - return 0 |
37 | | - } |
38 | | - |
39 | | - // Build and execute the parameterized UPDATE query |
40 | | - const { query, params: queryParams } = this.buildParameterizedUpdateQuery( |
| 16 | + return this.executeColumnOperation( |
41 | 17 | table, |
42 | 18 | column, |
43 | | - find, |
44 | | - replace, |
45 | | - caseSensitive, |
46 | | - wholeWord, |
| 19 | + () => |
| 20 | + this.buildParameterizedUpdateQuery(table, column, find, replace, caseSensitive, wholeWord), |
| 21 | + () => this.countAffectedRows(table, column, find, caseSensitive, wholeWord), |
47 | 22 | ) |
48 | | - |
49 | | - await this.db.run(query, queryParams) |
50 | | - |
51 | | - return affectedRows |
52 | 23 | } |
53 | 24 |
|
54 | 25 | /** |
@@ -113,7 +84,7 @@ export class ReplaceOperationService { |
113 | 84 | /** |
114 | 85 | * Counts the number of rows that will be affected by the replace operation |
115 | 86 | */ |
116 | | - private async countAffectedRows( |
| 87 | + private countAffectedRows( |
117 | 88 | table: string, |
118 | 89 | column: string, |
119 | 90 | find: string, |
@@ -152,70 +123,6 @@ export class ReplaceOperationService { |
152 | 123 | } |
153 | 124 | } |
154 | 125 |
|
155 | | - const countBeforeReader = await this.db.runAndReadAll(query, params) |
156 | | - const countBeforeResult = countBeforeReader.getRowObjectsJson() |
157 | | - |
158 | | - return Number(countBeforeResult[0]?.count ?? 0) |
159 | | - } |
160 | | - |
161 | | - /** |
162 | | - * Escapes special regex characters in a string |
163 | | - */ |
164 | | - private escapeRegex(str: string): string { |
165 | | - return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') |
166 | | - } |
167 | | - |
168 | | - /** |
169 | | - * Gets the column type from the table schema |
170 | | - */ |
171 | | - private async getColumnType(table: string, column: string): Promise<string> { |
172 | | - const result = await this.db.runAndReadAll(`PRAGMA table_info("${table}")`) |
173 | | - const columns = result.getRowObjectsJson() as Array<{ |
174 | | - cid: number |
175 | | - name: string |
176 | | - type: string |
177 | | - pk: boolean |
178 | | - notnull: boolean |
179 | | - dflt_value: string | null |
180 | | - }> |
181 | | - |
182 | | - const columnInfo = columns.find((col) => col.name === column) |
183 | | - if (!columnInfo) { |
184 | | - throw new Error(`Column '${column}' not found in table '${table}'`) |
185 | | - } |
186 | | - |
187 | | - return columnInfo.type.toUpperCase() |
188 | | - } |
189 | | - |
190 | | - /** |
191 | | - * Checks if a column type is string-like (VARCHAR, TEXT, BLOB) |
192 | | - */ |
193 | | - private isStringLikeType(columnType: string): boolean { |
194 | | - const stringTypes = ['VARCHAR', 'TEXT', 'CHAR', 'BPCHAR'] |
195 | | - |
196 | | - return stringTypes.some((type) => columnType.includes(type)) |
197 | | - } |
198 | | - |
199 | | - /** |
200 | | - * Ensures the column is a string-like type, converting it if necessary |
201 | | - * Returns true if the column was converted, false otherwise |
202 | | - */ |
203 | | - private async ensureColumnIsStringType(table: string, column: string): Promise<boolean> { |
204 | | - const columnType = await this.getColumnType(table, column) |
205 | | - |
206 | | - if (!this.isStringLikeType(columnType)) { |
207 | | - // Convert the column to VARCHAR |
208 | | - await this.changeColumnType(table, column, 'VARCHAR') |
209 | | - return true |
210 | | - } |
211 | | - |
212 | | - return false |
213 | | - } |
214 | | - |
215 | | - /** |
216 | | - * Changes the column type to the specified type |
217 | | - */ |
218 | | - private async changeColumnType(table: string, column: string, newType: string): Promise<void> { |
219 | | - await this.db.run(`ALTER TABLE "${table}" ALTER "${column}" TYPE ${newType}`) |
| 126 | + return this.getCount(query, params) |
220 | 127 | } |
221 | 128 | } |
0 commit comments