๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
UpsertBuilder์˜ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์œผ๋กœ ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ณ ๊ธ‰ ํŒจํ„ด ๊ฐœ์š”

์กฐ๊ฑด๋ถ€ ๋“ฑ๋ก

๋™์  ๋ฐ์ดํ„ฐ ์ƒ์„ฑif/else ๋ถ„๊ธฐ

์ค‘์ฒฉ ๊ด€๊ณ„

3๋‹จ๊ณ„ ์ด์ƒ ๊ด€๊ณ„๊นŠ์€ ๊ณ„์ธต ๊ตฌ์กฐ

Upsert ๋™์ž‘

์ค‘๋ณต ์‹œ ์—…๋ฐ์ดํŠธonConflict ํ™œ์šฉ

๋ณตํ•ฉ ํ‚ค

์—ฌ๋Ÿฌ ํ•„๋“œ ์กฐํ•ฉUNIQUE ์ œ์•ฝ

์กฐ๊ฑด๋ถ€ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ

์—ญํ• ์— ๋”ฐ๋ฅธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ

async function createUserWithRole(data: {
  email: string;
  username: string;
  role: "admin" | "normal";
}) {
  return await db.transaction(async (trx) => {
    // 1. User ๋“ฑ๋ก
    const userRef = trx.ubRegister("users", {
      email: data.email,
      username: data.username,
      password: "password",
      role: data.role,
    });
    
    // 2. ์กฐ๊ฑด๋ถ€ Profile ์ƒ์„ฑ
    if (data.role === "admin") {
      // Admin๋งŒ AdminProfile ์ƒ์„ฑ
      trx.ubRegister("admin_profiles", {
        user_id: userRef,
        permissions: "full_access",
      });
    } else {
      // Normal์€ ์ผ๋ฐ˜ Profile ์ƒ์„ฑ
      trx.ubRegister("profiles", {
        user_id: userRef,
        bio: "",
      });
    }
    
    // 3. ์ €์žฅ
    const [userId] = await trx.ubUpsert("users");
    
    if (data.role === "admin") {
      await trx.ubUpsert("admin_profiles");
    } else {
      await trx.ubUpsert("profiles");
    }
    
    return userId;
  });
}

์˜ต์…”๋„ ๊ด€๊ณ„

async function createEmployee(data: {
  email: string;
  username: string;
  departmentId?: number; // ์„ ํƒ์ 
  mentorEmail?: string; // ์„ ํƒ์ 
}) {
  return await db.transaction(async (trx) => {
    // User
    const userRef = trx.ubRegister("users", {
      email: data.email,
      username: data.username,
      password: "pass",
      role: "normal",
    });
    
    // Mentor ์ฐพ๊ธฐ (์žˆ์œผ๋ฉด)
    let mentorId: number | null = null;
    if (data.mentorEmail) {
      const mentor = await trx
        .table("employees")
        .join("users", "employees.user_id", "users.id")
        .select({ emp_id: "employees.id" })
        .where("users.email", data.mentorEmail)
        .first();
      
      mentorId = mentor?.emp_id || null;
    }
    
    // Employee
    trx.ubRegister("employees", {
      user_id: userRef,
      department_id: data.departmentId || null,
      mentor_id: mentorId,
      employee_number: `E${Date.now()}`,
    });
    
    await trx.ubUpsert("users");
    const [empId] = await trx.ubUpsert("employees");
    
    return empId;
  });
}

๊นŠ์€ ๊ณ„์ธต ๊ตฌ์กฐ

4๋‹จ๊ณ„ ๊ด€๊ณ„ (Company โ†’ Dept โ†’ Team โ†’ Employee)

await db.transaction(async (trx) => {
  // 1. Company
  const companyRef = trx.ubRegister("companies", {
    name: "Tech Corp",
  });
  
  // 2. Department (Company ํ•˜์œ„)
  const deptRef = trx.ubRegister("departments", {
    name: "Engineering",
    company_id: companyRef,
  });
  
  // 3. Team (Department ํ•˜์œ„)
  const teamRef = trx.ubRegister("teams", {
    name: "Backend Team",
    department_id: deptRef,
  });
  
  // 4. User
  const userRef = trx.ubRegister("users", {
    email: "[email protected]",
    username: "developer",
    password: "pass",
    role: "normal",
  });
  
  // 5. Employee (Team ํ•˜์œ„)
  trx.ubRegister("employees", {
    user_id: userRef,
    team_id: teamRef,
    employee_number: "E001",
  });
  
  // 6. ์ˆœ์„œ๋Œ€๋กœ ์ €์žฅ
  await trx.ubUpsert("companies");
  await trx.ubUpsert("departments");
  await trx.ubUpsert("teams");
  await trx.ubUpsert("users");
  await trx.ubUpsert("employees");
});
๊นŠ์€ ๊ณ„์ธต ๊ตฌ์กฐ

Upsert ๋™์ž‘ (ON CONFLICT)

์ค‘๋ณต ์‹œ ์—…๋ฐ์ดํŠธ

await db.transaction(async (trx) => {
  // email์ด ์ด๋ฏธ ์กด์žฌํ•˜๋ฉด username ์—…๋ฐ์ดํŠธ
  trx.ubRegister(
    "users",
    {
      email: "[email protected]",
      username: "john_updated",
      password: "new_pass",
      role: "normal",
    },
    {
      onConflict: {
        columns: ["email"],
        update: ["username", "password"],
      },
    }
  );
  
  await trx.ubUpsert("users");
});

๋ณตํ•ฉ UNIQUE ํ‚ค

await db.transaction(async (trx) => {
  // (project_id, employee_id) ์กฐํ•ฉ์ด ์ด๋ฏธ ์กด์žฌํ•˜๋ฉด role ์—…๋ฐ์ดํŠธ
  trx.ubRegister(
    "projects__employees",
    {
      project_id: 1,
      employee_id: 5,
      role: "lead",
    },
    {
      onConflict: {
        columns: ["project_id", "employee_id"],
        update: ["role"],
      },
    }
  );
  
  await trx.ubUpsert("projects__employees");
});

DO NOTHING

await db.transaction(async (trx) => {
  // ์ค‘๋ณต์ด๋ฉด ๋ฌด์‹œ (์—๋Ÿฌ ๋ฐœ์ƒ ์•ˆ ํ•จ)
  trx.ubRegister(
    "users",
    {
      email: "[email protected]",
      username: "john",
      password: "pass",
      role: "normal",
    },
    {
      onConflict: {
        columns: ["email"],
        action: "nothing",
      },
    }
  );
  
  await trx.ubUpsert("users");
});

๋ณต์žกํ•œ ManyToMany

์กฐ์ธ ํ…Œ์ด๋ธ”์— ์ถ”๊ฐ€ ํ•„๋“œ

await db.transaction(async (trx) => {
  const projectRef = trx.ubRegister("projects", {
    name: "Project Alpha",
    status: "in_progress",
  });
  
  const empRefs = ["John", "Jane"].map((name) => {
    const userRef = trx.ubRegister("users", {
      email: `${name.toLowerCase()}@test.com`,
      username: name,
      password: "pass",
      role: "normal",
    });
    
    return trx.ubRegister("employees", {
      user_id: userRef,
      employee_number: `E${name}`,
    });
  });
  
  // ์กฐ์ธ ํ…Œ์ด๋ธ”์— role, assigned_at ํ•„๋“œ
  empRefs.forEach((empRef, i) => {
    trx.ubRegister("projects__employees", {
      project_id: projectRef,
      employee_id: empRef,
      role: i === 0 ? "lead" : "member",
      assigned_at: new Date(),
    });
  });
  
  await trx.ubUpsert("projects");
  await trx.ubUpsert("users");
  await trx.ubUpsert("employees");
  await trx.ubUpsert("projects__employees");
});

๋™์  ํ…Œ์ด๋ธ” ์„ ํƒ

ํ…Œ์ด๋ธ”๋ช…์„ ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉ

async function createRecordInTable(
  tableName: string,
  data: Record<string, any>
) {
  return await db.transaction(async (trx) => {
    trx.ubRegister(tableName as any, data);
    const [id] = await trx.ubUpsert(tableName as any);
    return id;
  });
}

// ์‚ฌ์šฉ
await createRecordInTable("users", {
  email: "[email protected]",
  username: "test",
  password: "pass",
  role: "normal",
});

์ˆœํ™˜ ์ฐธ์กฐ ๋ฐฉ์ง€

์ˆœํ™˜ ์ฐธ์กฐ๋Š” ๋ถˆ๊ฐ€๋Šฅ

// โŒ ์—๋Ÿฌ: ์ˆœํ™˜ ์ฐธ์กฐ
await db.transaction(async (trx) => {
  const dept1Ref = trx.ubRegister("departments", {
    name: "Dept A",
    parent_id: dept2Ref, // โ† dept2Ref๋ฅผ ๋จผ์ € ์ฐธ์กฐ
  });
  
  const dept2Ref = trx.ubRegister("departments", {
    name: "Dept B",
    parent_id: dept1Ref, // โ† dept1Ref๋ฅผ ๋จผ์ € ์ฐธ์กฐ
  });
  
  // ์ €์žฅ ๋ถˆ๊ฐ€๋Šฅ (์ˆœํ™˜ ์ฐธ์กฐ)
});

ํ•ด๊ฒฐ: 2๋‹จ๊ณ„ ์ €์žฅ

// โœ… ํ•ด๊ฒฐ: ์ˆœ์ฐจ ์ €์žฅ
await db.transaction(async (trx) => {
  // 1. ๋จผ์ € parent_id ์—†์ด ์ƒ์„ฑ
  trx.ubRegister("departments", {
    name: "Dept A",
    parent_id: null,
  });
  
  const [deptAId] = await trx.ubUpsert("departments");
  
  // 2. ์ƒ์„ฑ๋œ ID๋กœ ๊ด€๊ณ„ ์„ค์ •
  await trx
    .table("departments")
    .insert({
      name: "Dept B",
      parent_id: deptAId,
    });
});

๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋ฐ ๊ฒ€์ฆ

์ž…๋ ฅ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜

async function importUsers(csvData: any[]) {
  return await db.transaction(async (trx) => {
    for (const row of csvData) {
      // ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜
      const userData = {
        email: row.email.toLowerCase().trim(),
        username: row.username.trim(),
        password: hashPassword(row.password),
        role: row.role === "1" ? "admin" : "normal",
        is_verified: row.verified === "true",
      };
      
      // ๊ฒ€์ฆ
      if (!isValidEmail(userData.email)) {
        console.warn(`Invalid email: ${userData.email}`);
        continue;
      }
      
      trx.ubRegister("users", userData);
    }
    
    const userIds = await trx.ubUpsert("users");
    return { imported: userIds.length };
  });
}

๋Œ€๋Ÿ‰ ๊ด€๊ณ„ ์—…๋ฐ์ดํŠธ

๊ธฐ์กด ๊ด€๊ณ„ ์ œ๊ฑฐ + ์ƒˆ ๊ด€๊ณ„ ์ถ”๊ฐ€

async function updateProjectMembers(
  projectId: number,
  newMemberIds: number[]
) {
  return await db.transaction(async (trx) => {
    // 1. ๊ธฐ์กด ๊ด€๊ณ„ ๋ชจ๋‘ ์ œ๊ฑฐ
    await trx
      .table("projects__employees")
      .where("project_id", projectId)
      .delete();
    
    // 2. ์ƒˆ ๊ด€๊ณ„ ๋“ฑ๋ก
    for (const empId of newMemberIds) {
      trx.ubRegister("projects__employees", {
        project_id: projectId,
        employee_id: empId,
        assigned_at: new Date(),
      });
    }
    
    // 3. ์ €์žฅ
    await trx.ubUpsert("projects__employees");
    
    return { memberCount: newMemberIds.length };
  });
}

์‹ค์ „ ์˜ˆ์ œ

๋ณตํ•ฉ ๋ฐ์ดํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

async function migrateFromLegacySystem(legacyData: {
  companies: Array<{ id: number; name: string }>;
  users: Array<{
    id: number;
    email: string;
    companyId: number;
  }>;
}) {
  return await db.transaction(async (trx) => {
    // 1. Legacy Company ID โ†’ UBRef ๋งคํ•‘
    const companyRefMap = new Map<number, any>();
    
    for (const company of legacyData.companies) {
      const ref = trx.ubRegister("companies", {
        name: company.name,
        legacy_id: company.id,
      });
      companyRefMap.set(company.id, ref);
    }
    
    // 2. Users ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
    for (const user of legacyData.users) {
      const companyRef = companyRefMap.get(user.companyId);
      
      trx.ubRegister("users", {
        email: user.email,
        username: user.email.split("@")[0],
        password: "temp_password",
        role: "normal",
        legacy_id: user.id,
      });
    }
    
    // 3. ์ €์žฅ
    const companyIds = await trx.ubUpsert("companies");
    const userIds = await trx.ubUpsert("users");
    
    return {
      companies: companyIds.length,
      users: userIds.length,
    };
  });
}

์กฐ์ง ์žฌํŽธ์„ฑ

async function reorganizeDepartments(changes: Array<{
  departmentId: number;
  newParentId: number | null;
  newName?: string;
}>) {
  return await db.transaction(async (trx) => {
    for (const change of changes) {
      await trx
        .table("departments")
        .where("id", change.departmentId)
        .update({
          parent_id: change.newParentId,
          ...(change.newName && { name: change.newName }),
        });
    }
    
    return { updated: changes.length };
  });
}

๋ณต์žกํ•œ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ

async function createInitialData() {
  await db.transaction(async (trx) => {
    // 1. System User
    const systemUserRef = trx.ubRegister("users", {
      email: "system@internal",
      username: "system",
      password: "n/a",
      role: "admin",
    });
    
    // 2. Default Company
    const companyRef = trx.ubRegister("companies", {
      name: "Default Company",
    });
    
    // 3. Departments
    const depts = ["Engineering", "Sales", "HR"];
    const deptRefs = depts.map((name) =>
      trx.ubRegister("departments", {
        name,
        company_id: companyRef,
      })
    );
    
    // 4. Default Projects
    const projects = [
      { name: "Onboarding", status: "active" },
      { name: "Training", status: "active" },
    ];
    
    for (const project of projects) {
      trx.ubRegister("projects", project);
    }
    
    // 5. ์ €์žฅ
    await trx.ubUpsert("users");
    await trx.ubUpsert("companies");
    await trx.ubUpsert("departments");
    await trx.ubUpsert("projects");
    
    console.log("โœ… Initial data created");
  });
}

์—๋Ÿฌ ์ฒ˜๋ฆฌ ํŒจํ„ด

๋กค๋ฐฑ ํ›„ ์žฌ์‹œ๋„

async function createWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      
      console.warn(`Retry ${i + 1}/${maxRetries}:`, error.message);
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }
  throw new Error("Max retries exceeded");
}

// ์‚ฌ์šฉ
await createWithRetry(async () => {
  return await db.transaction(async (trx) => {
    trx.ubRegister("users", { ... });
    await trx.ubUpsert("users");
  });
});

๋ถ€๋ถ„ ์„ฑ๊ณต ์ฒ˜๋ฆฌ

async function importWithPartialSuccess(data: any[]) {
  const results = {
    success: [] as number[],
    failed: [] as { data: any; error: string }[],
  };
  
  for (const item of data) {
    try {
      const id = await db.transaction(async (trx) => {
        trx.ubRegister("users", item);
        const [userId] = await trx.ubUpsert("users");
        return userId;
      });
      results.success.push(id);
    } catch (error) {
      results.failed.push({
        data: item,
        error: error.message,
      });
    }
  }
  
  return results;
}

๋‹ค์Œ ๋‹จ๊ณ„