# ๐Ÿ“‹ Phase 3.13: EntityJoinService Raw Query ์ „ํ™˜ ๊ณ„ํš ## ๐Ÿ“‹ ๊ฐœ์š” EntityJoinService๋Š” **5๊ฐœ์˜ Prisma ํ˜ธ์ถœ**์ด ์žˆ์œผ๋ฉฐ, ์—”ํ‹ฐํ‹ฐ ๊ฐ„ ์กฐ์ธ ๊ด€๊ณ„ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ### ๐Ÿ“Š ๊ธฐ๋ณธ ์ •๋ณด | ํ•ญ๋ชฉ | ๋‚ด์šฉ | | --------------- | ------------------------------------------------ | | ํŒŒ์ผ ์œ„์น˜ | `backend-node/src/services/entityJoinService.ts` | | ํŒŒ์ผ ํฌ๊ธฐ | 574 ๋ผ์ธ | | Prisma ํ˜ธ์ถœ | 5๊ฐœ | | **ํ˜„์žฌ ์ง„ํ–‰๋ฅ ** | **0/5 (0%)** ๐Ÿ”„ **์ง„ํ–‰ ์˜ˆ์ •** | | ๋ณต์žก๋„ | ์ค‘๊ฐ„ (์กฐ์ธ ์ฟผ๋ฆฌ, ๊ด€๊ณ„ ์„ค์ •) | | ์šฐ์„ ์ˆœ์œ„ | ๐ŸŸก ์ค‘๊ฐ„ (Phase 3.13) | | **์ƒํƒœ** | โณ **๋Œ€๊ธฐ ์ค‘** | ### ๐ŸŽฏ ์ „ํ™˜ ๋ชฉํ‘œ - โณ **5๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ `db.ts`์˜ `query()`, `queryOne()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด** - โณ ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ์„ค์ • CRUD ๊ธฐ๋Šฅ ์ •์ƒ ๋™์ž‘ - โณ ๋ณต์žกํ•œ ์กฐ์ธ ์ฟผ๋ฆฌ ์ „ํ™˜ (LEFT JOIN, INNER JOIN) - โณ ์กฐ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ - โณ TypeScript ์ปดํŒŒ์ผ ์„ฑ๊ณต - โณ **Prisma import ์™„์ „ ์ œ๊ฑฐ** --- ## ๐Ÿ” ์˜ˆ์ƒ Prisma ์‚ฌ์šฉ ํŒจํ„ด ### ์ฃผ์š” ๊ธฐ๋Šฅ (5๊ฐœ ์˜ˆ์ƒ) #### 1. **์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ๋ชฉ๋ก ์กฐํšŒ** - findMany with filters - ๋™์  WHERE ์กฐ๊ฑด - ํŽ˜์ด์ง•, ์ •๋ ฌ #### 2. **์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ๋‹จ๊ฑด ์กฐํšŒ** - findUnique or findFirst - join_id ๊ธฐ์ค€ #### 3. **์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ์ƒ์„ฑ** - create - ์กฐ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ #### 4. **์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ์ˆ˜์ •** - update - ๋™์  UPDATE ์ฟผ๋ฆฌ #### 5. **์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ์‚ญ์ œ** - delete --- ## ๐Ÿ’ก ์ „ํ™˜ ์ „๋žต ### 1๋‹จ๊ณ„: ๊ธฐ๋ณธ CRUD ์ „ํ™˜ (5๊ฐœ) - getEntityJoins() - ๋ชฉ๋ก ์กฐํšŒ - getEntityJoin() - ๋‹จ๊ฑด ์กฐํšŒ - createEntityJoin() - ์ƒ์„ฑ - updateEntityJoin() - ์ˆ˜์ • - deleteEntityJoin() - ์‚ญ์ œ --- ## ๐Ÿ’ป ์ „ํ™˜ ์˜ˆ์‹œ ### ์˜ˆ์‹œ 1: ์กฐ์ธ ์„ค์ • ์กฐํšŒ (LEFT JOIN์œผ๋กœ ํ…Œ์ด๋ธ” ์ •๋ณด ํฌํ•จ) **๋ณ€๊ฒฝ ์ „**: ```typescript const joins = await prisma.entity_joins.findMany({ where: { company_code: companyCode, is_active: true, }, include: { source_table: true, target_table: true, }, orderBy: { created_at: "desc" }, }); ``` **๋ณ€๊ฒฝ ํ›„**: ```typescript const joins = await query( `SELECT ej.*, st.table_name as source_table_name, st.table_label as source_table_label, tt.table_name as target_table_name, tt.table_label as target_table_label FROM entity_joins ej LEFT JOIN tables st ON ej.source_table_id = st.table_id LEFT JOIN tables tt ON ej.target_table_id = tt.table_id WHERE ej.company_code = $1 AND ej.is_active = $2 ORDER BY ej.created_at DESC`, [companyCode, true] ); ``` ### ์˜ˆ์‹œ 2: ์กฐ์ธ ์ƒ์„ฑ (์œ ํšจ์„ฑ ๊ฒ€์ฆ ํฌํ•จ) **๋ณ€๊ฒฝ ์ „**: ```typescript // ์กฐ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ const sourceTable = await prisma.tables.findUnique({ where: { table_id: sourceTableId }, }); const targetTable = await prisma.tables.findUnique({ where: { table_id: targetTableId }, }); if (!sourceTable || !targetTable) { throw new Error("Invalid table references"); } // ์กฐ์ธ ์ƒ์„ฑ const join = await prisma.entity_joins.create({ data: { source_table_id: sourceTableId, target_table_id: targetTableId, join_type: joinType, join_condition: joinCondition, company_code: companyCode, }, }); ``` **๋ณ€๊ฒฝ ํ›„**: ```typescript // ์กฐ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ (Promise.all๋กœ ๋ณ‘๋ ฌ ์‹คํ–‰) const [sourceTable, targetTable] = await Promise.all([ queryOne( `SELECT * FROM tables WHERE table_id = $1`, [sourceTableId] ), queryOne( `SELECT * FROM tables WHERE table_id = $1`, [targetTableId] ), ]); if (!sourceTable || !targetTable) { throw new Error("Invalid table references"); } // ์กฐ์ธ ์ƒ์„ฑ const join = await queryOne( `INSERT INTO entity_joins (source_table_id, target_table_id, join_type, join_condition, company_code, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, NOW(), NOW()) RETURNING *`, [sourceTableId, targetTableId, joinType, joinCondition, companyCode] ); ``` ### ์˜ˆ์‹œ 3: ์กฐ์ธ ์ˆ˜์ • **๋ณ€๊ฒฝ ์ „**: ```typescript const join = await prisma.entity_joins.update({ where: { join_id: joinId }, data: { join_type: joinType, join_condition: joinCondition, is_active: isActive, }, }); ``` **๋ณ€๊ฒฝ ํ›„**: ```typescript const updateFields: string[] = ["updated_at = NOW()"]; const values: any[] = []; let paramIndex = 1; if (joinType !== undefined) { updateFields.push(`join_type = $${paramIndex++}`); values.push(joinType); } if (joinCondition !== undefined) { updateFields.push(`join_condition = $${paramIndex++}`); values.push(joinCondition); } if (isActive !== undefined) { updateFields.push(`is_active = $${paramIndex++}`); values.push(isActive); } const join = await queryOne( `UPDATE entity_joins SET ${updateFields.join(", ")} WHERE join_id = $${paramIndex} RETURNING *`, [...values, joinId] ); ``` --- ## ๐Ÿ”ง ๊ธฐ์ˆ ์  ๊ณ ๋ ค์‚ฌํ•ญ ### 1. ์กฐ์ธ ํƒ€์ž… ๊ฒ€์ฆ ```typescript const VALID_JOIN_TYPES = ["INNER", "LEFT", "RIGHT", "FULL"]; if (!VALID_JOIN_TYPES.includes(joinType)) { throw new Error("Invalid join type"); } ``` ### 2. ์กฐ์ธ ์กฐ๊ฑด ๊ฒ€์ฆ ```typescript // ์กฐ์ธ ์กฐ๊ฑด์€ SQL ์กฐ๊ฑด์‹ ํ˜•ํƒœ (์˜ˆ: "source.id = target.parent_id") // SQL ์ธ์ ์…˜ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ๊ฒ€์ฆ ํ•„์š” const isValidJoinCondition = /^[\w\s.=<>]+$/.test(joinCondition); if (!isValidJoinCondition) { throw new Error("Invalid join condition"); } ``` ### 3. ์ˆœํ™˜ ์ฐธ์กฐ ๋ฐฉ์ง€ ```typescript // ์กฐ์ธ์ด ์ˆœํ™˜ ์ฐธ์กฐ๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๋Š”์ง€ ๊ฒ€์ฆ async function checkCircularReference( sourceTableId: number, targetTableId: number ): Promise { // ์žฌ๊ท€์ ์œผ๋กœ ์กฐ์ธ ๊ด€๊ณ„ ํ™•์ธ // ... } ``` ### 4. LEFT JOIN์œผ๋กœ ๊ด€๋ จ ํ…Œ์ด๋ธ” ์ •๋ณด ์กฐํšŒ ์กฐ์ธ ์„ค์ • ์กฐํšŒ ์‹œ source/target ํ…Œ์ด๋ธ” ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด LEFT JOIN ์‚ฌ์šฉ --- ## ๐Ÿ“ ์ „ํ™˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ### 1๋‹จ๊ณ„: Prisma ํ˜ธ์ถœ ์ „ํ™˜ - [ ] `getEntityJoins()` - ๋ชฉ๋ก ์กฐํšŒ (findMany with include) - [ ] `getEntityJoin()` - ๋‹จ๊ฑด ์กฐํšŒ (findUnique) - [ ] `createEntityJoin()` - ์ƒ์„ฑ (create with validation) - [ ] `updateEntityJoin()` - ์ˆ˜์ • (update) - [ ] `deleteEntityJoin()` - ์‚ญ์ œ (delete) ### 2๋‹จ๊ณ„: ์ฝ”๋“œ ์ •๋ฆฌ - [ ] import ๋ฌธ ์ˆ˜์ • (`prisma` โ†’ `query, queryOne`) - [ ] ์กฐ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋กœ์ง ์œ ์ง€ - [ ] Prisma import ์™„์ „ ์ œ๊ฑฐ ### 3๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ - [ ] ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (5๊ฐœ) - [ ] ์กฐ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ํ…Œ์ŠคํŠธ - [ ] ์ˆœํ™˜ ์ฐธ์กฐ ๋ฐฉ์ง€ ํ…Œ์ŠคํŠธ - [ ] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (2๊ฐœ) ### 4๋‹จ๊ณ„: ๋ฌธ์„œํ™” - [ ] ์ „ํ™˜ ์™„๋ฃŒ ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ --- ## ๐ŸŽฏ ์˜ˆ์ƒ ๋‚œ์ด๋„ ๋ฐ ์†Œ์š” ์‹œ๊ฐ„ - **๋‚œ์ด๋„**: โญโญโญ (์ค‘๊ฐ„) - LEFT JOIN ์ฟผ๋ฆฌ - ์กฐ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ - ์ˆœํ™˜ ์ฐธ์กฐ ๋ฐฉ์ง€ - **์˜ˆ์ƒ ์†Œ์š” ์‹œ๊ฐ„**: 1์‹œ๊ฐ„ --- **์ƒํƒœ**: โณ **๋Œ€๊ธฐ ์ค‘** **ํŠน์ด์‚ฌํ•ญ**: LEFT JOIN, ์กฐ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ, ์ˆœํ™˜ ์ฐธ์กฐ ๋ฐฉ์ง€ ํฌํ•จ