From f9f31c7bd361fe9f664287c427fdf66460694b14 Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 30 Sep 2025 17:40:21 +0900 Subject: [PATCH] =?UTF-8?q?phase=202=20=EB=B3=80=ED=99=98=EA=B3=84?= =?UTF-8?q?=ED=9A=8D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md | 428 +++++++++++ PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md | 708 +++++++++++++++++++ PHASE2.4_DYNAMIC_FORM_MIGRATION.md | 218 ++++++ PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md | 99 +++ PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md | 205 ++++++ PHASE2.7_DDL_EXECUTION_MIGRATION.md | 175 +++++ PRISMA_TO_RAW_QUERY_MIGRATION_PLAN.md | 46 +- 7 files changed, 1869 insertions(+), 10 deletions(-) create mode 100644 PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md create mode 100644 PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md create mode 100644 PHASE2.4_DYNAMIC_FORM_MIGRATION.md create mode 100644 PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md create mode 100644 PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md create mode 100644 PHASE2.7_DDL_EXECUTION_MIGRATION.md diff --git a/PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md b/PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md new file mode 100644 index 00000000..c8735763 --- /dev/null +++ b/PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md @@ -0,0 +1,428 @@ +# ๐Ÿ—‚๏ธ Phase 2.2: TableManagementService Raw Query ์ „ํ™˜ ๊ณ„ํš + +## ๐Ÿ“‹ ๊ฐœ์š” + +TableManagementService๋Š” **33๊ฐœ์˜ Prisma ํ˜ธ์ถœ**์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„(์•ฝ 26๊ฐœ)์€ `$queryRaw`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด SQL์€ ์ด๋ฏธ ์ž‘์„ฑ๋˜์–ด ์žˆ์ง€๋งŒ, **Prisma ํด๋ผ์ด์–ธํŠธ๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜๋ ค๋ฉด 33๊ฐœ ๋ชจ๋‘๋ฅผ `db.ts`์˜ `query` ํ•จ์ˆ˜๋กœ ๊ต์ฒด**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +### ๐Ÿ“Š ๊ธฐ๋ณธ ์ •๋ณด + +| ํ•ญ๋ชฉ | ๋‚ด์šฉ | +| --------------- | ----------------------------------------------------- | +| ํŒŒ์ผ ์œ„์น˜ | `backend-node/src/services/tableManagementService.ts` | +| ํŒŒ์ผ ํฌ๊ธฐ | 3,178 ๋ผ์ธ | +| Prisma ํ˜ธ์ถœ | 33๊ฐœ ($queryRaw: 26๊ฐœ, ORM: 7๊ฐœ) | +| **ํ˜„์žฌ ์ง„ํ–‰๋ฅ ** | **0/33 (0%)** โณ **์ „ํ™˜ ํ•„์š”** | +| **์ „ํ™˜ ํ•„์š”** | **33๊ฐœ ๋ชจ๋‘ ์ „ํ™˜ ํ•„์š”** (SQL์€ ์ด๋ฏธ ์ž‘์„ฑ๋˜์–ด ์žˆ์Œ) | +| ๋ณต์žก๋„ | ์ค‘๊ฐ„ (SQL ์ž‘์„ฑ์€ ์™„๋ฃŒ, `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด๋งŒ ํ•„์š”) | +| ์šฐ์„ ์ˆœ์œ„ | ๐ŸŸก ์ค‘๊ฐ„ (Phase 2.2) | + +### ๐ŸŽฏ ์ „ํ™˜ ๋ชฉํ‘œ + +- โœ… **33๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ `db.ts`์˜ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด** + - 26๊ฐœ `$queryRaw` โ†’ `query()` ๋˜๋Š” `queryOne()` + - 7๊ฐœ ORM ๋ฉ”์„œ๋“œ โ†’ `query()` (SQL ์ƒˆ๋กœ ์ž‘์„ฑ) + - 1๊ฐœ `$transaction` โ†’ `transaction()` +- โœ… ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ์ •์ƒ ๋™์ž‘ ํ™•์ธ +- โœ… ๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ +- โœ… **Prisma import ์™„์ „ ์ œ๊ฑฐ** + +--- + +## ๐Ÿ” Prisma ์‚ฌ์šฉ ํ˜„ํ™ฉ ๋ถ„์„ + +### 1. `$queryRaw` / `$queryRawUnsafe` ์‚ฌ์šฉ (26๊ฐœ) + +**ํ˜„์žฌ ์ƒํƒœ**: SQL์€ ์ด๋ฏธ ์ž‘์„ฑ๋˜์–ด ์žˆ์Œ โœ… +**์ „ํ™˜ ์ž‘์—…**: `prisma.$queryRaw` โ†’ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด๋งŒ ํ•˜๋ฉด ๋จ + +```typescript +// ๊ธฐ์กด +await prisma.$queryRaw`SELECT ...`; +await prisma.$queryRawUnsafe(sqlString, ...params); + +// ์ „ํ™˜ ํ›„ +import { query } from "../database/db"; +await query(`SELECT ...`); +await query(sqlString, params); +``` + +### 2. ORM ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ (7๊ฐœ) + +**ํ˜„์žฌ ์ƒํƒœ**: Prisma ORM ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ +**์ „ํ™˜ ์ž‘์—…**: SQL ์ž‘์„ฑ ํ•„์š” + +#### 1. table_labels ๊ด€๋ฆฌ (2๊ฐœ) + +```typescript +// Line 254: ํ…Œ์ด๋ธ” ๋ผ๋ฒจ UPSERT +await prisma.table_labels.upsert({ + where: { table_name: tableName }, + update: {}, + create: { table_name, table_label, description } +}); + +// Line 437: ํ…Œ์ด๋ธ” ๋ผ๋ฒจ ์กฐํšŒ +await prisma.table_labels.findUnique({ + where: { table_name: tableName }, + select: { table_name, table_label, description, ... } +}); +``` + +#### 2. column_labels ๊ด€๋ฆฌ (5๊ฐœ) + +```typescript +// Line 323: ์ปฌ๋Ÿผ ๋ผ๋ฒจ UPSERT +await prisma.column_labels.upsert({ + where: { + table_name_column_name: { + table_name: tableName, + column_name: columnName + } + }, + update: { column_label, input_type, ... }, + create: { table_name, column_name, ... } +}); + +// Line 481: ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์กฐํšŒ +await prisma.column_labels.findUnique({ + where: { + table_name_column_name: { + table_name: tableName, + column_name: columnName + } + }, + select: { id, table_name, column_name, ... } +}); + +// Line 567: ์ปฌ๋Ÿผ ์กด์žฌ ํ™•์ธ +await prisma.column_labels.findFirst({ + where: { table_name, column_name } +}); + +// Line 586: ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์—…๋ฐ์ดํŠธ +await prisma.column_labels.update({ + where: { id: existingColumn.id }, + data: { web_type, detail_settings, ... } +}); + +// Line 610: ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์ƒ์„ฑ +await prisma.column_labels.create({ + data: { table_name, column_name, web_type, ... } +}); + +// Line 1003: ํŒŒ์ผ ํƒ€์ž… ์ปฌ๋Ÿผ ์กฐํšŒ +await prisma.column_labels.findMany({ + where: { table_name, web_type: 'file' }, + select: { column_name } +}); + +// Line 1382: ์ปฌ๋Ÿผ ์›นํƒ€์ž… ์ •๋ณด ์กฐํšŒ +await prisma.column_labels.findFirst({ + where: { table_name, column_name }, + select: { web_type, code_category, ... } +}); + +// Line 2690: ์ปฌ๋Ÿผ ๋ผ๋ฒจ UPSERT (๋ณต์ œ) +await prisma.column_labels.upsert({ + where: { + table_name_column_name: { table_name, column_name } + }, + update: { column_label, web_type, ... }, + create: { table_name, column_name, ... } +}); +``` + +#### 3. attach_file_info ๊ด€๋ฆฌ (2๊ฐœ) + +```typescript +// Line 914: ํŒŒ์ผ ์ •๋ณด ์กฐํšŒ +await prisma.attach_file_info.findMany({ + where: { target_objid, doc_type, status: 'ACTIVE' }, + select: { objid, real_file_name, file_size, ... }, + orderBy: { regdate: 'desc' } +}); + +// Line 959: ํŒŒ์ผ ๊ฒฝ๋กœ๋กœ ํŒŒ์ผ ์ •๋ณด ์กฐํšŒ +await prisma.attach_file_info.findFirst({ + where: { file_path, status: 'ACTIVE' }, + select: { objid, real_file_name, ... } +}); +``` + +#### 4. ํŠธ๋žœ์žญ์…˜ (1๊ฐœ) + +```typescript +// Line 391: ์ „์ฒด ์ปฌ๋Ÿผ ์„ค์ • ์ผ๊ด„ ์—…๋ฐ์ดํŠธ +await prisma.$transaction(async (tx) => { + await this.insertTableIfNotExists(tableName); + for (const columnSetting of columnSettings) { + await this.updateColumnSettings(tableName, columnName, columnSetting); + } +}); +``` + +--- + +## ๐Ÿ“ ์ „ํ™˜ ์˜ˆ์‹œ + +### ์˜ˆ์‹œ 1: table_labels UPSERT ์ „ํ™˜ + +**๊ธฐ์กด Prisma ์ฝ”๋“œ:** + +```typescript +await prisma.table_labels.upsert({ + where: { table_name: tableName }, + update: {}, + create: { + table_name: tableName, + table_label: tableName, + description: "", + }, +}); +``` + +**์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** + +```typescript +import { query } from "../database/db"; + +await query( + `INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) + VALUES ($1, $2, $3, NOW(), NOW()) + ON CONFLICT (table_name) DO NOTHING`, + [tableName, tableName, ""] +); +``` + +### ์˜ˆ์‹œ 2: column_labels UPSERT ์ „ํ™˜ + +**๊ธฐ์กด Prisma ์ฝ”๋“œ:** + +```typescript +await prisma.column_labels.upsert({ + where: { + table_name_column_name: { + table_name: tableName, + column_name: columnName, + }, + }, + update: { + column_label: settings.columnLabel, + input_type: settings.inputType, + detail_settings: settings.detailSettings, + updated_date: new Date(), + }, + create: { + table_name: tableName, + column_name: columnName, + column_label: settings.columnLabel, + input_type: settings.inputType, + detail_settings: settings.detailSettings, + }, +}); +``` + +**์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** + +```typescript +await query( + `INSERT INTO column_labels ( + table_name, column_name, column_label, input_type, detail_settings, + code_category, code_value, reference_table, reference_column, + display_column, display_order, is_visible, created_date, updated_date + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NOW(), NOW()) + ON CONFLICT (table_name, column_name) + DO UPDATE SET + column_label = EXCLUDED.column_label, + input_type = EXCLUDED.input_type, + detail_settings = EXCLUDED.detail_settings, + code_category = EXCLUDED.code_category, + code_value = EXCLUDED.code_value, + reference_table = EXCLUDED.reference_table, + reference_column = EXCLUDED.reference_column, + display_column = EXCLUDED.display_column, + display_order = EXCLUDED.display_order, + is_visible = EXCLUDED.is_visible, + updated_date = NOW()`, + [ + tableName, + columnName, + settings.columnLabel, + settings.inputType, + settings.detailSettings, + settings.codeCategory, + settings.codeValue, + settings.referenceTable, + settings.referenceColumn, + settings.displayColumn, + settings.displayOrder || 0, + settings.isVisible !== undefined ? settings.isVisible : true, + ] +); +``` + +### ์˜ˆ์‹œ 3: ํŠธ๋žœ์žญ์…˜ ์ „ํ™˜ + +**๊ธฐ์กด Prisma ์ฝ”๋“œ:** + +```typescript +await prisma.$transaction(async (tx) => { + await this.insertTableIfNotExists(tableName); + for (const columnSetting of columnSettings) { + await this.updateColumnSettings(tableName, columnName, columnSetting); + } +}); +``` + +**์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** + +```typescript +import { transaction } from "../database/db"; + +await transaction(async (client) => { + // ํ…Œ์ด๋ธ” ๋ผ๋ฒจ ์ž๋™ ์ถ”๊ฐ€ + await client.query( + `INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) + VALUES ($1, $2, $3, NOW(), NOW()) + ON CONFLICT (table_name) DO NOTHING`, + [tableName, tableName, ""] + ); + + // ๊ฐ ์ปฌ๋Ÿผ ์„ค์ • ์—…๋ฐ์ดํŠธ + for (const columnSetting of columnSettings) { + const columnName = columnSetting.columnName; + if (columnName) { + await client.query( + `INSERT INTO column_labels (...) + VALUES (...) + ON CONFLICT (table_name, column_name) DO UPDATE SET ...`, + [...] + ); + } + } +}); +``` + +--- + +## ๐Ÿงช ํ…Œ์ŠคํŠธ ๊ณ„ํš + +### ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (10๊ฐœ) + +```typescript +describe("TableManagementService Raw Query ์ „ํ™˜ ํ…Œ์ŠคํŠธ", () => { + describe("insertTableIfNotExists", () => { + test("ํ…Œ์ด๋ธ” ๋ผ๋ฒจ UPSERT ์„ฑ๊ณต", async () => { ... }); + test("์ค‘๋ณต ํ…Œ์ด๋ธ” ์ฒ˜๋ฆฌ", async () => { ... }); + }); + + describe("updateColumnSettings", () => { + test("์ปฌ๋Ÿผ ์„ค์ • UPSERT ์„ฑ๊ณต", async () => { ... }); + test("๊ธฐ์กด ์ปฌ๋Ÿผ ์—…๋ฐ์ดํŠธ", async () => { ... }); + }); + + describe("getTableLabels", () => { + test("ํ…Œ์ด๋ธ” ๋ผ๋ฒจ ์กฐํšŒ ์„ฑ๊ณต", async () => { ... }); + }); + + describe("getColumnLabels", () => { + test("์ปฌ๋Ÿผ ๋ผ๋ฒจ ์กฐํšŒ ์„ฑ๊ณต", async () => { ... }); + }); + + describe("updateAllColumnSettings", () => { + test("์ผ๊ด„ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต (ํŠธ๋žœ์žญ์…˜)", async () => { ... }); + test("๋ถ€๋ถ„ ์‹คํŒจ ์‹œ ๋กค๋ฐฑ", async () => { ... }); + }); + + describe("getFileInfoByColumnAndTarget", () => { + test("ํŒŒ์ผ ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต", async () => { ... }); + }); +}); +``` + +### ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (5๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค) + +```typescript +describe("ํ…Œ์ด๋ธ” ๊ด€๋ฆฌ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ", () => { + test("ํ…Œ์ด๋ธ” ๋ผ๋ฒจ ์ƒ์„ฑ โ†’ ์กฐํšŒ โ†’ ์ˆ˜์ •", async () => { ... }); + test("์ปฌ๋Ÿผ ๋ผ๋ฒจ ์ƒ์„ฑ โ†’ ์กฐํšŒ โ†’ ์ˆ˜์ •", async () => { ... }); + test("์ปฌ๋Ÿผ ์ผ๊ด„ ์„ค์ • ์—…๋ฐ์ดํŠธ", async () => { ... }); + test("ํŒŒ์ผ ์ •๋ณด ์กฐํšŒ ๋ฐ ๋ณด๊ฐ•", async () => { ... }); + test("ํŠธ๋žœ์žญ์…˜ ๋กค๋ฐฑ ํ…Œ์ŠคํŠธ", async () => { ... }); +}); +``` + +--- + +## ๐Ÿ“‹ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +### 1๋‹จ๊ณ„: table_labels ์ „ํ™˜ (2๊ฐœ ํ•จ์ˆ˜) โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] `insertTableIfNotExists()` - UPSERT +- [ ] `getTableLabels()` - ์กฐํšŒ + +### 2๋‹จ๊ณ„: column_labels ์ „ํ™˜ (5๊ฐœ ํ•จ์ˆ˜) โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] `updateColumnSettings()` - UPSERT +- [ ] `getColumnLabels()` - ์กฐํšŒ +- [ ] `updateColumnWebType()` - findFirst + update/create +- [ ] `getColumnWebTypeInfo()` - findFirst +- [ ] `updateColumnLabel()` - UPSERT (๋ณต์ œ) + +### 3๋‹จ๊ณ„: attach_file_info ์ „ํ™˜ (2๊ฐœ ํ•จ์ˆ˜) โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] `getFileInfoByColumnAndTarget()` - findMany +- [ ] `getFileInfoByPath()` - findFirst + +### 4๋‹จ๊ณ„: ํŠธ๋žœ์žญ์…˜ ์ „ํ™˜ (1๊ฐœ ํ•จ์ˆ˜) โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] `updateAllColumnSettings()` - ํŠธ๋žœ์žญ์…˜ + +### 5๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ & ๊ฒ€์ฆ โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (10๊ฐœ) +- [ ] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (5๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค) +- [ ] Prisma import ์™„์ „ ์ œ๊ฑฐ ํ™•์ธ +- [ ] ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ + +--- + +## ๐ŸŽฏ ์™„๋ฃŒ ๊ธฐ์ค€ + +- [ ] **33๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ Raw Query๋กœ ์ „ํ™˜ ์™„๋ฃŒ** + - [ ] 26๊ฐœ `$queryRaw` โ†’ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด + - [ ] 7๊ฐœ ORM ๋ฉ”์„œ๋“œ โ†’ `query()` ํ•จ์ˆ˜๋กœ ์ „ํ™˜ (SQL ์ž‘์„ฑ) +- [ ] **๋ชจ๋“  TypeScript ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ** +- [ ] **ํŠธ๋žœ์žญ์…˜ ์ •์ƒ ๋™์ž‘ ํ™•์ธ** +- [ ] **์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ๋กค๋ฐฑ ์ •์ƒ ๋™์ž‘** +- [ ] **๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (10๊ฐœ)** +- [ ] **๋ชจ๋“  ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ (5๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค)** +- [ ] **`import prisma` ์™„์ „ ์ œ๊ฑฐ ๋ฐ `import { query, transaction } from "../database/db"` ์‚ฌ์šฉ** +- [ ] **์„ฑ๋Šฅ ์ €ํ•˜ ์—†์Œ (๊ธฐ์กด ๋Œ€๋น„ ยฑ10% ์ด๋‚ด)** + +--- + +## ๐Ÿ’ก ํŠน์ด์‚ฌํ•ญ + +### SQL์€ ์ด๋ฏธ ๋Œ€๋ถ€๋ถ„ ์ž‘์„ฑ๋˜์–ด ์žˆ์Œ + +์ด ์„œ๋น„์Šค๋Š” ์ด๋ฏธ 79%๊ฐ€ `$queryRaw`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด, **SQL ์ž‘์„ฑ์€ ์™„๋ฃŒ**๋˜์—ˆ์Šต๋‹ˆ๋‹ค: + +- โœ… `information_schema` ์กฐํšŒ: SQL ์ž‘์„ฑ ์™„๋ฃŒ (`$queryRaw` ์‚ฌ์šฉ ์ค‘) +- โœ… ๋™์  ํ…Œ์ด๋ธ” ์ฟผ๋ฆฌ: SQL ์ž‘์„ฑ ์™„๋ฃŒ (`$queryRawUnsafe` ์‚ฌ์šฉ ์ค‘) +- โœ… DDL ์‹คํ–‰: SQL ์ž‘์„ฑ ์™„๋ฃŒ (`$executeRaw` ์‚ฌ์šฉ ์ค‘) +- โณ **์ „ํ™˜ ์ž‘์—…**: `prisma.$queryRaw` โ†’ `query()` ํ•จ์ˆ˜๋กœ **๋‹จ์ˆœ ๊ต์ฒด๋งŒ ํ•„์š”** +- โณ CRUD ์ž‘์—…: 7๊ฐœ๋งŒ SQL ์ƒˆ๋กœ ์ž‘์„ฑ ํ•„์š” + +### UPSERT ํŒจํ„ด ์ค‘์š” + +๋Œ€๋ถ€๋ถ„์˜ ์ „ํ™˜์ด UPSERT ํŒจํ„ด์ด๋ฏ€๋กœ PostgreSQL์˜ `ON CONFLICT` ๊ตฌ๋ฌธ์„ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค. + +--- + +**์ž‘์„ฑ์ผ**: 2025-09-30 +**์˜ˆ์ƒ ์†Œ์š” ์‹œ๊ฐ„**: 1-1.5์ผ (SQL์€ 79% ์ž‘์„ฑ ์™„๋ฃŒ, ํ•จ์ˆ˜ ๊ต์ฒด ์ž‘์—… ํ•„์š”) +**๋‹ด๋‹น์ž**: ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœํŒ€ +**์šฐ์„ ์ˆœ์œ„**: ๐ŸŸก ์ค‘๊ฐ„ (Phase 2.2) +**์ƒํƒœ**: โณ **์ง„ํ–‰ ์˜ˆ์ •** +**ํŠน์ด์‚ฌํ•ญ**: SQL์€ ๋Œ€๋ถ€๋ถ„ ์ž‘์„ฑ๋˜์–ด ์žˆ์–ด `prisma.$queryRaw` โ†’ `query()` ๋‹จ์ˆœ ๊ต์ฒด ์ž‘์—…์ด ์ฃผ์š” ์ž‘์—… diff --git a/PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md b/PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md new file mode 100644 index 00000000..e902eabc --- /dev/null +++ b/PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md @@ -0,0 +1,708 @@ +# ๐Ÿ“Š Phase 2.3: DataflowService Raw Query ์ „ํ™˜ ๊ณ„ํš + +## ๐Ÿ“‹ ๊ฐœ์š” + +DataflowService๋Š” **31๊ฐœ์˜ Prisma ํ˜ธ์ถœ**์ด ์žˆ๋Š” ํ•ต์‹ฌ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ํ…Œ์ด๋ธ” ๊ฐ„ ๊ด€๊ณ„ ๊ด€๋ฆฌ, ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ๋‹ค์ด์–ด๊ทธ๋žจ, ๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ๋ธŒ๋ฆฌ์ง€ ๋“ฑ ๋ณต์žกํ•œ ๊ธฐ๋Šฅ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. + +### ๐Ÿ“Š ๊ธฐ๋ณธ ์ •๋ณด + +| ํ•ญ๋ชฉ | ๋‚ด์šฉ | +| --------------- | ---------------------------------------------- | +| ํŒŒ์ผ ์œ„์น˜ | `backend-node/src/services/dataflowService.ts` | +| ํŒŒ์ผ ํฌ๊ธฐ | 1,100+ ๋ผ์ธ | +| Prisma ํ˜ธ์ถœ | 31๊ฐœ | +| **ํ˜„์žฌ ์ง„ํ–‰๋ฅ ** | **0/31 (0%)** โณ **์ง„ํ–‰ ์˜ˆ์ •** | +| ๋ณต์žก๋„ | ๋งค์šฐ ๋†’์Œ (ํŠธ๋žœ์žญ์…˜ + ๋ณต์žกํ•œ ๊ด€๊ณ„ ๊ด€๋ฆฌ) | +| ์šฐ์„ ์ˆœ์œ„ | ๐Ÿ”ด ์ตœ์šฐ์„  (Phase 2.3) | + +### ๐ŸŽฏ ์ „ํ™˜ ๋ชฉํ‘œ + +- โœ… 31๊ฐœ Prisma ํ˜ธ์ถœ์„ ๋ชจ๋‘ Raw Query๋กœ ์ „ํ™˜ +- โœ… ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ์ •์ƒ ๋™์ž‘ ํ™•์ธ +- โœ… ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ๋กค๋ฐฑ ์ •์ƒ ๋™์ž‘ +- โœ… ๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (20๊ฐœ ์ด์ƒ) +- โœ… ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ +- โœ… Prisma import ์™„์ „ ์ œ๊ฑฐ + +--- + +## ๐Ÿ” Prisma ์‚ฌ์šฉ ํ˜„ํ™ฉ ๋ถ„์„ + +### 1. ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ๊ด€๋ฆฌ (Table Relationships) - 22๊ฐœ + +#### 1.1 ๊ด€๊ณ„ ์ƒ์„ฑ (3๊ฐœ) + +```typescript +// Line 48: ์ตœ๋Œ€ diagram_id ์กฐํšŒ +await prisma.table_relationships.findFirst({ + where: { company_code }, + orderBy: { diagram_id: 'desc' } +}); + +// Line 64: ์ค‘๋ณต ๊ด€๊ณ„ ํ™•์ธ +await prisma.table_relationships.findFirst({ + where: { diagram_id, source_table, target_table, relationship_type } +}); + +// Line 83: ์ƒˆ ๊ด€๊ณ„ ์ƒ์„ฑ +await prisma.table_relationships.create({ + data: { diagram_id, source_table, target_table, ... } +}); +``` + +#### 1.2 ๊ด€๊ณ„ ์กฐํšŒ (6๊ฐœ) + +```typescript +// Line 128: ๊ด€๊ณ„ ๋ชฉ๋ก ์กฐํšŒ +await prisma.table_relationships.findMany({ + where: whereCondition, + orderBy: { created_at: 'desc' } +}); + +// Line 164: ๋‹จ์ผ ๊ด€๊ณ„ ์กฐํšŒ +await prisma.table_relationships.findFirst({ + where: whereCondition +}); + +// Line 287: ํšŒ์‚ฌ๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ +await prisma.table_relationships.findMany({ + where: { company_code, is_active: 'Y' }, + orderBy: { diagram_id: 'asc' } +}); + +// Line 326: ํ…Œ์ด๋ธ”๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ +await prisma.table_relationships.findMany({ + where: whereCondition, + orderBy: { relationship_type: 'asc' } +}); + +// Line 784: diagram_id๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ +await prisma.table_relationships.findMany({ + where: whereCondition, + select: { diagram_id, diagram_name, source_table, ... } +}); + +// Line 883: ํšŒ์‚ฌ ์ฝ”๋“œ๋กœ ์ „์ฒด ์กฐํšŒ +await prisma.table_relationships.findMany({ + where: { company_code, is_active: 'Y' } +}); +``` + +#### 1.3 ํ†ต๊ณ„ ์กฐํšŒ (3๊ฐœ) + +```typescript +// Line 362: ์ „์ฒด ๊ด€๊ณ„ ์ˆ˜ +await prisma.table_relationships.count({ + where: whereCondition, +}); + +// Line 367: ๊ด€๊ณ„ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ +await prisma.table_relationships.groupBy({ + by: ["relationship_type"], + where: whereCondition, + _count: { relationship_id: true }, +}); + +// Line 376: ์—ฐ๊ฒฐ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ +await prisma.table_relationships.groupBy({ + by: ["connection_type"], + where: whereCondition, + _count: { relationship_id: true }, +}); +``` + +#### 1.4 ๊ด€๊ณ„ ์ˆ˜์ •/์‚ญ์ œ (5๊ฐœ) + +```typescript +// Line 209: ๊ด€๊ณ„ ์ˆ˜์ • +await prisma.table_relationships.update({ + where: { relationship_id }, + data: { source_table, target_table, ... } +}); + +// Line 248: ์†Œํ”„ํŠธ ์‚ญ์ œ +await prisma.table_relationships.update({ + where: { relationship_id }, + data: { is_active: 'N', updated_at: new Date() } +}); + +// Line 936: ์ค‘๋ณต diagram_name ํ™•์ธ +await prisma.table_relationships.findFirst({ + where: { company_code, diagram_name, is_active: 'Y' } +}); + +// Line 953: ์ตœ๋Œ€ diagram_id ์กฐํšŒ (๋ณต์‚ฌ์šฉ) +await prisma.table_relationships.findFirst({ + where: { company_code }, + orderBy: { diagram_id: 'desc' } +}); + +// Line 1015: ๊ด€๊ณ„๋„ ์™„์ „ ์‚ญ์ œ +await prisma.table_relationships.deleteMany({ + where: { company_code, diagram_id, is_active: 'Y' } +}); +``` + +#### 1.5 ๋ณต์žกํ•œ ์กฐํšŒ (5๊ฐœ) + +```typescript +// Line 919: ์›๋ณธ ๊ด€๊ณ„๋„ ์กฐํšŒ +await prisma.table_relationships.findMany({ + where: { company_code, diagram_id: sourceDiagramId, is_active: "Y" }, +}); + +// Line 1046: diagram_id๋กœ ๋ชจ๋“  ๊ด€๊ณ„ ์กฐํšŒ +await prisma.table_relationships.findMany({ + where: { diagram_id, is_active: "Y" }, + orderBy: { created_at: "asc" }, +}); + +// Line 1085: ํŠน์ • relationship_id์˜ diagram_id ์ฐพ๊ธฐ +await prisma.table_relationships.findFirst({ + where: { relationship_id, company_code }, +}); +``` + +### 2. ๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ๋ธŒ๋ฆฌ์ง€ (Data Relationship Bridge) - 8๊ฐœ + +#### 2.1 ๋ธŒ๋ฆฌ์ง€ ์ƒ์„ฑ/์ˆ˜์ • (4๊ฐœ) + +```typescript +// Line 425: ๋ธŒ๋ฆฌ์ง€ ์ƒ์„ฑ +await prisma.data_relationship_bridge.create({ + data: { + relationship_id, + source_record_id, + target_record_id, + ... + } +}); + +// Line 554: ๋ธŒ๋ฆฌ์ง€ ์ˆ˜์ • +await prisma.data_relationship_bridge.update({ + where: whereCondition, + data: { target_record_id, ... } +}); + +// Line 595: ๋ธŒ๋ฆฌ์ง€ ์†Œํ”„ํŠธ ์‚ญ์ œ +await prisma.data_relationship_bridge.update({ + where: whereCondition, + data: { is_active: 'N', updated_at: new Date() } +}); + +// Line 637: ๋ธŒ๋ฆฌ์ง€ ์ผ๊ด„ ์‚ญ์ œ +await prisma.data_relationship_bridge.updateMany({ + where: whereCondition, + data: { is_active: 'N', updated_at: new Date() } +}); +``` + +#### 2.2 ๋ธŒ๋ฆฌ์ง€ ์กฐํšŒ (4๊ฐœ) + +```typescript +// Line 471: relationship_id๋กœ ๋ธŒ๋ฆฌ์ง€ ์กฐํšŒ +await prisma.data_relationship_bridge.findMany({ + where: whereCondition, + orderBy: { created_at: "desc" }, +}); + +// Line 512: ๋ ˆ์ฝ”๋“œ๋ณ„ ๋ธŒ๋ฆฌ์ง€ ์กฐํšŒ +await prisma.data_relationship_bridge.findMany({ + where: whereCondition, + orderBy: { created_at: "desc" }, +}); +``` + +### 3. Raw Query ์‚ฌ์šฉ (์ด๋ฏธ ์žˆ์Œ) - 1๊ฐœ + +```typescript +// Line 673: ํ…Œ์ด๋ธ” ์กด์žฌ ํ™•์ธ +await prisma.$queryRaw` + SELECT table_name + FROM information_schema.tables + WHERE table_schema = 'public' + AND table_name = ${tableName} +`; +``` + +### 4. ํŠธ๋žœ์žญ์…˜ ์‚ฌ์šฉ - 1๊ฐœ + +```typescript +// Line 968: ๊ด€๊ณ„๋„ ๋ณต์‚ฌ ํŠธ๋žœ์žญ์…˜ +await prisma.$transaction( + originalRelationships.map((rel) => + prisma.table_relationships.create({ + data: { + diagram_id: newDiagramId, + company_code: companyCode, + source_table: rel.source_table, + target_table: rel.target_table, + ... + } + }) + ) +); +``` + +--- + +## ๐Ÿ› ๏ธ ์ „ํ™˜ ์ „๋žต + +### ์ „๋žต 1: ๋‹จ๊ณ„์  ์ „ํ™˜ + +1. **1๋‹จ๊ณ„**: ๋‹จ์ˆœ CRUD ์ „ํ™˜ (findFirst, findMany, create, update, delete) +2. **2๋‹จ๊ณ„**: ๋ณต์žกํ•œ ์กฐํšŒ ์ „ํ™˜ (groupBy, count, ์กฐ๊ฑด๋ถ€ ์กฐํšŒ) +3. **3๋‹จ๊ณ„**: ํŠธ๋žœ์žญ์…˜ ์ „ํ™˜ +4. **4๋‹จ๊ณ„**: Raw Query ๊ฐœ์„  + +### ์ „๋žต 2: ํ•จ์ˆ˜๋ณ„ ์ „ํ™˜ ์šฐ์„ ์ˆœ์œ„ + +#### ๐Ÿ”ด ์ตœ์šฐ์„  (๊ธฐ๋ณธ CRUD) + +- `createRelationship()` - Line 83 +- `getRelationships()` - Line 128 +- `getRelationshipById()` - Line 164 +- `updateRelationship()` - Line 209 +- `deleteRelationship()` - Line 248 + +#### ๐ŸŸก 2์ˆœ์œ„ (๋ธŒ๋ฆฌ์ง€ ๊ด€๋ฆฌ) + +- `createDataLink()` - Line 425 +- `getLinkedData()` - Line 471 +- `getLinkedDataByRecord()` - Line 512 +- `updateDataLink()` - Line 554 +- `deleteDataLink()` - Line 595 + +#### ๐ŸŸข 3์ˆœ์œ„ (ํ†ต๊ณ„ & ์กฐํšŒ) + +- `getRelationshipStats()` - Line 362-376 +- `getAllRelationshipsByCompany()` - Line 287 +- `getRelationshipsByTable()` - Line 326 +- `getDiagrams()` - Line 784 + +#### ๐Ÿ”ต 4์ˆœ์œ„ (๋ณต์žกํ•œ ๊ธฐ๋Šฅ) + +- `copyDiagram()` - Line 968 (ํŠธ๋žœ์žญ์…˜) +- `deleteDiagram()` - Line 1015 +- `getRelationshipsForDiagram()` - Line 1046 + +--- + +## ๐Ÿ“ ์ „ํ™˜ ์˜ˆ์‹œ + +### ์˜ˆ์‹œ 1: createRelationship() ์ „ํ™˜ + +**๊ธฐ์กด Prisma ์ฝ”๋“œ:** + +```typescript +// Line 48: ์ตœ๋Œ€ diagram_id ์กฐํšŒ +const maxDiagramId = await prisma.table_relationships.findFirst({ + where: { company_code: data.companyCode }, + orderBy: { diagram_id: 'desc' } +}); + +// Line 64: ์ค‘๋ณต ๊ด€๊ณ„ ํ™•์ธ +const existingRelationship = await prisma.table_relationships.findFirst({ + where: { + diagram_id: diagramId, + source_table: data.sourceTable, + target_table: data.targetTable, + relationship_type: data.relationshipType + } +}); + +// Line 83: ์ƒˆ ๊ด€๊ณ„ ์ƒ์„ฑ +const relationship = await prisma.table_relationships.create({ + data: { + diagram_id: diagramId, + company_code: data.companyCode, + diagram_name: data.diagramName, + source_table: data.sourceTable, + target_table: data.targetTable, + relationship_type: data.relationshipType, + ... + } +}); +``` + +**์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** + +```typescript +import { query } from "../database/db"; + +// ์ตœ๋Œ€ diagram_id ์กฐํšŒ +const maxDiagramResult = await query<{ diagram_id: number }>( + `SELECT diagram_id FROM table_relationships + WHERE company_code = $1 + ORDER BY diagram_id DESC + LIMIT 1`, + [data.companyCode] +); + +const diagramId = + data.diagramId || + (maxDiagramResult.length > 0 ? maxDiagramResult[0].diagram_id + 1 : 1); + +// ์ค‘๋ณต ๊ด€๊ณ„ ํ™•์ธ +const existingResult = await query<{ relationship_id: number }>( + `SELECT relationship_id FROM table_relationships + WHERE diagram_id = $1 + AND source_table = $2 + AND target_table = $3 + AND relationship_type = $4 + LIMIT 1`, + [diagramId, data.sourceTable, data.targetTable, data.relationshipType] +); + +if (existingResult.length > 0) { + throw new Error("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ด€๊ณ„์ž…๋‹ˆ๋‹ค."); +} + +// ์ƒˆ ๊ด€๊ณ„ ์ƒ์„ฑ +const [relationship] = await query( + `INSERT INTO table_relationships ( + diagram_id, company_code, diagram_name, source_table, target_table, + relationship_type, connection_type, source_column, target_column, + is_active, created_at, updated_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'Y', NOW(), NOW()) + RETURNING *`, + [ + diagramId, + data.companyCode, + data.diagramName, + data.sourceTable, + data.targetTable, + data.relationshipType, + data.connectionType, + data.sourceColumn, + data.targetColumn, + ] +); +``` + +### ์˜ˆ์‹œ 2: getRelationshipStats() ์ „ํ™˜ (ํ†ต๊ณ„ ์กฐํšŒ) + +**๊ธฐ์กด Prisma ์ฝ”๋“œ:** + +```typescript +// Line 362: ์ „์ฒด ๊ด€๊ณ„ ์ˆ˜ +const totalCount = await prisma.table_relationships.count({ + where: whereCondition, +}); + +// Line 367: ๊ด€๊ณ„ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ +const relationshipTypeStats = await prisma.table_relationships.groupBy({ + by: ["relationship_type"], + where: whereCondition, + _count: { relationship_id: true }, +}); + +// Line 376: ์—ฐ๊ฒฐ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ +const connectionTypeStats = await prisma.table_relationships.groupBy({ + by: ["connection_type"], + where: whereCondition, + _count: { relationship_id: true }, +}); +``` + +**์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** + +```typescript +// WHERE ์กฐ๊ฑด ๋™์  ์ƒ์„ฑ +const whereParams: any[] = []; +let whereSQL = ""; +let paramIndex = 1; + +if (companyCode) { + whereSQL += `WHERE company_code = $${paramIndex}`; + whereParams.push(companyCode); + paramIndex++; + + if (isActive !== undefined) { + whereSQL += ` AND is_active = $${paramIndex}`; + whereParams.push(isActive ? "Y" : "N"); + paramIndex++; + } +} + +// ์ „์ฒด ๊ด€๊ณ„ ์ˆ˜ +const [totalResult] = await query<{ count: number }>( + `SELECT COUNT(*) as count + FROM table_relationships ${whereSQL}`, + whereParams +); + +const totalCount = totalResult?.count || 0; + +// ๊ด€๊ณ„ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ +const relationshipTypeStats = await query<{ + relationship_type: string; + count: number; +}>( + `SELECT relationship_type, COUNT(*) as count + FROM table_relationships ${whereSQL} + GROUP BY relationship_type + ORDER BY count DESC`, + whereParams +); + +// ์—ฐ๊ฒฐ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ +const connectionTypeStats = await query<{ + connection_type: string; + count: number; +}>( + `SELECT connection_type, COUNT(*) as count + FROM table_relationships ${whereSQL} + GROUP BY connection_type + ORDER BY count DESC`, + whereParams +); +``` + +### ์˜ˆ์‹œ 3: copyDiagram() ํŠธ๋žœ์žญ์…˜ ์ „ํ™˜ + +**๊ธฐ์กด Prisma ์ฝ”๋“œ:** + +```typescript +// Line 968: ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ชจ๋“  ๊ด€๊ณ„ ๋ณต์‚ฌ +const copiedRelationships = await prisma.$transaction( + originalRelationships.map((rel) => + prisma.table_relationships.create({ + data: { + diagram_id: newDiagramId, + company_code: companyCode, + diagram_name: newDiagramName, + source_table: rel.source_table, + target_table: rel.target_table, + ... + } + }) + ) +); +``` + +**์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** + +```typescript +import { transaction } from "../database/db"; + +const copiedRelationships = await transaction(async (client) => { + const results: TableRelationship[] = []; + + for (const rel of originalRelationships) { + const [copiedRel] = await client.query( + `INSERT INTO table_relationships ( + diagram_id, company_code, diagram_name, source_table, target_table, + relationship_type, connection_type, source_column, target_column, + is_active, created_at, updated_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'Y', NOW(), NOW()) + RETURNING *`, + [ + newDiagramId, + companyCode, + newDiagramName, + rel.source_table, + rel.target_table, + rel.relationship_type, + rel.connection_type, + rel.source_column, + rel.target_column, + ] + ); + + results.push(copiedRel); + } + + return results; +}); +``` + +--- + +## ๐Ÿงช ํ…Œ์ŠคํŠธ ๊ณ„ํš + +### ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (20๊ฐœ ์ด์ƒ) + +```typescript +describe('DataflowService Raw Query ์ „ํ™˜ ํ…Œ์ŠคํŠธ', () => { + describe('createRelationship', () => { + test('๊ด€๊ณ„ ์ƒ์„ฑ ์„ฑ๊ณต', async () => { ... }); + test('์ค‘๋ณต ๊ด€๊ณ„ ์—๋Ÿฌ', async () => { ... }); + test('diagram_id ์ž๋™ ์ƒ์„ฑ', async () => { ... }); + }); + + describe('getRelationships', () => { + test('์ „์ฒด ๊ด€๊ณ„ ์กฐํšŒ ์„ฑ๊ณต', async () => { ... }); + test('ํšŒ์‚ฌ๋ณ„ ํ•„ํ„ฐ๋ง', async () => { ... }); + test('diagram_id๋ณ„ ํ•„ํ„ฐ๋ง', async () => { ... }); + }); + + describe('getRelationshipStats', () => { + test('ํ†ต๊ณ„ ์กฐํšŒ ์„ฑ๊ณต', async () => { ... }); + test('๊ด€๊ณ„ ํƒ€์ž…๋ณ„ ๊ทธ๋ฃนํ™”', async () => { ... }); + test('์—ฐ๊ฒฐ ํƒ€์ž…๋ณ„ ๊ทธ๋ฃนํ™”', async () => { ... }); + }); + + describe('copyDiagram', () => { + test('๊ด€๊ณ„๋„ ๋ณต์‚ฌ ์„ฑ๊ณต (ํŠธ๋žœ์žญ์…˜)', async () => { ... }); + test('diagram_name ์ค‘๋ณต ์—๋Ÿฌ', async () => { ... }); + }); + + describe('createDataLink', () => { + test('๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ์ƒ์„ฑ ์„ฑ๊ณต', async () => { ... }); + test('๋ธŒ๋ฆฌ์ง€ ๋ ˆ์ฝ”๋“œ ์ €์žฅ', async () => { ... }); + }); + + describe('getLinkedData', () => { + test('์—ฐ๊ฒฐ๋œ ๋ฐ์ดํ„ฐ ์กฐํšŒ', async () => { ... }); + test('relationship_id๋ณ„ ํ•„ํ„ฐ๋ง', async () => { ... }); + }); +}); +``` + +### ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (7๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค) + +```typescript +describe('Dataflow ๊ด€๋ฆฌ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ', () => { + test('๊ด€๊ณ„ ์ƒ๋ช…์ฃผ๊ธฐ (์ƒ์„ฑ โ†’ ์กฐํšŒ โ†’ ์ˆ˜์ • โ†’ ์‚ญ์ œ)', async () => { ... }); + test('๊ด€๊ณ„๋„ ๋ณต์‚ฌ ๋ฐ ๊ฒ€์ฆ', async () => { ... }); + test('๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ๋ธŒ๋ฆฌ์ง€ ์ƒ์„ฑ ๋ฐ ์กฐํšŒ', async () => { ... }); + test('ํ†ต๊ณ„ ์ •๋ณด ์กฐํšŒ', async () => { ... }); + test('ํ…Œ์ด๋ธ”๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ', async () => { ... }); + test('diagram_id๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ', async () => { ... }); + test('๊ด€๊ณ„๋„ ์™„์ „ ์‚ญ์ œ', async () => { ... }); +}); +``` + +--- + +## ๐Ÿ“‹ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +### 1๋‹จ๊ณ„: ๊ธฐ๋ณธ CRUD (8๊ฐœ ํ•จ์ˆ˜) โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] `createRelationship()` - ๊ด€๊ณ„ ์ƒ์„ฑ +- [ ] `getRelationships()` - ๊ด€๊ณ„ ๋ชฉ๋ก ์กฐํšŒ +- [ ] `getRelationshipById()` - ๋‹จ์ผ ๊ด€๊ณ„ ์กฐํšŒ +- [ ] `updateRelationship()` - ๊ด€๊ณ„ ์ˆ˜์ • +- [ ] `deleteRelationship()` - ๊ด€๊ณ„ ์‚ญ์ œ (์†Œํ”„ํŠธ) +- [ ] `getAllRelationshipsByCompany()` - ํšŒ์‚ฌ๋ณ„ ์ „์ฒด ์กฐํšŒ +- [ ] `getRelationshipsByTable()` - ํ…Œ์ด๋ธ”๋ณ„ ์กฐํšŒ +- [ ] `getDiagrams()` - diagram_id๋ณ„ ๊ทธ๋ฃน ์กฐํšŒ + +### 2๋‹จ๊ณ„: ๋ธŒ๋ฆฌ์ง€ ๊ด€๋ฆฌ (6๊ฐœ ํ•จ์ˆ˜) โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] `createDataLink()` - ๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ์ƒ์„ฑ +- [ ] `getLinkedData()` - ์—ฐ๊ฒฐ ๋ฐ์ดํ„ฐ ์กฐํšŒ +- [ ] `getLinkedDataByRecord()` - ๋ ˆ์ฝ”๋“œ๋ณ„ ์—ฐ๊ฒฐ ์กฐํšŒ +- [ ] `updateDataLink()` - ์—ฐ๊ฒฐ ์ˆ˜์ • +- [ ] `deleteDataLink()` - ์—ฐ๊ฒฐ ์‚ญ์ œ (์†Œํ”„ํŠธ) +- [ ] `bulkDeleteDataLinks()` - ์—ฐ๊ฒฐ ์ผ๊ด„ ์‚ญ์ œ + +### 3๋‹จ๊ณ„: ํ†ต๊ณ„ & ๋ณต์žกํ•œ ์กฐํšŒ (3๊ฐœ ํ•จ์ˆ˜) โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] `getRelationshipStats()` - ํ†ต๊ณ„ ์กฐํšŒ + - [ ] count ์ฟผ๋ฆฌ ์ „ํ™˜ + - [ ] groupBy ์ฟผ๋ฆฌ ์ „ํ™˜ (๊ด€๊ณ„ ํƒ€์ž…๋ณ„) + - [ ] groupBy ์ฟผ๋ฆฌ ์ „ํ™˜ (์—ฐ๊ฒฐ ํƒ€์ž…๋ณ„) +- [ ] `getRelationshipsForDiagram()` - diagram_id๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ +- [ ] `checkTableExists()` - ํ…Œ์ด๋ธ” ์กด์žฌ ํ™•์ธ (Raw Query ์œ ์ง€) + +### 4๋‹จ๊ณ„: ๋ณต์žกํ•œ ๊ธฐ๋Šฅ (3๊ฐœ ํ•จ์ˆ˜) โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] `copyDiagram()` - ๊ด€๊ณ„๋„ ๋ณต์‚ฌ (ํŠธ๋žœ์žญ์…˜) +- [ ] `deleteDiagram()` - ๊ด€๊ณ„๋„ ์™„์ „ ์‚ญ์ œ +- [ ] `getRelationshipIdByDiagramId()` - ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ฉ”์„œ๋“œ + +### 5๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ & ๊ฒ€์ฆ โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (20๊ฐœ ์ด์ƒ) + - createRelationship, updateRelationship, deleteRelationship + - getRelationships, getRelationshipById + - createDataLink, getLinkedData + - getRelationshipStats + - copyDiagram +- [ ] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (7๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค) + - ๊ด€๊ณ„ ์ƒ๋ช…์ฃผ๊ธฐ ํ…Œ์ŠคํŠธ + - ๊ด€๊ณ„๋„ ๋ณต์‚ฌ ํ…Œ์ŠคํŠธ + - ๋ฐ์ดํ„ฐ ๋ธŒ๋ฆฌ์ง€ ํ…Œ์ŠคํŠธ + - ํ†ต๊ณ„ ์กฐํšŒ ํ…Œ์ŠคํŠธ +- [ ] Prisma import ์™„์ „ ์ œ๊ฑฐ ํ™•์ธ +- [ ] ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ + +--- + +## ๐ŸŽฏ ์™„๋ฃŒ ๊ธฐ์ค€ + +- [ ] **31๊ฐœ Prisma ํ˜ธ์ถœ ๋ชจ๋‘ Raw Query๋กœ ์ „ํ™˜ ์™„๋ฃŒ** +- [ ] **๋ชจ๋“  TypeScript ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ** +- [ ] **ํŠธ๋žœ์žญ์…˜ ์ •์ƒ ๋™์ž‘ ํ™•์ธ** +- [ ] **์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ๋กค๋ฐฑ ์ •์ƒ ๋™์ž‘** +- [ ] **๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (20๊ฐœ ์ด์ƒ)** +- [ ] **๋ชจ๋“  ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ (7๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค)** +- [ ] **Prisma import ์™„์ „ ์ œ๊ฑฐ** +- [ ] **์„ฑ๋Šฅ ์ €ํ•˜ ์—†์Œ (๊ธฐ์กด ๋Œ€๋น„ ยฑ10% ์ด๋‚ด)** + +--- + +## ๐ŸŽฏ ์ฃผ์š” ๊ธฐ์ˆ ์  ๋„์ „ ๊ณผ์ œ + +### 1. groupBy ์ฟผ๋ฆฌ ์ „ํ™˜ + +**๋ฌธ์ œ**: Prisma์˜ `groupBy`๋ฅผ Raw Query๋กœ ์ „ํ™˜ +**ํ•ด๊ฒฐ**: PostgreSQL์˜ `GROUP BY` ๋ฐ ์ง‘๊ณ„ ํ•จ์ˆ˜ ์‚ฌ์šฉ + +```sql +SELECT relationship_type, COUNT(*) as count +FROM table_relationships +WHERE company_code = $1 AND is_active = 'Y' +GROUP BY relationship_type +ORDER BY count DESC +``` + +### 2. ํŠธ๋žœ์žญ์…˜ ๋ฐฐ์—ด ์ฒ˜๋ฆฌ + +**๋ฌธ์ œ**: Prisma์˜ `$transaction([...])` ๋ฐฐ์—ด ๋ฐฉ์‹์„ Raw Query๋กœ ์ „ํ™˜ +**ํ•ด๊ฒฐ**: `transaction` ํ•จ์ˆ˜ ๋‚ด์—์„œ ์ˆœ์ฐจ ์‹คํ–‰ + +```typescript +await transaction(async (client) => { + const results = []; + for (const item of items) { + const result = await client.query(...); + results.push(result); + } + return results; +}); +``` + +### 3. ๋™์  WHERE ์กฐ๊ฑด ์ƒ์„ฑ + +**๋ฌธ์ œ**: ๋‹ค์–‘ํ•œ ํ•„ํ„ฐ ์กฐ๊ฑด์„ ๋™์ ์œผ๋กœ ๊ตฌ์„ฑ +**ํ•ด๊ฒฐ**: ์กฐ๊ฑด๋ถ€ ํŒŒ๋ผ๋ฏธํ„ฐ ์ธ๋ฑ์Šค ๊ด€๋ฆฌ + +```typescript +const whereParams: any[] = []; +const whereConditions: string[] = []; +let paramIndex = 1; + +if (companyCode) { + whereConditions.push(`company_code = $${paramIndex++}`); + whereParams.push(companyCode); +} + +if (diagramId) { + whereConditions.push(`diagram_id = $${paramIndex++}`); + whereParams.push(diagramId); +} + +const whereSQL = + whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : ""; +``` + +--- + +**์ž‘์„ฑ์ผ**: 2025-09-30 +**์˜ˆ์ƒ ์†Œ์š” ์‹œ๊ฐ„**: 2-3์ผ +**๋‹ด๋‹น์ž**: ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœํŒ€ +**์šฐ์„ ์ˆœ์œ„**: ๐Ÿ”ด ์ตœ์šฐ์„  (Phase 2.3) +**์ƒํƒœ**: โณ **์ง„ํ–‰ ์˜ˆ์ •** diff --git a/PHASE2.4_DYNAMIC_FORM_MIGRATION.md b/PHASE2.4_DYNAMIC_FORM_MIGRATION.md new file mode 100644 index 00000000..0bd91a3f --- /dev/null +++ b/PHASE2.4_DYNAMIC_FORM_MIGRATION.md @@ -0,0 +1,218 @@ +# ๐Ÿ“ Phase 2.4: DynamicFormService Raw Query ์ „ํ™˜ ๊ณ„ํš + +## ๐Ÿ“‹ ๊ฐœ์š” + +DynamicFormService๋Š” **13๊ฐœ์˜ Prisma ํ˜ธ์ถœ**์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„(์•ฝ 11๊ฐœ)์€ `$queryRaw`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด SQL์€ ์ด๋ฏธ ์ž‘์„ฑ๋˜์–ด ์žˆ์ง€๋งŒ, **Prisma ํด๋ผ์ด์–ธํŠธ๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜๋ ค๋ฉด 13๊ฐœ ๋ชจ๋‘๋ฅผ `db.ts`์˜ `query` ํ•จ์ˆ˜๋กœ ๊ต์ฒด**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +### ๐Ÿ“Š ๊ธฐ๋ณธ ์ •๋ณด + +| ํ•ญ๋ชฉ | ๋‚ด์šฉ | +| --------------- | ---------------------------------------------------- | +| ํŒŒ์ผ ์œ„์น˜ | `backend-node/src/services/dynamicFormService.ts` | +| ํŒŒ์ผ ํฌ๊ธฐ | 1,200+ ๋ผ์ธ | +| Prisma ํ˜ธ์ถœ | 13๊ฐœ ($queryRaw: 11๊ฐœ, ORM: 2๊ฐœ) | +| **ํ˜„์žฌ ์ง„ํ–‰๋ฅ ** | **0/13 (0%)** โณ **์ „ํ™˜ ํ•„์š”** | +| **์ „ํ™˜ ํ•„์š”** | **13๊ฐœ ๋ชจ๋‘ ์ „ํ™˜ ํ•„์š”** (SQL์€ ์ด๋ฏธ ์ž‘์„ฑ๋˜์–ด ์žˆ์Œ) | +| ๋ณต์žก๋„ | ๋‚ฎ์Œ (SQL ์ž‘์„ฑ์€ ์™„๋ฃŒ, `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด๋งŒ ํ•„์š”) | +| ์šฐ์„ ์ˆœ์œ„ | ๐ŸŸข ๋‚ฎ์Œ (Phase 2.4) | + +### ๐ŸŽฏ ์ „ํ™˜ ๋ชฉํ‘œ + +- โœ… **13๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ `db.ts`์˜ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด** + - 11๊ฐœ `$queryRaw` โ†’ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด + - 2๊ฐœ ORM ๋ฉ”์„œ๋“œ โ†’ `query()` (SQL ์ƒˆ๋กœ ์ž‘์„ฑ) +- โœ… ๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ +- โœ… **Prisma import ์™„์ „ ์ œ๊ฑฐ** + +--- + +## ๐Ÿ” Prisma ์‚ฌ์šฉ ํ˜„ํ™ฉ ๋ถ„์„ + +### 1. `$queryRaw` / `$queryRawUnsafe` ์‚ฌ์šฉ (11๊ฐœ) + +**ํ˜„์žฌ ์ƒํƒœ**: SQL์€ ์ด๋ฏธ ์ž‘์„ฑ๋˜์–ด ์žˆ์Œ โœ… +**์ „ํ™˜ ์ž‘์—…**: `prisma.$queryRaw` โ†’ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด๋งŒ ํ•˜๋ฉด ๋จ + +```typescript +// ๊ธฐ์กด +await prisma.$queryRaw>`...`; +await prisma.$queryRawUnsafe(upsertQuery, ...values); + +// ์ „ํ™˜ ํ›„ +import { query } from "../database/db"; +await query>(`...`); +await query(upsertQuery, values); +``` + +### 2. ORM ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ (2๊ฐœ) + +**ํ˜„์žฌ ์ƒํƒœ**: Prisma ORM ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ +**์ „ํ™˜ ์ž‘์—…**: SQL ์ž‘์„ฑ ํ•„์š” + +#### 1. dynamic_form_data ์กฐํšŒ (1๊ฐœ) + +```typescript +// Line 867: ํผ ๋ฐ์ดํ„ฐ ์กฐํšŒ +const result = await prisma.dynamic_form_data.findUnique({ + where: { id }, + select: { data: true }, +}); +``` + +#### 2. screen_layouts ์กฐํšŒ (1๊ฐœ) + +```typescript +// Line 1101: ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ +const screenLayouts = await prisma.screen_layouts.findMany({ + where: { + screen_id: screenId, + component_type: "widget", + }, + select: { + component_id: true, + properties: true, + }, +}); +``` + +--- + +## ๐Ÿ“ ์ „ํ™˜ ์˜ˆ์‹œ + +### ์˜ˆ์‹œ 1: dynamic_form_data ์กฐํšŒ ์ „ํ™˜ + +**๊ธฐ์กด Prisma ์ฝ”๋“œ:** + +```typescript +const result = await prisma.dynamic_form_data.findUnique({ + where: { id }, + select: { data: true }, +}); +``` + +**์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** + +```typescript +import { queryOne } from "../database/db"; + +const result = await queryOne<{ data: any }>( + `SELECT data FROM dynamic_form_data WHERE id = $1`, + [id] +); +``` + +### ์˜ˆ์‹œ 2: screen_layouts ์กฐํšŒ ์ „ํ™˜ + +**๊ธฐ์กด Prisma ์ฝ”๋“œ:** + +```typescript +const screenLayouts = await prisma.screen_layouts.findMany({ + where: { + screen_id: screenId, + component_type: "widget", + }, + select: { + component_id: true, + properties: true, + }, +}); +``` + +**์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** + +```typescript +import { query } from "../database/db"; + +const screenLayouts = await query<{ + component_id: string; + properties: any; +}>( + `SELECT component_id, properties + FROM screen_layouts + WHERE screen_id = $1 AND component_type = $2`, + [screenId, "widget"] +); +``` + +--- + +## ๐Ÿงช ํ…Œ์ŠคํŠธ ๊ณ„ํš + +### ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (5๊ฐœ) + +```typescript +describe("DynamicFormService Raw Query ์ „ํ™˜ ํ…Œ์ŠคํŠธ", () => { + describe("getFormDataById", () => { + test("ํผ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ฑ๊ณต", async () => { ... }); + test("์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ", async () => { ... }); + }); + + describe("getScreenLayoutsForControl", () => { + test("ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ ์„ฑ๊ณต", async () => { ... }); + test("widget ํƒ€์ž…๋งŒ ํ•„ํ„ฐ๋ง", async () => { ... }); + test("๋นˆ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ", async () => { ... }); + }); +}); +``` + +### ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (3๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค) + +```typescript +describe("๋™์  ํผ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ", () => { + test("ํผ ๋ฐ์ดํ„ฐ UPSERT โ†’ ์กฐํšŒ", async () => { ... }); + test("ํผ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ โ†’ ์กฐํšŒ", async () => { ... }); + test("ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ โ†’ ์ œ์–ด ์„ค์ • ํ™•์ธ", async () => { ... }); +}); +``` + +--- + +## ๐Ÿ“‹ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +### 1๋‹จ๊ณ„: ORM ํ˜ธ์ถœ ์ „ํ™˜ (2๊ฐœ ํ•จ์ˆ˜) โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] `getFormDataById()` - dynamic_form_data.findUnique +- [ ] `getScreenLayoutsForControl()` - screen_layouts.findMany + +### 2๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ & ๊ฒ€์ฆ โณ **์ง„ํ–‰ ์˜ˆ์ •** + +- [ ] ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (5๊ฐœ) +- [ ] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (3๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค) +- [ ] Prisma import ์™„์ „ ์ œ๊ฑฐ ํ™•์ธ +- [ ] ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ + +--- + +## ๐ŸŽฏ ์™„๋ฃŒ ๊ธฐ์ค€ + +- [ ] **13๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ Raw Query๋กœ ์ „ํ™˜ ์™„๋ฃŒ** + - [ ] 11๊ฐœ `$queryRaw` โ†’ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด + - [ ] 2๊ฐœ ORM ๋ฉ”์„œ๋“œ โ†’ `query()` ํ•จ์ˆ˜๋กœ ์ „ํ™˜ (SQL ์ž‘์„ฑ) +- [ ] **๋ชจ๋“  TypeScript ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ** +- [ ] **๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (5๊ฐœ)** +- [ ] **๋ชจ๋“  ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ (3๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค)** +- [ ] **`import prisma` ์™„์ „ ์ œ๊ฑฐ ๋ฐ `import { query } from "../database/db"` ์‚ฌ์šฉ** +- [ ] **์„ฑ๋Šฅ ์ €ํ•˜ ์—†์Œ** + +--- + +## ๐Ÿ’ก ํŠน์ด์‚ฌํ•ญ + +### SQL์€ ์ด๋ฏธ ๊ฑฐ์˜ ์ž‘์„ฑ๋˜์–ด ์žˆ์Œ + +์ด ์„œ๋น„์Šค๋Š” ์ด๋ฏธ 85%๊ฐ€ `$queryRaw`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด, **SQL ์ž‘์„ฑ์€ ๊ฑฐ์˜ ์™„๋ฃŒ**๋˜์—ˆ์Šต๋‹ˆ๋‹ค: + +- โœ… UPSERT ๋กœ์ง: SQL ์ž‘์„ฑ ์™„๋ฃŒ (`$queryRawUnsafe` ์‚ฌ์šฉ ์ค‘) +- โœ… ์ปฌ๋Ÿผ ์ •๋ณด ์กฐํšŒ: SQL ์ž‘์„ฑ ์™„๋ฃŒ (`$queryRaw` ์‚ฌ์šฉ ์ค‘) +- โœ… Primary Key ์กฐํšŒ: SQL ์ž‘์„ฑ ์™„๋ฃŒ (Raw Query ์‚ฌ์šฉ ์ค‘) +- โณ **์ „ํ™˜ ์ž‘์—…**: `prisma.$queryRaw` โ†’ `query()` ํ•จ์ˆ˜๋กœ **๋‹จ์ˆœ ๊ต์ฒด๋งŒ ํ•„์š”** +- โณ ๋‹จ์ˆœ ์กฐํšŒ: 2๊ฐœ๋งŒ SQL ์ƒˆ๋กœ ์ž‘์„ฑ ํ•„์š” (๋งค์šฐ ๊ฐ„๋‹จํ•œ SELECT ์ฟผ๋ฆฌ) + +--- + +**์ž‘์„ฑ์ผ**: 2025-09-30 +**์˜ˆ์ƒ ์†Œ์š” ์‹œ๊ฐ„**: 0.5์ผ (SQL์€ 85% ์ž‘์„ฑ ์™„๋ฃŒ, ํ•จ์ˆ˜ ๊ต์ฒด ์ž‘์—… ํ•„์š”) +**๋‹ด๋‹น์ž**: ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœํŒ€ +**์šฐ์„ ์ˆœ์œ„**: ๐ŸŸข ๋‚ฎ์Œ (Phase 2.4) +**์ƒํƒœ**: โณ **์ง„ํ–‰ ์˜ˆ์ •** +**ํŠน์ด์‚ฌํ•ญ**: SQL์€ ๊ฑฐ์˜ ์ž‘์„ฑ๋˜์–ด ์žˆ์–ด `prisma.$queryRaw` โ†’ `query()` ๋‹จ์ˆœ ๊ต์ฒด ์ž‘์—…์ด ์ฃผ์š” ์ž‘์—… diff --git a/PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md b/PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md new file mode 100644 index 00000000..6b28a2ae --- /dev/null +++ b/PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md @@ -0,0 +1,99 @@ +# ๐Ÿ”Œ Phase 2.5: ExternalDbConnectionService Raw Query ์ „ํ™˜ ๊ณ„ํš + +## ๐Ÿ“‹ ๊ฐœ์š” + +ExternalDbConnectionService๋Š” **15๊ฐœ์˜ Prisma ํ˜ธ์ถœ**์ด ์žˆ์œผ๋ฉฐ, ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. + +### ๐Ÿ“Š ๊ธฐ๋ณธ ์ •๋ณด + +| ํ•ญ๋ชฉ | ๋‚ด์šฉ | +| --------------- | ---------------------------------------------------------- | +| ํŒŒ์ผ ์œ„์น˜ | `backend-node/src/services/externalDbConnectionService.ts` | +| ํŒŒ์ผ ํฌ๊ธฐ | 800+ ๋ผ์ธ | +| Prisma ํ˜ธ์ถœ | 15๊ฐœ | +| **ํ˜„์žฌ ์ง„ํ–‰๋ฅ ** | **0/15 (0%)** โณ **์ง„ํ–‰ ์˜ˆ์ •** | +| ๋ณต์žก๋„ | ์ค‘๊ฐ„ (CRUD + ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ) | +| ์šฐ์„ ์ˆœ์œ„ | ๐ŸŸก ์ค‘๊ฐ„ (Phase 2.5) | + +### ๐ŸŽฏ ์ „ํ™˜ ๋ชฉํ‘œ + +- โœ… 15๊ฐœ Prisma ํ˜ธ์ถœ์„ ๋ชจ๋‘ Raw Query๋กœ ์ „ํ™˜ +- โœ… ๋ฏผ๊ฐ ์ •๋ณด ์•”ํ˜ธํ™” ์ฒ˜๋ฆฌ ์œ ์ง€ +- โœ… ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ ๋กœ์ง ์ •์ƒ ๋™์ž‘ +- โœ… ๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ + +--- + +## ๐Ÿ” ์ฃผ์š” ๊ธฐ๋Šฅ + +### 1. ์™ธ๋ถ€ DB ์—ฐ๊ฒฐ ์ •๋ณด CRUD + +- ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ +- ์—ฐ๊ฒฐ ์ •๋ณด ์•”ํ˜ธํ™”/๋ณตํ˜ธํ™” + +### 2. ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ + +- MySQL, PostgreSQL, MSSQL, Oracle ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ + +### 3. ์—ฐ๊ฒฐ ์ •๋ณด ๊ด€๋ฆฌ + +- ํšŒ์‚ฌ๋ณ„ ์—ฐ๊ฒฐ ์ •๋ณด ์กฐํšŒ +- ํ™œ์„ฑ/๋น„ํ™œ์„ฑ ์ƒํƒœ ๊ด€๋ฆฌ + +--- + +## ๐Ÿ“ ์˜ˆ์ƒ ์ „ํ™˜ ํŒจํ„ด + +### CRUD ์ž‘์—… + +```typescript +// ์ƒ์„ฑ +await query( + `INSERT INTO external_db_connections + (connection_name, db_type, host, port, database_name, username, password, company_code) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING *`, + [...] +); + +// ์กฐํšŒ +await query( + `SELECT * FROM external_db_connections + WHERE company_code = $1 AND is_active = 'Y'`, + [companyCode] +); + +// ์ˆ˜์ • +await query( + `UPDATE external_db_connections + SET connection_name = $1, host = $2, ... + WHERE connection_id = $2`, + [...] +); + +// ์‚ญ์ œ (์†Œํ”„ํŠธ) +await query( + `UPDATE external_db_connections + SET is_active = 'N' + WHERE connection_id = $1`, + [connectionId] +); +``` + +--- + +## ๐ŸŽฏ ์™„๋ฃŒ ๊ธฐ์ค€ + +- [ ] **15๊ฐœ Prisma ํ˜ธ์ถœ ๋ชจ๋‘ Raw Query๋กœ ์ „ํ™˜** +- [ ] **์•”ํ˜ธํ™”/๋ณตํ˜ธํ™” ๋กœ์ง ์ •์ƒ ๋™์ž‘** +- [ ] **์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ ์ •์ƒ ๋™์ž‘** +- [ ] **๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (10๊ฐœ ์ด์ƒ)** +- [ ] **Prisma import ์™„์ „ ์ œ๊ฑฐ** + +--- + +**์ž‘์„ฑ์ผ**: 2025-09-30 +**์˜ˆ์ƒ ์†Œ์š” ์‹œ๊ฐ„**: 1์ผ +**๋‹ด๋‹น์ž**: ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœํŒ€ +**์šฐ์„ ์ˆœ์œ„**: ๐ŸŸก ์ค‘๊ฐ„ (Phase 2.5) +**์ƒํƒœ**: โณ **์ง„ํ–‰ ์˜ˆ์ •** diff --git a/PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md b/PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md new file mode 100644 index 00000000..7c17308a --- /dev/null +++ b/PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md @@ -0,0 +1,205 @@ +# ๐ŸŽฎ Phase 2.6: DataflowControlService Raw Query ์ „ํ™˜ ๊ณ„ํš + +## ๐Ÿ“‹ ๊ฐœ์š” + +DataflowControlService๋Š” **6๊ฐœ์˜ Prisma ํ˜ธ์ถœ**์ด ์žˆ์œผ๋ฉฐ, ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์ œ์–ด ๋ฐ ์‹คํ–‰์„ ๋‹ด๋‹นํ•˜๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. + +### ๐Ÿ“Š ๊ธฐ๋ณธ ์ •๋ณด + +| ํ•ญ๋ชฉ | ๋‚ด์šฉ | +| --------------- | ----------------------------------------------------- | +| ํŒŒ์ผ ์œ„์น˜ | `backend-node/src/services/dataflowControlService.ts` | +| ํŒŒ์ผ ํฌ๊ธฐ | 600+ ๋ผ์ธ | +| Prisma ํ˜ธ์ถœ | 6๊ฐœ | +| **ํ˜„์žฌ ์ง„ํ–‰๋ฅ ** | **0/6 (0%)** โณ **์ง„ํ–‰ ์˜ˆ์ •** | +| ๋ณต์žก๋„ | ๋†’์Œ (๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) | +| ์šฐ์„ ์ˆœ์œ„ | ๐ŸŸก ์ค‘๊ฐ„ (Phase 2.6) | + +### ๐ŸŽฏ ์ „ํ™˜ ๋ชฉํ‘œ + +- โœ… **6๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ `db.ts`์˜ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด** +- โœ… ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ •์ƒ ๋™์ž‘ ํ™•์ธ +- โœ… ๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ +- โœ… **Prisma import ์™„์ „ ์ œ๊ฑฐ** + +--- + +## ๐Ÿ” Prisma ์‚ฌ์šฉ ํ˜„ํ™ฉ ๋ถ„์„ + +### ์ฃผ์š” ๊ธฐ๋Šฅ + +1. **๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์‹คํ–‰ ๊ด€๋ฆฌ** + - ๊ด€๊ณ„ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ์กฐํšŒ ๋ฐ ์ €์žฅ + - ์กฐ๊ฑด๋ถ€ ์‹คํ–‰ ๋กœ์ง +2. **ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ** + - ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์— ๊ฑธ์นœ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ +3. **๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋ฐ ๋งคํ•‘** + - ์†Œ์Šค-ํƒ€๊ฒŸ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ + +--- + +## ๐Ÿ“ ์ „ํ™˜ ๊ณ„ํš + +### 1๋‹จ๊ณ„: ๊ธฐ๋ณธ ์กฐํšŒ ์ „ํ™˜ (2๊ฐœ ํ•จ์ˆ˜) + +**ํ•จ์ˆ˜ ๋ชฉ๋ก**: + +- `getRelationshipById()` - ๊ด€๊ณ„ ์ •๋ณด ์กฐํšŒ +- `getDataflowConfig()` - ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์„ค์ • ์กฐํšŒ + +### 2๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ์‹คํ–‰ ๋กœ์ง ์ „ํ™˜ (2๊ฐœ ํ•จ์ˆ˜) + +**ํ•จ์ˆ˜ ๋ชฉ๋ก**: + +- `executeDataflow()` - ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์‹คํ–‰ +- `validateDataflow()` - ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ๊ฒ€์ฆ + +### 3๋‹จ๊ณ„: ๋ณต์žกํ•œ ๊ธฐ๋Šฅ - ํŠธ๋žœ์žญ์…˜ (2๊ฐœ ํ•จ์ˆ˜) + +**ํ•จ์ˆ˜ ๋ชฉ๋ก**: + +- `executeWithTransaction()` - ํŠธ๋žœ์žญ์…˜ ๋‚ด ์‹คํ–‰ +- `rollbackOnError()` - ์—๋Ÿฌ ์‹œ ๋กค๋ฐฑ + +--- + +## ๐Ÿ’ป ์ „ํ™˜ ์˜ˆ์‹œ + +### ์˜ˆ์‹œ 1: ๊ด€๊ณ„ ์ •๋ณด ์กฐํšŒ + +```typescript +// ๊ธฐ์กด Prisma +const relationship = await prisma.table_relationship.findUnique({ + where: { relationship_id: relationshipId }, + include: { + source_table: true, + target_table: true, + }, +}); + +// ์ „ํ™˜ ํ›„ +import { query } from "../database/db"; + +const relationship = await query( + `SELECT + tr.*, + st.table_name as source_table_name, + tt.table_name as target_table_name + FROM table_relationship tr + LEFT JOIN table_labels st ON tr.source_table_id = st.table_id + LEFT JOIN table_labels tt ON tr.target_table_id = tt.table_id + WHERE tr.relationship_id = $1`, + [relationshipId] +); +``` + +### ์˜ˆ์‹œ 2: ํŠธ๋žœ์žญ์…˜ ๋‚ด ์‹คํ–‰ + +```typescript +// ๊ธฐ์กด Prisma +await prisma.$transaction(async (tx) => { + // ์†Œ์Šค ๋ฐ์ดํ„ฐ ์กฐํšŒ + const sourceData = await tx.dynamic_form_data.findMany(...); + + // ํƒ€๊ฒŸ ๋ฐ์ดํ„ฐ ์ €์žฅ + await tx.dynamic_form_data.createMany(...); + + // ์‹คํ–‰ ๋กœ๊ทธ ์ €์žฅ + await tx.dataflow_execution_log.create(...); +}); + +// ์ „ํ™˜ ํ›„ +import { transaction } from "../database/db"; + +await transaction(async (client) => { + // ์†Œ์Šค ๋ฐ์ดํ„ฐ ์กฐํšŒ + const sourceData = await client.query( + `SELECT * FROM dynamic_form_data WHERE ...`, + [...] + ); + + // ํƒ€๊ฒŸ ๋ฐ์ดํ„ฐ ์ €์žฅ + await client.query( + `INSERT INTO dynamic_form_data (...) VALUES (...)`, + [...] + ); + + // ์‹คํ–‰ ๋กœ๊ทธ ์ €์žฅ + await client.query( + `INSERT INTO dataflow_execution_log (...) VALUES (...)`, + [...] + ); +}); +``` + +--- + +## โœ… 5๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ & ๊ฒ€์ฆ + +### ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (10๊ฐœ) + +- [ ] getRelationshipById - ๊ด€๊ณ„ ์ •๋ณด ์กฐํšŒ +- [ ] getDataflowConfig - ์„ค์ • ์กฐํšŒ +- [ ] executeDataflow - ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์‹คํ–‰ +- [ ] validateDataflow - ๊ฒ€์ฆ +- [ ] executeWithTransaction - ํŠธ๋žœ์žญ์…˜ ์‹คํ–‰ +- [ ] rollbackOnError - ์—๋Ÿฌ ์ฒ˜๋ฆฌ +- [ ] transformData - ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ +- [ ] mapSourceToTarget - ํ•„๋“œ ๋งคํ•‘ +- [ ] applyConditions - ์กฐ๊ฑด ์ ์šฉ +- [ ] logExecution - ์‹คํ–‰ ๋กœ๊ทธ + +### ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (4๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค) + +1. **๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์‹คํ–‰ ์‹œ๋‚˜๋ฆฌ์˜ค** + - ๊ด€๊ณ„ ์กฐํšŒ โ†’ ๋ฐ์ดํ„ฐ ์‹คํ–‰ โ†’ ๋กœ๊ทธ ์ €์žฅ +2. **ํŠธ๋žœ์žญ์…˜ ํ…Œ์ŠคํŠธ** + - ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ” ๋™์‹œ ์ฒ˜๋ฆฌ + - ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ๋กค๋ฐฑ +3. **์กฐ๊ฑด๋ถ€ ์‹คํ–‰ ํ…Œ์ŠคํŠธ** + - ์กฐ๊ฑด์— ๋”ฐ๋ฅธ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ +4. **๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ํ…Œ์ŠคํŠธ** + - ์†Œ์Šค-ํƒ€๊ฒŸ ๋ฐ์ดํ„ฐ ๋งคํ•‘ + +--- + +## ๐ŸŽฏ ์™„๋ฃŒ ๊ธฐ์ค€ + +- [ ] **6๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ Raw Query๋กœ ์ „ํ™˜ ์™„๋ฃŒ** +- [ ] **๋ชจ๋“  TypeScript ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ** +- [ ] **ํŠธ๋žœ์žญ์…˜ ์ •์ƒ ๋™์ž‘ ํ™•์ธ** +- [ ] **๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ •์ƒ ๋™์ž‘** +- [ ] **๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (10๊ฐœ)** +- [ ] **๋ชจ๋“  ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ (4๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค)** +- [ ] **`import prisma` ์™„์ „ ์ œ๊ฑฐ ๋ฐ `import { query, transaction } from "../database/db"` ์‚ฌ์šฉ** +- [ ] **์„ฑ๋Šฅ ์ €ํ•˜ ์—†์Œ** + +--- + +## ๐Ÿ’ก ํŠน์ด์‚ฌํ•ญ + +### ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง + +์ด ์„œ๋น„์Šค๋Š” ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์ œ์–ด๋ผ๋Š” ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค: + +- ์กฐ๊ฑด๋ถ€ ์‹คํ–‰ ๋กœ์ง +- ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋ฐ ๋งคํ•‘ +- ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ +- ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ๋กค๋ฐฑ + +### ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ค‘์š” + +๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์‹คํ–‰์€ ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ: + +- ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ๊ณ ๋ ค +- ์ธ๋ฑ์Šค ํ™œ์šฉ +- ์ฟผ๋ฆฌ ์ตœ์ ํ™” + +--- + +**์ž‘์„ฑ์ผ**: 2025-09-30 +**์˜ˆ์ƒ ์†Œ์š” ์‹œ๊ฐ„**: 1์ผ +**๋‹ด๋‹น์ž**: ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœํŒ€ +**์šฐ์„ ์ˆœ์œ„**: ๐ŸŸก ์ค‘๊ฐ„ (Phase 2.6) +**์ƒํƒœ**: โณ **์ง„ํ–‰ ์˜ˆ์ •** +**ํŠน์ด์‚ฌํ•ญ**: ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ํฌํ•จ๋˜์–ด ์žˆ์–ด ์‹ ์ค‘ํ•œ ํ…Œ์ŠคํŠธ ํ•„์š” diff --git a/PHASE2.7_DDL_EXECUTION_MIGRATION.md b/PHASE2.7_DDL_EXECUTION_MIGRATION.md new file mode 100644 index 00000000..eaa2e97b --- /dev/null +++ b/PHASE2.7_DDL_EXECUTION_MIGRATION.md @@ -0,0 +1,175 @@ +# ๐Ÿ”ง Phase 2.7: DDLExecutionService Raw Query ์ „ํ™˜ ๊ณ„ํš + +## ๐Ÿ“‹ ๊ฐœ์š” + +DDLExecutionService๋Š” **4๊ฐœ์˜ Prisma ํ˜ธ์ถœ**์ด ์žˆ์œผ๋ฉฐ, DDL(Data Definition Language) ์‹คํ–‰ ๋ฐ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. + +### ๐Ÿ“Š ๊ธฐ๋ณธ ์ •๋ณด + +| ํ•ญ๋ชฉ | ๋‚ด์šฉ | +| --------------- | -------------------------------------------------- | +| ํŒŒ์ผ ์œ„์น˜ | `backend-node/src/services/ddlExecutionService.ts` | +| ํŒŒ์ผ ํฌ๊ธฐ | 400+ ๋ผ์ธ | +| Prisma ํ˜ธ์ถœ | 4๊ฐœ | +| **ํ˜„์žฌ ์ง„ํ–‰๋ฅ ** | **0/4 (0%)** โณ **์ง„ํ–‰ ์˜ˆ์ •** | +| ๋ณต์žก๋„ | ์ค‘๊ฐ„ (DDL ์‹คํ–‰ + ๋กœ๊ทธ ๊ด€๋ฆฌ) | +| ์šฐ์„ ์ˆœ์œ„ | ๐ŸŸข ๋‚ฎ์Œ (Phase 2.7) | + +### ๐ŸŽฏ ์ „ํ™˜ ๋ชฉํ‘œ + +- โœ… **4๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ `db.ts`์˜ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด** +- โœ… DDL ์‹คํ–‰ ์ •์ƒ ๋™์ž‘ ํ™•์ธ +- โœ… ๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ +- โœ… **Prisma import ์™„์ „ ์ œ๊ฑฐ** + +--- + +## ๐Ÿ” Prisma ์‚ฌ์šฉ ํ˜„ํ™ฉ ๋ถ„์„ + +### ์ฃผ์š” ๊ธฐ๋Šฅ + +1. **DDL ์‹คํ–‰** + - CREATE TABLE, ALTER TABLE, DROP TABLE + - CREATE INDEX, DROP INDEX +2. **์‹คํ–‰ ๋กœ๊ทธ ๊ด€๋ฆฌ** + - DDL ์‹คํ–‰ ์ด๋ ฅ ์ €์žฅ + - ์—๋Ÿฌ ๋กœ๊ทธ ๊ด€๋ฆฌ +3. **๋กค๋ฐฑ ์ง€์›** + - DDL ๋กค๋ฐฑ SQL ์ƒ์„ฑ ๋ฐ ์‹คํ–‰ + +--- + +## ๐Ÿ“ ์ „ํ™˜ ๊ณ„ํš + +### 1๋‹จ๊ณ„: DDL ์‹คํ–‰ ์ „ํ™˜ (2๊ฐœ ํ•จ์ˆ˜) + +**ํ•จ์ˆ˜ ๋ชฉ๋ก**: + +- `executeDDL()` - DDL ์‹คํ–‰ +- `validateDDL()` - DDL ๋ฌธ๋ฒ• ๊ฒ€์ฆ + +### 2๋‹จ๊ณ„: ๋กœ๊ทธ ๊ด€๋ฆฌ ์ „ํ™˜ (2๊ฐœ ํ•จ์ˆ˜) + +**ํ•จ์ˆ˜ ๋ชฉ๋ก**: + +- `saveDDLLog()` - ์‹คํ–‰ ๋กœ๊ทธ ์ €์žฅ +- `getDDLHistory()` - ์‹คํ–‰ ์ด๋ ฅ ์กฐํšŒ + +--- + +## ๐Ÿ’ป ์ „ํ™˜ ์˜ˆ์‹œ + +### ์˜ˆ์‹œ 1: DDL ์‹คํ–‰ ๋ฐ ๋กœ๊ทธ ์ €์žฅ + +```typescript +// ๊ธฐ์กด Prisma +await prisma.$executeRawUnsafe(ddlQuery); + +await prisma.ddl_execution_log.create({ + data: { + ddl_statement: ddlQuery, + execution_status: "SUCCESS", + executed_by: userId, + }, +}); + +// ์ „ํ™˜ ํ›„ +import { query } from "../database/db"; + +await query(ddlQuery); + +await query( + `INSERT INTO ddl_execution_log + (ddl_statement, execution_status, executed_by, executed_date) + VALUES ($1, $2, $3, $4)`, + [ddlQuery, "SUCCESS", userId, new Date()] +); +``` + +### ์˜ˆ์‹œ 2: DDL ์‹คํ–‰ ์ด๋ ฅ ์กฐํšŒ + +```typescript +// ๊ธฐ์กด Prisma +const history = await prisma.ddl_execution_log.findMany({ + where: { + company_code: companyCode, + execution_status: "SUCCESS", + }, + orderBy: { executed_date: "desc" }, + take: 50, +}); + +// ์ „ํ™˜ ํ›„ +import { query } from "../database/db"; + +const history = await query( + `SELECT * FROM ddl_execution_log + WHERE company_code = $1 + AND execution_status = $2 + ORDER BY executed_date DESC + LIMIT $3`, + [companyCode, "SUCCESS", 50] +); +``` + +--- + +## โœ… 3๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ & ๊ฒ€์ฆ + +### ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (8๊ฐœ) + +- [ ] executeDDL - CREATE TABLE +- [ ] executeDDL - ALTER TABLE +- [ ] executeDDL - DROP TABLE +- [ ] executeDDL - CREATE INDEX +- [ ] validateDDL - ๋ฌธ๋ฒ• ๊ฒ€์ฆ +- [ ] saveDDLLog - ๋กœ๊ทธ ์ €์žฅ +- [ ] getDDLHistory - ์ด๋ ฅ ์กฐํšŒ +- [ ] rollbackDDL - DDL ๋กค๋ฐฑ + +### ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (3๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค) + +1. **ํ…Œ์ด๋ธ” ์ƒ์„ฑ โ†’ ๋กœ๊ทธ ์ €์žฅ โ†’ ์ด๋ ฅ ์กฐํšŒ** +2. **DDL ์‹คํ–‰ ์‹คํŒจ โ†’ ์—๋Ÿฌ ๋กœ๊ทธ ์ €์žฅ** +3. **DDL ๋กค๋ฐฑ ํ…Œ์ŠคํŠธ** + +--- + +## ๐ŸŽฏ ์™„๋ฃŒ ๊ธฐ์ค€ + +- [ ] **4๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ Raw Query๋กœ ์ „ํ™˜ ์™„๋ฃŒ** +- [ ] **๋ชจ๋“  TypeScript ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ** +- [ ] **DDL ์‹คํ–‰ ์ •์ƒ ๋™์ž‘ ํ™•์ธ** +- [ ] **๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (8๊ฐœ)** +- [ ] **๋ชจ๋“  ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ (3๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค)** +- [ ] **`import prisma` ์™„์ „ ์ œ๊ฑฐ ๋ฐ `import { query } from "../database/db"` ์‚ฌ์šฉ** +- [ ] **์„ฑ๋Šฅ ์ €ํ•˜ ์—†์Œ** + +--- + +## ๐Ÿ’ก ํŠน์ด์‚ฌํ•ญ + +### DDL ์‹คํ–‰์˜ ์œ„ํ—˜์„ฑ + +DDL์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฏ€๋กœ ๋งค์šฐ ์‹ ์ค‘ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: + +- ์‹คํ–‰ ์ „ ๊ฒ€์ฆ ํ•„์ˆ˜ +- ๋กค๋ฐฑ SQL ์ž๋™ ์ƒ์„ฑ +- ์‹คํ–‰ ์ด๋ ฅ ์ฒ ์ €ํžˆ ๊ด€๋ฆฌ + +### ํŠธ๋žœ์žญ์…˜ ์ง€์› ์ œํ•œ + +PostgreSQL์—์„œ ์ผ๋ถ€ DDL์€ ํŠธ๋žœ์žญ์…˜์„ ์ง€์›ํ•˜์ง€๋งŒ, ์ผ๋ถ€๋Š” ์ž๋™ ์ปค๋ฐ‹๋ฉ๋‹ˆ๋‹ค: + +- CREATE TABLE: ํŠธ๋žœ์žญ์…˜ ์ง€์› โœ… +- DROP TABLE: ํŠธ๋žœ์žญ์…˜ ์ง€์› โœ… +- CREATE INDEX CONCURRENTLY: ํŠธ๋žœ์žญ์…˜ ๋ฏธ์ง€์› โŒ + +--- + +**์ž‘์„ฑ์ผ**: 2025-09-30 +**์˜ˆ์ƒ ์†Œ์š” ์‹œ๊ฐ„**: 0.5์ผ +**๋‹ด๋‹น์ž**: ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœํŒ€ +**์šฐ์„ ์ˆœ์œ„**: ๐ŸŸข ๋‚ฎ์Œ (Phase 2.7) +**์ƒํƒœ**: โณ **์ง„ํ–‰ ์˜ˆ์ •** +**ํŠน์ด์‚ฌํ•ญ**: DDL ์‹คํ–‰์˜ ํŠน์„ฑ์ƒ ์‹ ์ค‘ํ•œ ํ…Œ์ŠคํŠธ ํ•„์š” diff --git a/PRISMA_TO_RAW_QUERY_MIGRATION_PLAN.md b/PRISMA_TO_RAW_QUERY_MIGRATION_PLAN.md index d04bdf51..f3664b9d 100644 --- a/PRISMA_TO_RAW_QUERY_MIGRATION_PLAN.md +++ b/PRISMA_TO_RAW_QUERY_MIGRATION_PLAN.md @@ -1065,16 +1065,42 @@ describe("Performance Benchmarks", () => { ### **Phase 2: ํ•ต์‹ฌ ์„œ๋น„์Šค (3์ฃผ) - 107๊ฐœ ํ˜ธ์ถœ** -- [ ] ScreenManagementService ์ „ํ™˜ (46๊ฐœ) - ์ตœ์šฐ์„  -- [ ] TableManagementService ์ „ํ™˜ (35๊ฐœ) - ์ตœ์šฐ์„  -- [ ] DataflowService ์ „ํ™˜ (31๊ฐœ) โญ ์‹ ๊ทœ ๋ฐœ๊ฒฌ -- [ ] DynamicFormService ์ „ํ™˜ (15๊ฐœ) - UPSERT ํฌํ•จ -- [ ] ExternalDbConnectionService ์ „ํ™˜ (15๊ฐœ) -- [ ] DataflowControlService ์ „ํ™˜ (6๊ฐœ) - ๋ณต์žกํ•œ ๋กœ์ง -- [ ] DDLExecutionService ์ „ํ™˜ (6๊ฐœ) -- [ ] ~~AuthService ์ „ํ™˜ (5๊ฐœ)~~ โœ… Phase 1.5๋กœ ์ด๋™ -- [ ] ~~MultiConnectionQueryService ์ „ํ™˜ (4๊ฐœ)~~ โœ… Phase 1 ์™„๋ฃŒ -- [ ] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์‹คํ–‰ +#### โœ… ์™„๋ฃŒ๋œ ์„œ๋น„์Šค + +- [x] **ScreenManagementService ์ „ํ™˜ (46๊ฐœ)** โœ… **์™„๋ฃŒ** + - [x] 46๊ฐœ Prisma ํ˜ธ์ถœ ์ „ํ™˜ ์™„๋ฃŒ + - [x] 18๊ฐœ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ + - [x] 6๊ฐœ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ + - [x] ์‹ค์ œ ์šด์˜ ๋ฒ„๊ทธ ๋ฐœ๊ฒฌ ๋ฐ ์ˆ˜์ • (์†Œ์ˆ˜์  ์ขŒํ‘œ) + - ๐Ÿ“„ **[PHASE2_SCREEN_MANAGEMENT_MIGRATION.md](PHASE2_SCREEN_MANAGEMENT_MIGRATION.md)** + +#### โณ ์ง„ํ–‰ ์˜ˆ์ • ์„œ๋น„์Šค + +- [ ] **TableManagementService ์ „ํ™˜ (33๊ฐœ)** - Phase 2.2 ๐ŸŸก ์ค‘๊ฐ„ ์šฐ์„ ์ˆœ์œ„ + - 33๊ฐœ Prisma ํ˜ธ์ถœ ($queryRaw 26๊ฐœ + ORM 7๊ฐœ) + - SQL์€ 79% ์ž‘์„ฑ ์™„๋ฃŒ โ†’ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด๋งŒ ํ•„์š” + - ๐Ÿ“„ **[PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md](PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md)** +- [ ] **DataflowService ์ „ํ™˜ (31๊ฐœ)** - Phase 2.3 ๐Ÿ”ด ์ตœ์šฐ์„  + - 31๊ฐœ Prisma ํ˜ธ์ถœ (๋ณต์žกํ•œ ๊ด€๊ณ„ ๊ด€๋ฆฌ) + - ๐Ÿ“„ **[PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md](PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md)** +- [ ] **DynamicFormService ์ „ํ™˜ (13๊ฐœ)** - Phase 2.4 ๐ŸŸข ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ + - 13๊ฐœ Prisma ํ˜ธ์ถœ ($queryRaw 11๊ฐœ + ORM 2๊ฐœ) + - SQL์€ 85% ์ž‘์„ฑ ์™„๋ฃŒ โ†’ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด๋งŒ ํ•„์š” + - ๐Ÿ“„ **[PHASE2.4_DYNAMIC_FORM_MIGRATION.md](PHASE2.4_DYNAMIC_FORM_MIGRATION.md)** +- [ ] **ExternalDbConnectionService ์ „ํ™˜ (15๊ฐœ)** - Phase 2.5 ๐ŸŸก ์ค‘๊ฐ„ ์šฐ์„ ์ˆœ์œ„ + - 15๊ฐœ Prisma ํ˜ธ์ถœ (์™ธ๋ถ€ DB ์—ฐ๊ฒฐ ๊ด€๋ฆฌ) + - ๐Ÿ“„ **[PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md](PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md)** +- [ ] **DataflowControlService ์ „ํ™˜ (6๊ฐœ)** - Phase 2.6 ๐ŸŸก ์ค‘๊ฐ„ ์šฐ์„ ์ˆœ์œ„ + - 6๊ฐœ Prisma ํ˜ธ์ถœ (๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) + - ๐Ÿ“„ **[PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md](PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md)** +- [ ] **DDLExecutionService ์ „ํ™˜ (4๊ฐœ)** - Phase 2.7 ๐ŸŸข ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ + - 4๊ฐœ Prisma ํ˜ธ์ถœ (DDL ์‹คํ–‰ ๋ฐ ๋กœ๊ทธ) + - ๐Ÿ“„ **[PHASE2.7_DDL_EXECUTION_MIGRATION.md](PHASE2.7_DDL_EXECUTION_MIGRATION.md)** + +#### โœ… ๋‹ค๋ฅธ Phase๋กœ ์ด๋™ + +- [x] ~~AuthService ์ „ํ™˜ (5๊ฐœ)~~ โ†’ Phase 1.5๋กœ ์ด๋™ +- [x] ~~MultiConnectionQueryService ์ „ํ™˜ (4๊ฐœ)~~ โ†’ Phase 1 ์™„๋ฃŒ ### **Phase 3: ๊ด€๋ฆฌ ๊ธฐ๋Šฅ (2.5์ฃผ) - 162๊ฐœ ํ˜ธ์ถœ**