참여 제약 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""참여 제약 검증.

문서: 25.참여 제약 (예시로 보기, COMPANY 표, 카드 C3)
출처: Elmasri & Navathe, Fundamentals of Database Systems 3장의 WORKS_FOR, MANAGES 예와 COMPANY 상태
주장:
  1. WORKS_FOR: 직원 e1~e7이 모두 선에 이어져 있다(전체 참여 규칙과 맞는다).
  2. MANAGES: 직원 7명 중 e3, e5, e6 세 명만 이어져 있다(부분 참여). 부서 d1~d3은 모두 이어져 있다.
  3. 전체 참여 규칙이 걸린 쪽에 선이 없는 개체를 넣으면 규칙에 어긋난다.
  4. COMPANY 상태: 직원 8명 모두 부서가 있다. 부서를 관리하는 직원은 3명, 부양가족이 있는 직원은 3명이다.
  5. 상태만 보고 규칙을 알 수는 없다: CONTROLS에서 부서는 부분 참여 규칙인데(문서의 COMPANY 표),
     이 상태에서는 세 부서가 모두 프로젝트를 하나 이상 관리한다.
"""

EMPLOYEE = [f"e{i}" for i in range(1, 8)]
DEPARTMENT = ["d1", "d2", "d3"]
WORKS_FOR = [("e1", "d1"), ("e2", "d2"), ("e3", "d1"), ("e4", "d2"), ("e5", "d3"), ("e6", "d1"), ("e7", "d3")]
MANAGES = [("e3", "d1"), ("e6", "d2"), ("e5", "d3")]


def missing(entities, pairs, side):
    """관계에 한 번도 끼지 않은 개체들."""
    seen = {p[side] for p in pairs}
    return [e for e in entities if e not in seen]


def total(entities, pairs, side):
    return not missing(entities, pairs, side)


# COMPANY 상태
EMP_DNO = {"123456789": 5, "333445555": 5, "999887777": 4, "987654321": 4,
           "666884444": 5, "453453453": 5, "987987987": 4, "888665555": 1}
DEPT_MGR = {5: "333445555", 4: "987654321", 1: "888665555"}
PROJ_DNUM = {1: 5, 2: 5, 3: 5, 10: 4, 20: 1, 30: 4}
DEPENDENT_ESSN = ["333445555", "333445555", "333445555", "987654321", "123456789", "123456789", "123456789"]


def main():
    assert total(EMPLOYEE, WORKS_FOR, 0) and total(DEPARTMENT, WORKS_FOR, 1)
    print("[OK] WORKS_FOR: 직원 7명, 부서 3개가 모두 이어져 있다")

    assert missing(EMPLOYEE, MANAGES, 0) == ["e1", "e2", "e4", "e7"]
    assert not total(EMPLOYEE, MANAGES, 0) and total(DEPARTMENT, MANAGES, 1)
    print("[OK] MANAGES: 직원은 e3, e5, e6만 이어져 있고(부분), 부서는 모두 이어져 있다")

    # 선이 없는 직원 e8을 넣으면 WORKS_FOR의 전체 참여 규칙에 어긋난다
    assert missing(EMPLOYEE + ["e8"], WORKS_FOR, 0) == ["e8"]
    print("[OK] 선 없는 e8을 넣으면 전체 참여가 깨진다")

    emps = list(EMP_DNO)
    works_for = [(e, d) for e, d in EMP_DNO.items()]
    manages = [(m, d) for d, m in DEPT_MGR.items()]
    controls = [(d, p) for p, d in PROJ_DNUM.items()]
    dependents_of = [(e, i) for i, e in enumerate(DEPENDENT_ESSN)]
    assert total(emps, works_for, 0) and total(list(DEPT_MGR), works_for, 1)
    assert len(emps) - len(missing(emps, manages, 0)) == 3 and total(list(DEPT_MGR), manages, 1)
    assert len(emps) - len(missing(emps, dependents_of, 0)) == 3
    print("[OK] COMPANY 상태: 직원 8명 모두 부서가 있다. 관리자 3명, 부양가족이 있는 직원 3명")

    assert total(list(PROJ_DNUM), controls, 1)
    assert total(list(DEPT_MGR), controls, 0)   # 규칙은 부분 참여지만 이 상태에서는 모두 낀다
    print("[OK] CONTROLS: 이 상태에서는 세 부서가 모두 프로젝트를 관리한다(규칙은 부분 참여)")

    print("ALL CHECKS PASSED")


if __name__ == "__main__":
    main()