Skip to content

API 참조 (한국어)

🌐 API_REFERENCE.md의 번역입니다. 영어 원문이 표준이며, CI가 영어 원문과의 구조 일치를 검사합니다.

CUBRID용 순수 Python DB-API 2.0 드라이버 pycubrid의 완전한 API 문서.


목차


모듈 수준 속성

PEP 249가 요구하는 속성들이 모듈 수준에 정의되어 있습니다.

속성 값 설명
apilevel "2.0" DB-API 사양 버전
threadsafety 1 스레드는 모듈을 공유할 수 있으나 연결은 공유 불가
paramstyle "qmark" 물음표 파라미터 방식: WHERE name = ?
__version__ "1.8.0" 패키지 버전 문자열
import pycubrid

print(pycubrid.apilevel)      # "2.0"
print(pycubrid.threadsafety)  # 1
print(pycubrid.paramstyle)    # "qmark"
print(pycubrid.__version__)   # "1.8.0"

모듈 수준 생성자

pycubrid.connect()

def connect(
    host: str = "localhost",
    port: int = 33000,
    database: str = "",
    user: str = "dba",
    password: str = "",
    decode_collections: bool = False,
    json_deserializer: Any = None,
    ssl: bool | ssl_module.SSLContext | None = None,
    charset: str = "utf-8",
    **kwargs: Any,
) -> Connection

새 데이터베이스 연결을 만듭니다.

파라미터:

파라미터 타입 기본값 설명
host str "localhost" CUBRID 서버 호스트명 또는 IP 주소
port int 33000 CUBRID 브로커 포트
database str "" 데이터베이스 이름
user str "dba" 데이터베이스 사용자
password str "" 데이터베이스 비밀번호
decode_collections bool False SET/MULTISET/SEQUENCE 컬럼을 Python 컬렉션으로 디코딩
json_deserializer Any None fetch 시 JSON 컬럼을 디코딩하는 콜러블. 미설정 시 JSON은 str로 반환
charset str "utf-8" SQL 텍스트, 자격 증명, 문자 값, 이름, 오류 텍스트의 코덱. charset 참고
ssl bool \| ssl_module.SSLContext \| None None 동기·비동기 브로커 연결의 옵트인 TLS. True면 TLS 1.2 최소의 기본 검증 컨텍스트 사용. 연결은 CUBRID의 STARTTLS 방식 업그레이드를 사용 — 평문 CUBRS 핸드셰이크 후 OPEN_DATABASE 전에 TLS 업그레이드. 연결 가이드 참고.
**kwargs Any — connect_timeout, read_timeout, fetch_size, enable_timing, no_backslash_escapes, autocommit 등 추가 파라미터. 인식하지 못한 키워드는 무시되지만 경고로 알립니다 — 알 수 없는 옵션 참고

알 수 없는 옵션

지원되는 연결 옵션이 아닌 키워드는 무시되지만, 그 이름을 담은 UnknownConnectionOptionWarning을 냅니다(비슷한 이름이 있으면 철자 제안 포함). 그래서 read_timout=30 같은 오타가 조용히 묻히지 않습니다. pycubrid.aio.connect()와 Connection(...) / AsyncConnection(...) 직접 생성에도 똑같이 적용됩니다. 경고를 오류로 올리거나 끄는 방법은 알 수 없는 옵션을 참고하세요.

decode_collections

False(기본값)이면 컬렉션 컬럼이 하위 호환을 위해 raw CAS 와이어 bytes로 반환됩니다. True이면 pycubrid가 지원되는 SET, MULTISET, SEQUENCE 페이로드를 Python 컨테이너로 디코딩합니다.

JSON 컬럼

json_deserializer가 fetch 시 JSON 컬럼 디코딩을 제어합니다:

설정 결과
None (기본) JSON 컬럼을 str로 반환
callable raw JSON 문자열을 콜러블에 전달하고 그 결과 반환

charset

charset(기본값 "utf-8")은 SQL 텍스트(렌더링된 파라미터 포함), 배치·스키마 정보 인자, OPEN_DATABASE 자격 증명, 문자 값(CHAR, VARCHAR, STRING, NCHAR, NCHAR VARYING, ENUM, 컬렉션 요소), 컬럼·테이블 이름, 기본값, 서버 오류 텍스트에 사용하는 Python 코덱입니다. 데이터베이스 문자셋으로 설정하세요. 예를 들어 ko_KR.euckr로 만든 데이터베이스에는 charset="euckr"를 사용합니다(#86).

  • Python 코덱 이름, CUBRID 이름 utf8, euckr, iso88591, 그리고 "ko_KR.euckr" 같은 CUBRID 로케일(점 뒤 부분 사용)을 받으며 Python 코덱 이름("euc_kr")으로 정규화합니다. None은 기본값을 뜻합니다. 소켓 작업 전에 검증합니다: 문자열이 아니면 TypeError, 알 수 없는 코덱·CUBRID binary·ASCII 투명하지 않은 코덱(UTF-16/32, UTF-7, Shift_JIS, Big5, GBK, GB18030, CP949, ISO-2022 등)은 ValueError, 코덱으로 인코딩할 수 없는 자격 증명은 DataError입니다.
  • 인코딩할 수 없는 텍스트는 모든 경로(일반·compat.native 커서, get_schema_info())에서 해당 요청의 어떤 바이트도 보내기 전에 DataError를 발생시키며 세션은 계속 사용할 수 있습니다. euc_kr에서는 KS X 1001 밖의 한글 음절(예: 똠, 뷁)도 인코딩할 수 없는 것으로 취급합니다(Python은 이를 8바이트 조합 시퀀스로 인코딩함).
  • 디코딩할 수 없는 바이트는 코덱 이름을 담은 DataError를 발생시킵니다. 일반 커서는 세션을 유지합니다. get_schema_info()는 해석할 수 없는 FC9 응답을 받으면 연결을 폐기하고, 명시적 prepared API(pycubrid.compat.native)는 세션을 폐기하고 OperationalError를 발생시킵니다. 오류 텍스트와 LOB 파일 로케이터는 errors="replace"로 디코딩합니다.
  • 가져온 JSON 값은 UTF-8이지만 JSON 파라미터는 SQL 텍스트이므로 연결 코덱으로 인코딩됩니다(euckr에서 JSON 안의 이모지는 삽입 시 DataError). NUMERIC, 타임존 이름, 버전 문자열, LOB 내용은 영향을 받지 않으며(CLOB 바이트는 컬럼 문자셋), pycubrid.Binary(str)는 항상 UTF-8로 인코딩합니다.
  • 브로커는 변환하지 않으므로 EUC-KR 데이터베이스의 CHARSET utf8 컬럼은 charset="euckr"에서 DataError를 발생시킵니다. SQL에서 CAST(col AS VARCHAR(n) CHARSET euckr)로 변환하세요.

전체 계약은 문자 인코딩을 참고하세요.

반환: 새 Connection 인스턴스.

발생: 연결을 수립할 수 없으면 OperationalError.

import pycubrid

# 최소 연결
conn = pycubrid.connect(database="testdb")

# 타임아웃 포함 전체 연결
conn = pycubrid.connect(
    host="192.168.1.100",
    port=33000,
    database="production",
    user="app_user",
    password="secret",
    connect_timeout=5.0,
)

명시적 네이티브 호환 기능

Deprecated. pycubrid.compat은 FutureWarning을 내며 이후의 1.x MINOR 릴리스에서 제거됩니다. 마이그레이션 가이드를 참고하세요.

옵트인 pycubrid.compat.native는 순수 Python 동기 전송 위에 스칼라 값 (None, bool, int, float, Decimal, str, bytes/bytearray, date, time, datetime; #840부터 타입 지정, Prepared 호스트 변수 매핑 참고), SET/MULTISET/SEQUENCE 컬렉션 값과 조회한 BLOB/CLOB 핸들만 지원하는 동기 prepared 커서를 제공합니다. 문자열은 연결 문자셋을 사용합니다. 기존 pycubrid.connect()와 pycubrid.aio의 execute()는 그대로 FC41을 사용합니다. pycubrid.compat.cubriddb 래퍼는 연결 생성·종료, 명시적 commit/rollback, 서버 버전/query ping과 autocommit 설정을 지원하며 한정된 래퍼 행 커서도 제공합니다. DB-API 전역 값, 스레드 공유 보장 또는 네이티브 C 확장과의 완전한 동등성은 제공하지 않습니다.

native.connect(url, user="public", passwd="", *, charset="utf-8")는 native.connection을 반환합니다. 래퍼의 cubriddb.Connect/connect/connection(*args, **kwargs)는 cubriddb.Connection(dsn="", user="public", password="", charset="utf8")을 반환합니다. 래퍼의 .connection은 단일 전송을 소유하는 바로 그 네이티브 형태의 객체입니다. 팩터리의 위치 인자 최대 세 개는 대응하는 dsn/user/password 키워드를 덮어씁니다. 두 표면은 서버에 적용되는 autocommit이 켜진 상태로 시작하며 기존 드라이버의 dba/수동 커밋 기본값은 바꾸지 않습니다.

마지막 콜론이 포함된 CUBRID:host:port:database:user:password: 형식을 사용합니다. DSN 안의 계정보다 Python 인자가 우선하며 생략 시에도 public/빈 비밀번호 기본값이 사용됩니다. 래퍼의 charset(기본값 "utf8")은 드라이버의 charset 옵션으로 전달되므로 "euckr" 같은 CUBRID 이름을 쓸 수 있고, 잘못된 코덱은 연결 전에 실패합니다. 기본 CUBRID 백엔드만 허용하며, 다른 백엔드, HA/TLS URL 옵션과 초과 인자는 연결 전에 거부하며 오류에 계정 정보가 담긴 DSN 원문을 노출하지 않습니다.

네이티브 연결은 cursor(), set(), lob(), commit(), rollback(), set_autocommit(bool), set_isolation_level(level), close()를 제공합니다. 연결 유틸리티는 아래에서 설명합니다. 커서는 prepare(sql), 1부터 시작하는 bind_param(index, value, bind_type=0), bind_set(index, s), bind_lob(index, lob), execute(option=0, max_col_size=0) -> int, 튜플만 반환하는 fetch_row(how=0), fetch_lob(col, lob), result_info([n]), data_seek(n), row_seek(offset), row_tell(), close()를 지원합니다. bind_param()은 bool을 INT 1/0으로, int를 INT, BIGINT 또는 (64비트를 넘고 38자리 이하이면) NUMERIC으로, float를 DOUBLE로, Decimal을 NUMERIC 텍스트로, date/time/datetime을 DATE, TIME, DATETIME(tz 포함 datetime은 DATETIMETZ)으로, bytes/bytearray를 VARBIT로 보냅니다. nan/inf, 위조된 하위 클래스와 그 밖의 객체는 ProgrammingError, 38자리가 넘게 필요한 값은 DataError, 빈 bytes는 브로커가 NULL로 읽으므로 NotSupportedError입니다. 서버 측 호스트 변수 변환은 리터럴 경로와 다를 수 있습니다(예: float는 NUMERIC 리터럴이 아니라 DOUBLE). 차이는 위에 링크한 매핑에 정리되어 있습니다. 기본값이 아닌 플래그와 다른 Python 값 형식은 실행 전 거부합니다. 브로커가 현재 세션의 statement pooling을 알리지 않거나 비활성화한 경우 FC2 전에 거부합니다. 핸들은 물리 CAS 세션에 묶이며 재접속 뒤 자동 재실행하지 않고, 실패한 트랜잭션을 재시도로 바꾸지도 않습니다. commit()은 HOLDABLE SELECT 결과를 유지하고 rollback()은 버퍼에 든 행까지 무효화합니다. 연결은 기본적으로 autocommit이 켜져 있습니다. 브로커가 반환한 prepared 오류는 DB-API 예외 종류·코드·errno·SQLSTATE를 유지하지만 SQL이나 값이 포함될 수 있는 오류 문구는 가립니다. LOB이 없는 준비 문장의 완전한 브로커 실행 오류가 나면 현재 호출은 원래의 가려진 오류를 발생시키고 실행을 다시 보내지 않습니다. 다음 명시적인 사용자 execute()에서만 같은 물리 세션의 이전 핸들을 닫고 동일 SQL을 다시 준비한 뒤, 파라미터 개수가 같으면 이미 바인딩된 스칼라/컬렉션 값을 복원하고 그 새 호출을 한 번 실행합니다. 이로써 실패한 호출 안에서 이미 효과를 냈을 수도 있는 문장을 재실행하지 않고도 반복되는 변환 오류를 바로잡습니다. 이는 CCI의 같은 호출 내 invalid-plan 재시도와 의도적으로 다릅니다. 전송 오류, 세션 교체, 핸들 닫기/준비 실패, 파라미터 개수 변경은 안전하게 실패합니다. 임시 LOB 핸들이 소비됐을 수 있으므로 LOB 바인딩은 오류 뒤 명시적으로 다시 prepare()하고 바인딩해야 합니다. 일반 FC41과 비동기 동작은 바뀌지 않습니다. 고정된 공식 네이티브 확장은 bind_param(None)에서 SystemError를 내지만 이 제한된 구현은 SQL NULL을 명시적으로 바인딩합니다. 이는 네이티브 NULL 동등성 주장이 아닌 안전한 차이입니다. 이는 범용 DB-API 커서나 비동기 prepared API가 아닙니다. 자세한 범위는 typed CAS 설계와 호환성 가이드를 참고하세요.

from pycubrid.compat import native

conn = native.connect("CUBRID:localhost:33000:testdb:::", "dba", "")
try:
    cur = conn.cursor()
    try:
        cur.prepare("SELECT CAST(? AS INTEGER)")
        cur.bind_param(1, 42)
        cur.execute()
        assert cur.fetch_row() == (42,)
    finally:
        cur.close()
finally:
    conn.close()

네이티브 커서 위치 이동 (data_seek, row_seek, row_tell)

동기 네이티브 전용 메서드는 유효한 현재 SELECT 결과에서 동작하며 직접 행을 반환하지 않습니다. data_seek(n)는 1부터 시작하는 절대 행을 선택하고 두 위치를 n으로 맞춥니다. row_seek(offset)은 물리적 다음 행에 대한 상대 이동이며 둘 다 None을 반환합니다. row_tell()은 별도의 공식 shadow 카운터를 반환합니다. 처음에는 0이며 성공한 fetch마다 증가하고, 같은 핸들의 재실행으로 물리 위치가 1로 돌아가도 유지됩니다. 새 prepare()는 초기화합니다. 따라서 항상 다음 행의 절대 번호를 뜻하지 않습니다.

1..N 밖의 절대 번호는 위치 변경 없이 InterfaceError(code=-30006)를 냅니다. 범위 밖 상대 이동은 물리 위치를 첫 행 전·마지막 행 뒤로 맞춘 뒤 코드 -20005를 내며 shadow는 유지합니다. 유효한 seek로 복구하기 전 fetch는 None입니다. shadow가 N보다 크면 row_tell()은 코드 -30012로 실패합니다. 일반 순회는 tellN, 1로 seek한 뒤 전체 순회는 shadowN+1과 tell 오류로 끝나는 공식 동작을 유지합니다.

브로커의 다른 페이지로 이동해야 하면 prepare·execute 전에 명시적으로 conn.set_autocommit(False)를 사용하고 동일 소유자·결과가 살아 있어야 합니다. 기본 autocommitTrue는 바뀌지 않습니다. 브로커가 마지막 배치를 보내면 클라이언트가 버퍼의 모든 행을 소비하기 전에도 결과를 해제할 수 있습니다. 그러면 로컬 seek는 성공할 수 있어도 나중의 페이지 밖 FETCH는 실패합니다. 고정 공식 드라이버의 기본 모드 진단에서도 이 제한을 관측했으며 기본 모드·트랜잭션 경계·세션 손실 뒤 무조건적인 재조회는 보장하지 않습니다. 캐시 행 조회만으로 브로커 결과 수명이 유지된다고 판단하지 않습니다.

seek는 로컬 위치만 바꾸며 한 개의 응답 페이지 밖 fetch는 기존 절대 FC8을 사용하고 준비·실행·재접속하지 않습니다. 보관 페이지·스트리밍 메모리 지표는 후보 드라이버 전용이며 공식 C 확장은 공개 페이지 지표가 없습니다. 요청 fetch 크기는 서버 행 수의 상한이 아니며 fetchall()의 O(N) 반환 목록은 seek 캐시와 별개입니다.

data_seek/row_seek는 C-int32·index 형식 위치 인자 하나, row_tell은 인자 없음만 받으며 키워드는 거부합니다. 닫힘 검사는 위치 인자 변환보다 먼저이며 메시지 하나의 args와 숫자 .code를 유지합니다. 새·비SELECT·rollback·끊김·다른·오래된 소유자 결과는 안전하게 실패합니다. 소유자·결과·위치를 바꾸는 선택자 __index__ 콜백과 정의되지 않은 C 정수 덧셈 overflow를 재현하지 않습니다. 메타데이터·description·일반·비동기 커서는 유지하며 기존 래퍼는 내부 _cs로 fetch할 뿐 공개 이동 메서드는 추가하지 않습니다.

확장 컬럼 메타데이터 (result_info)

native.cursor.result_info()는 서버 요청이나 행 이동 없이 마지막으로 성공한 준비 실행의 캐시된 메타데이터를 읽습니다. n 생략 또는 0은 모든 컬럼 튜플을 담은 튜플을 반환하고, 1부터 시작하는 번호는 컬럼 하나를 담은 바깥 튜플을 반환합니다. 행이 없는 SELECT도 메타데이터를 가집니다. 성공한 컬럼 없는 DML은 음수나 큰 범위 내 번호에도 None을 반환합니다. 정수 변환은 먼저 수행합니다. bool과 __index__는 허용하지만 None, 문자열, 실수와 __int__만 있는 객체는 허용하지 않으며 signed C-int32 범위를 벗어나면 OverflowError가 납니다. 위치 인자 0개 또는 1개만 받습니다.

컬럼마다 공식 구현의 순서대로 아래 15개 값을 반환합니다. 공식 docstring의 다른 순서는 사용하지 않습니다.

(cci_type, not_null, scale, precision, name, real_attribute, class_name,
 default_value, auto_increment, unique_key, primary_key, foreign_key,
 reverse_index, reverse_unique, shared)

타입과 플래그는 bool이 아닌 정수입니다. cci_type은 일반 DB-API의 정규화된 타입이 아닌 실제 CCI 확장 타입입니다. 소유한 10.2/11.4 서버에서 INTEGER SET/MULTISET/SEQUENCE는 40/72/104, JSON은 130으로 관측했습니다. 속성·클래스· 기본값 문자열을 실제 전달값 그대로 반환하며 없는 텍스트는 None이 아닌 ""입니다. 별칭으로 실제 속성 이름을 추측하거나 "NULL" 같은 기본값 문자열을 Python 값으로 변환하지 않습니다.

새 커서·준비만 한 커서·잘못된 번호는 .code == -30006인 InterfaceError를 내고, 닫힌 커서는 위치 인자 개수·변환보다 먼저 .code == -30019로 실패합니다. 키워드는 닫힌 커서에서도 본문 진입 전에 TypeError를 냅니다. 이러한 로컬 오류의 args는 공식 확장의 (code, message)가 아닌 pycubrid의 메시지 하나이며, InterfaceError는 .code를 가지지만 .errno를 새로 만들어 붙이지 않습니다.

EOF와 동일 소유자 commit·rollback은 메타데이터를 유지하며 rollback으로 행이 무효화돼도 조회할 수 있습니다. 실행 전 로컬 검증 실패는 이전 메타데이터를 유지하지만 실제 실행 시도의 실패는 다음 실행 성공까지 숨깁니다. 닫힌·끊긴 연결, 다른 소유자와 오래된 물리 세션은 탐지 요청이나 재접속 없이 안전하게 실패합니다. 일반 커서·한정된 래퍼의 description은 바뀌지 않습니다. 텍스트는 연결 코덱을 유지하며 고정 공식 드라이버와의 UTF-8·Unicode 비교로 비UTF-8 동등성을 주장하지 않습니다. 스키마나 위치 이동 API가 아닌 동기 네이티브 전용 추가 기능입니다.

캐시된 설정과 실제 설정자

native.connection의 쓰기 가능한 네 멤버 autocommit, isolation_level, lock_timeout, max_string_len은 캐시 스냅샷입니다. 임의의 Python 객체를 직접 대입해도 그 객체만 보존하고 브로커 요청이나 실제 설정 변경은 하지 않습니다. 초기 autocommit은 실제 연결 모드에서, 나머지 세 값은 DB 파라미터 조회로 얻습니다. max_string_len 조회에 대해 서버가 완전한 오류 응답을 준 경우에만 0으로 처리하며, 전송·응답 구조 오류는 연결 생성을 실패시킵니다. 고정된 공식 확장처럼 서버의 실제 READ COMMITTED 숫자 수준이 4인 새 연결도 초기 문자열 캐시에는 "CUBRID_TRAN_UNKNOWN_ISOLATION"을 표시합니다. 실제 수준은 4로 유지되며, set_isolation_level(4)를 부르면 표기가 고쳐집니다.

실제 모드는 conn.set_autocommit(mode, /)에 True 또는 False를 전달해 바꿉니다. 정확한 bool만 받고 성공 시 None을 반환하며 캐시도 갱신합니다. CCI와 같이 실제 모드가 같으면 로컬에서 끝나고, 모드가 달라질 때 활성 트랜잭션이 있는 경우에만 COMMIT을 보냅니다. 이후 prepared 실행은 새 모드를 사용합니다. 일반 Connection.autocommit 세터의 기존 SET+COMMIT 동작은 바뀌지 않습니다. set_isolation_level(level, /)은 숫자 4, 5, 6이나 해당 CUBRIDIsolationLevel 멤버를 받고 None을 반환합니다. 실제 세션 수준을 SET_DB_PARAMETER로 바꾸되 현재 트랜잭션은 커밋하지 않습니다. 실제 수준이 같으면 문자열 캐시만 갱신하며 물리 세션이 교체되면 요청 수준을 다시 설정합니다. 다른 입력 형식과 과거 격리 수준은 안전하지 않은 공식 C 인자 파서 동작을 따르지 않고 요청 전에 거부합니다. lock_timeout/max_string_len의 실제 설정자는 제공하지 않습니다.

래퍼의 get_autocommit()과 .autocommit getter는 직접 대입된 객체까지 네이티브 캐시 값을 그대로 반환합니다. 반대로 wrapper.set_autocommit(value)와 wrapper.autocommit = value는 bool만 받고(그 외에는 ValueError) 네이티브의 실제 설정자에 위임합니다. 공식 드라이버에서 실제 동작 변경을 의도한 멤버 직접 대입은 명시적 설정자 호출로 바꾸고 캐시 조회와 실제 상태 확인을 구분하세요. 일반 pycubrid.connect()의 수동 커밋 기본값은 변하지 않습니다.

from pycubrid.compat import cubriddb, native

conn = native.connect("CUBRID:localhost:33000:testdb:::", "dba", "")
try:
    conn.autocommit = False       # 캐시만 변경, SQL은 여전히 자동 커밋
    conn.set_autocommit(False)   # 실제 수동 트랜잭션 모드
    conn.set_isolation_level(5)  # REPEATABLE READ, 암묵적 커밋 없음
    conn.rollback()
finally:
    conn.close()

wrapper = cubriddb.Connect("CUBRID:localhost:33000:testdb:::", "dba", "")
try:
    wrapper.autocommit = False   # 직접 캐시 대입과 달리 실제 설정자 호출
finally:
    wrapper.close()

네이티브 연결 유틸리티 (#666)

네이티브 연결은 인자가 없는 server_version() -> str, client_version() -> str, ping() -> int를 제공합니다. 래퍼는 server_version()과 ping()만 제공하며 정확한 네이티브 소유자에 한 번 위임합니다. 잘못된 위치·키워드 인자는 연결·네트워크 작업 전에 실패합니다.

server_version()은 매번 FC15로 유효한 서버 문자열 전체를 조회합니다. 공식 확장의 고정 크기 C 버퍼는 이를 자를 수 있습니다. client_version()은 이 드라이버의 고정된 패키지 버전 문자열이며 I/O 없이 연결 종료 후에도 사용할 수 있습니다. 공식 빌드 ID나 네 부분 버전 형식을 약속하지 않습니다. ping()은 고정된 select 1+1 from db_root를 실행하여 기대한 값을 찾으면 정수 1, 아니면 0을 반환합니다. 쿼리 오류는 전달하며 일반 Connection.ping()의 CHECK_CAS/bool/선택적 복구 동작을 대신하지 않습니다.

실제 요청은 직접 대입 가능한 네이티브 캐시가 아닌 실제 autocommit 모드와 현재의 정확한 드라이버·소켓·물리 세대를 사용합니다. 재시도·재접속 또는 명시적인 commit/rollback은 하지 않습니다. 활성 private 스키마 결과가 있으면 실제 autocommit 유틸리티 요청을 거부하며, 네이티브 schema_info()는 결과를 열어 두지 않습니다. 종료되거나 교체된 소유자는 다른 세션을 쓰는 대신 실패합니다. Ping은 소비한 결과 페이지를 검증하고 아직 소유한 핸들만 닫은 뒤 성공을 반환합니다. 트랜잭션으로 이미 폐기된 핸들은 다시 닫지 않습니다. 정리만 실패하면 오류를 전달하지만 부차적인 정리 오류가 앞선 쿼리 오류를 대체하지 않습니다. 일반·비동기 유틸리티, 기본값과 스레드 공유 약속은 바꾸지 않습니다. 한정된 비교 사례는 기본 True와 명시적 수동 False 모드의 정상 네이티브·래퍼 호출을 다룹니다. Client ID는 드라이버별 별도 관측이며 ID 일치, 복구, 오류 프로파일 또는 upstream 정규식의 인증이 아닙니다.

네이티브 schema_info (#1011)

native.connection.schema_info(schema_type, class_name, attr_name, /)는 고정한 공식 _cubrid.connection.schema_info처럼 첫 번째 스키마 행을 list로, 행이 없으면 None을 반환합니다. 래퍼는 이 메서드를 제공하지 않으므로 wrapper.connection.schema_info(...)를 사용합니다. 일반 get_schema_info(), fetch_schema_info(), close_schema_info()는 바뀌지 않습니다.

인자 허용 I/O 전에 거부
schema_type CLASS(1), VCLASS(2), ATTRIBUTE(4), CLASS_ATTRIBUTE(5). CCISchemaType 멤버나 True처럼 __index__가 있는 값 정수가 아니면 TypeError, 부호 있는 32비트 범위 밖은 OverflowError, 1–20 밖은 InterfaceError, 범위 안의 나머지 형식(3, 6–20)은 NotSupportedError
class_name 필수 str, 그대로 전송(CLASS/VCLASS는 %/_ 패턴) 누락 또는 str이 아님(None 포함): TypeError, NUL: ValueError, 연결 문자 집합으로 인코딩 불가: DataError
attr_name 선택 str, 주었을 때만 전송(ATTRIBUTE/CLASS_ATTRIBUTE는 패턴) str이 아님(명시적 None 포함): TypeError, NUL: ValueError, 인코딩 불가: DataError

str 하위 클래스는 저장된 문자열 그대로 검증·전송합니다. 키워드 인자는 TypeError로 거부합니다. 공식 _cubrid.CUBRID_SCH_* 상수는 내보내지 않으므로 정수나 pycubrid.constants.CCISchemaType 멤버를 넘깁니다. 공식 확장처럼 CLASS/VCLASS는 패턴 플래그 1(클래스 이름), ATTRIBUTE/CLASS_ATTRIBUTE는 플래그 2(속성 이름)를 보냅니다. 행 값은 str, int, None입니다. CLASS/VCLASS는 [NAME, TYPE, REMARKS], ATTRIBUTE/CLASS_ATTRIBUTE는 14개 열 [ATTR_NAME, DOMAIN, SCALE, PRECISION, INDEXED, NON_NULL, SHARED, UNIQUE, DEFAULT, ATTR_ORDER, CLASS_NAME, SOURCE_CLASS, IS_KEY, REMARKS]입니다. CCI처럼 DOMAIN은 와이어 형식의 컬렉션·프로토콜 비트를 지우므로 SET(INT) 열은 8을 보고합니다. CUBRID 11.x는 클래스 이름에 소유자를 붙이고(dba.t) 10.2는 붙이지 않습니다. STRING/SHORT/INT가 아닌 결과 열은 NotSupportedError를 발생시킵니다.

호출은 연결의 현재 물리 세션과 실제 autocommit 모드에서 실행하며 커밋, 재시도, 재접속, 재실행을 하지 않습니다. FC9를 보내고 행이 있을 때만 FETCH를 한 번 보낸 뒤, 행이 없을 때와 FETCH 오류 후에도 반환 전에 서버 핸들을 항상 닫습니다(공식 확장은 이 경로에서 핸들을 열어 둡니다). 완전한 서버 오류는 전달하고 세션을 유지하며, 잘못된 응답이나 닫기 실패는 세션을 폐기합니다. 같은 연결의 열린 prepared 결과는 계속 사용할 수 있습니다.

from pycubrid.compat import native
from pycubrid.constants import CCISchemaType

conn = native.connect("CUBRID:localhost:33000:testdb:::", "dba", "")
try:
    table = conn.schema_info(CCISchemaType.CLASS, "users")  # ['dba.users', 2, None]
    column = conn.schema_info(4, "users", "email")  # 첫 ATTRIBUTE 행 또는 None
finally:
    conn.close()

래퍼 트랜잭션 경계 (#662)

cubriddb.Connection.commit() -> None과 rollback() -> None은 인자를 받지 않습니다. 정확히 소유한 네이티브 연결에 한 번 위임하고 None을 반환하며, 새로 감싸지 않고 그 예외를 전달합니다. 추가 위치·키워드 인자는 위임 전에 실패하고, 닫힌 소유자 검사는 기존 네이티브 동작을 따릅니다. 미완료 작업을 다루려면 먼저 set_autocommit(False) 또는 .autocommit = False를 사용하세요. 호환성 팩터리의 autocommit 기본값은 바뀌지 않습니다.

성공한 경계 뒤에만 기존 네이티브 결과 콜백이 적용됩니다. commit은 로컬 결과 상태를 유지하고 rollback은 네이티브 fetch를 무효화합니다. 래퍼 커서의 rowcount와 description은 실행 시점 캐시이므로, rollback 뒤 메타데이터가 남아 있어도 결과를 fetch할 수 있다는 뜻은 아닙니다. 실패한 위임에 facade가 추가로 결과에 알리거나 캐시를 초기화하지 않습니다. 새 연결·SQL·재시도·복구 로직을 추가하지 않으며 기존 네이티브 규칙을 따릅니다. 모든 원격 결과 수명이나 스레드 공유를 보장하지 않습니다. 일반·비동기 API, 기본값과 더 넓은 래퍼 실행은 그대로입니다.

한정된 래퍼 행 커서 (#466)

cubriddb.Connection.cursor(dictCursor=None)는 인자가 거짓이면 튜플 커서, 참이면 사전 커서를 만듭니다. pycubrid.compat.cursors.Cursor(conn)와 DictCursor(conn)로 직접 생성할 수도 있지만 패키지 전역 내보내기에는 추가하지 않습니다. 초기값은 arraysize=1, rowcount=-1, description=None입니다. execute(query, args=None, set_type=None)는 int, str, None 스칼라를 네이티브 bind_param()에 넘기고(#840부터 부호 있는 32비트를 넘는 int는 DataError 대신 BIGINT 또는 NUMERIC으로 바인딩), #610부터 list/tuple/set/frozenset 인자를 컬렉션으로 바인딩합니다. executemany(query, args_list)는 한 번 준비한 뒤 각 그룹을 실행합니다(래퍼 컬렉션 인자 참고). 매핑 인자와 미지원 값은 실행 전에 실패합니다. LOB 인자와 범용 DB-API 실행은 이번 범위에 포함되지 않습니다.

SELECT의 description은 (name, native_type, 0, 0, precision, scale, null_ok)의 7필드 튜플이며 null_ok는 정수입니다. 사전 키는 컬럼 이름과 대소문자를 그대로 보존하고 같은 이름이 반복되면 원래 키 위치는 그대로 둔 채 마지막 값이 앞 값을 덮습니다. SQL NULL의 None과 빈 문자열은 구분됩니다. 공개 description은 쓰기 가능한 스냅샷이지만, 이를 호출자가 바꿔도 키와 변환기 인자는 내부 실행 메타데이터를 사용합니다. SELECT가 아닌 경우 description=None을 안정적으로 설정합니다. 공식 확장은 재준비 후 속성이 사라질 수도 있어 이 부분은 의도적인 안전성 차이입니다.

conn.set_fetch_value_converter(func)는 연결별 값을 저장하고 기존 커서도 매번 최신 값을 읽습니다. 참인 변환기는 이미 구성된 튜플/사전 행과 네이티브 형식의 description을 받아 반환값을 None 등 거짓 값까지 그대로 전달합니다. 거짓 변환기 값은 변환을 끄고, 참이지만 호출 불가능한 값은 행을 소비한 뒤 TypeError를 냅니다. 변환기 예외도 행을 소비합니다. 공식 래퍼처럼 fetchmany()/fetchall()은 거짓인 변환 결과를 소비하고 멈추지만 반복자는 None에서만 멈추며 이후 fetch로 재개할 수 있습니다. 공식 래퍼처럼 size <= 0인 fetchmany(size)는 경고 없이 []를 반환합니다. 이 호출을 사용 중단 예정으로 표시한 네이티브 커서와 의도적으로 다른 동작입니다. next()는 공식 래퍼가 제공하는 __next__()의 명시적 별칭입니다. 핸들을 확실히 해제하려면 close()를 호출하세요. 버려진 래퍼 커서는 GC 때 같은 세션에서 커밋·재연결 없이 정리를 시도하지만, 이 정리는 최선 노력이며 I/O에서 지연되거나 실패할 수 있어 명시적인 종료를 대체하지 못합니다.

from pycubrid.compat import cubriddb

conn = cubriddb.connect("CUBRID:localhost:33000:testdb:::", "dba", "")
try:
    cur = conn.cursor(dictCursor=True)
    try:
        cur.execute('SELECT CAST(1 AS INTEGER) AS "ItemID"')
        row = cur.fetchone()  # {"ItemID": 1}
    finally:
        cur.close()
finally:
    conn.close()

컬렉션 바인딩 (set, imports, bind_set)

#440부터 네이티브 기능은 공식 이름으로 SET, MULTISET, SEQUENCE 파라미터 값을 바인딩합니다. conn.set()(또는 native.set(conn))은 빈 native.set을 반환하고, s.imports(data, type, /, *, kind=SET)가 값을 정하며, cur.bind_set(index, s)가 그 값을 1부터 시작하는 파라미터에 바인딩합니다. execute() 전에는 아무것도 보내지 않으며 set은 서버 자원을 갖지 않습니다.

  • data는 tuple이어야 합니다(그 밖의 값은 공식 드라이버처럼 InterfaceError). 원소는 str, NULL 원소를 뜻하는 None, 또는 type이 INT일 때 부호 있는 64비트 범위의 int입니다(범위 밖은 DataError). int와 숫자 문자열 원소는 모두 텍스트로 보내므로 섞어 쓸 수 있습니다. 다른 원소 타입(bool, float, bytes, 중첩 컨테이너), NUL이 든 문자열, 인코딩할 수 없는 문자열은 ProgrammingError 또는 DataError를 내고, set은 이전 값을 유지합니다. str 하위 클래스는 먼저 일반 텍스트로 복사합니다.
  • type은 CHAR(1), STRING/VARCHAR(2), NUMERIC(7), INT(8), DATE(13) 같은 CCI 원소 타입 코드입니다. 예를 들어 pycubrid.constants의 CUBRIDDataType.NUMERIC을 씁니다. 공식 드라이버처럼 어떤 코드든 받으며 import의 표시일 뿐입니다. 공식 드라이버가 비트 문자열로 변환하는 BIT(5)와 VARBIT(6)은 NotSupportedError를, int가 아닌 코드는 InterfaceError를 냅니다.
  • 공식 드라이버처럼 모든 원소는 type과 관계없이 STRING(2) 원소로 보내며, 서버가 컬럼의 원소 타입으로 변환합니다. int 원소는 10진 텍스트로 보내므로 imports((1, 2), INT)는 공식 imports(('1', '2'), INT)와 같은 바이트를 보냅니다. 컬럼이 담을 수 없는 값은 execute() 때 서버에서 실패하며 prepared 핸들은 계속 쓸 수 있습니다. 원소가 문자열이므로 원소 타입이 없는 SET 컬럼에는 문자열로 저장됩니다.
  • kind는 SET(16, 기본값), MULTISET(17), SEQUENCE(18)입니다. 기본값은 공식 바이트를 그대로 보내므로 MULTISET이나 SEQUENCE 컬럼에도 SET 의미가 적용되어 중복이 사라지고 순서가 유지되지 않습니다. 중복을 유지하려면 kind=MULTISET, 순서와 중복을 유지하려면 kind=SEQUENCE를 넘깁니다. CUBRID 10.2와 11.4 브로커는 MULTISET 바인드 종류를 거부하므로(오류 -454) kind=MULTISET은 SEQUENCE로 보내며, 서버는 이를 MULTISET 컬럼에 중복과 함께 저장합니다.
  • imports()는 값을 교체합니다. bind_set()은 그 시점의 값을 바인딩하므로 이후의 imports()는 앞선 바인딩을 바꾸지 않습니다. 한 번도 import하지 않은 set은 공식 드라이버처럼 SQL NULL을 바인딩하며, bind_param(index, None)도 SQL NULL을 바인딩합니다.
  • bind_set()은 native.set이 아닌 값에 InterfaceError를, 잘못된 인덱스나 다른 문자셋으로 import한 set에 ProgrammingError를 냅니다. 이전 세션·닫힌 커서 규칙은 bind_param()과 같습니다.

공식 드라이버와 의도적으로 다른 점은 각각 라이브 차등 비교 주장 (tests/fixtures/official_differential_claims.json의 bind-*)으로 고정되어 있습니다. None이 NULL 원소이고 텍스트 'NULL'은 문자열로 남습니다(공식은 'NULL'을 NULL 원소로 바꿈). 빈 문자열과 Python int 원소를 허용합니다(공식은 InterfaceError). NUL이 든 원소는 ProgrammingError를 냅니다(공식은 조용히 잘라냄). kind는 pycubrid 확장입니다(공식은 항상 SET으로 바인딩). 오류 클래스는 #439 prepared 커서를 따릅니다. float/bytes 원소와 잘못된 bind_set 인덱스는 ProgrammingError(공식 InterfaceError), 연결이 아닌 값을 받은 native.set()은 InterfaceError(공식 TypeError), 서버 오류 -494는 드라이버 전체 매핑에 따라 ProgrammingError(공식 IntegrityError)입니다.

공식 모듈과 마찬가지로 from pycubrid.compat.native import *는 set 이름을 native.set에 바인딩하므로 그 네임스페이스에서 내장 set을 가립니다. 래퍼의 execute(query, args, set_type)와 executemany() 컬렉션 형태는 이 API 위에서 동작합니다(다음 절). 일반 pycubrid 커서는 계속 타입 지정 pycubrid.types.Set/Multiset/Sequence 리터럴 파라미터(#567)를 사용합니다.

from pycubrid.compat import native
from pycubrid.constants import CUBRIDDataType

conn = native.connect("CUBRID:localhost:33000:testdb:::", "dba", "")
try:
    cur = conn.cursor()
    try:
        cur.prepare("INSERT INTO t (tags, scores) VALUES (?, ?)")
        tags = conn.set()
        tags.imports(("a", "b"), CUBRIDDataType.STRING)  # SET(VARCHAR)
        scores = conn.set()
        scores.imports((3, 1, 3), CUBRIDDataType.INT, kind=CUBRIDDataType.MULTISET)
        cur.bind_set(1, tags)
        cur.bind_set(2, scores)
        cur.execute()
    finally:
        cur.close()
finally:
    conn.close()

래퍼 컬렉션 인자 (#610)

한정된 래퍼 커서는 공식 CUBRIDdb의 컬렉션 호출 형태를 받습니다(고정된 업스트림 CUBRIDdb/cursors.py, e75ec36). 컬렉션 인자마다 위 네이티브 API의 conn.connection.set(), imports(tuple(elements), code), bind_set(index, s)를 사용합니다. 모든 원소는 SET 종류의 STRING 텍스트로 전송되고 타입 코드는 import에 붙는 표시일 뿐입니다. 모든 원소가 지원되고 None이 아닌 컬렉션이면 요청 바이트는 공식 드라이버와 같습니다. None 원소와 아래 차이 목록의 경우는 바이트가 같지 않습니다.

항목 계약
호출 형태 execute(query, args=None, set_type=None) -> int, executemany(query, args_list) -> None
args None, 위치 값의 tuple/list, 또는 단일 int/str(감싸서 사용). set을 포함한 다른 최상위 값은 ProgrammingError
위치 값 기존처럼 int(INT32; #840부터 부호 있는 32비트를 넘으면 BIGINT 또는 NUMERIC), str, None(SQL NULL). list, tuple, set, frozenset은 컬렉션. 다른 값(bool, float, bytes, dict, 제너레이터, 기타 이터러블)은 ProgrammingError
set_type None이면 모든 컬렉션을 추론합니다. int 코드는 모든 컬렉션에 적용됩니다. list/tuple은 위치별 코드이며 None 항목이나 끝을 넘는 위치는 추론합니다. 컬렉션이 없으면 무시합니다. 사용되는 코드가 int가 아니거나 bool이면 InterfaceError
추론 코드 int와 bool은 INT(8), float는 FLOAT(11), Decimal은 NUMERIC(7), date와 datetime은 DATE(13), time은 TIME(14), str은 STRING(2). None 원소는 건너뛰며 비었거나 모두 None이면 STRING
원소 None은 NULL 원소, str은 그대로, 다른 지원 원소는 str(element)로 전송합니다(True는 'True', datetime은 ISO 텍스트). 서버가 텍스트를 컬럼 타입으로 변환합니다
오류 추론 범주가 섞이면 업스트림처럼 TypeError. bytes/bytearray 원소와 BIT(5)/VARBIT(6) 코드는 NotSupportedError. 다른 원소 타입(중첩 컨테이너, 객체)은 ProgrammingError, NUL과 문자셋 오류는 imports()를 따릅니다
executemany 모든 그룹을 검사하고(추론 타입, set_type 없음) 컬렉션을 만든 뒤 한 번 준비합니다. 모든 그룹의 값 개수가 자리표시자 수와 같아야 하며, 다르면 ProgrammingError를 내고 어떤 그룹도 실행하지 않습니다. 그 뒤 그룹마다 순서대로 바인딩·실행합니다. rowcount와 description은 마지막 실행 값입니다. 빈 args_list는 준비만 하며 rowcount=-1, description=None입니다. 이는 공식 래퍼와 비교하지 않은 pycubrid 자체 동작입니다
시점과 상태 execute()와 executemany() 모든 그룹의 인자·타입·컬렉션 오류는 준비 전에, 서버 I/O 없이 발생합니다. 공식의 준비 후 바인딩처럼 이전 결과를 버리고(이후 fetchone()은 InterfaceError) rowcount=-1, description=None으로 설정합니다. executemany() 그룹의 값 개수는 준비 후, 어떤 실행보다도 먼저 검사합니다. 스칼라의 범위·NUL·문자셋 오류는 준비 후 바인딩 시점에 발생합니다. 준비 후의 실패는 rowcount와 description을 이전 값으로 둡니다. executemany()의 한 그룹이 서버 오류를 내면 앞선 그룹은 실행된 상태로 남고 커서는 계속 사용할 수 있습니다

공식 래퍼와의 의도된 차이입니다. 처음 네 가지는 라이브 차등 클레임 (wrapper-collection-null-element, wrapper-collection-errors)으로 고정되고, 나머지는 오프라인 테스트(tests/test_compat_wrapper_collections.py)로만 확인합니다.

  • None 원소는 NULL 원소로 바인딩합니다. 공식 래퍼는 텍스트 'None'을 보내거나 추론이 None을 먼저 만나면 UnboundLocalError를 냅니다.
  • BIT/VARBIT 컬렉션 원소는 지원하지 않습니다(NotSupportedError). 공식 래퍼는 bytes를 비트 문자열로 바꿉니다.
  • list, tuple, set, frozenset 인자만 컬렉션입니다. 공식 래퍼는 모든 이터러블을 컬렉션으로 다룹니다(제너레이터는 추론 중에 소비되어 빈 집합이 바인딩됨).
  • 서버 오류 -494는 ProgrammingError입니다(공식 IntegrityError).
  • executemany()의 뒤쪽 그룹에 타입·인자 오류가 있거나 값 개수가 자리표시자와 맞지 않으면 어떤 그룹도 실행하지 않습니다. 공식 래퍼는 앞선 그룹을 먼저 실행합니다.
  • 명시적 set_type이 있어도 미지원 원소는 오류입니다(중첩 리스트나 객체는 ProgrammingError, VARBIT가 아닌 코드의 bytes는 NotSupportedError). 공식 래퍼는 그 str() 텍스트를 보냅니다.
  • 그 밖의 스칼라 범위는 그대로입니다. 공식 래퍼는 bool, float, Decimal, 날짜, bytes 스칼라도 바인딩하고 None 인자는 건너뛰지만, 이 래퍼는 그 타입들을 거부하고 None을 SQL NULL로 바인딩합니다. #840부터 부호 있는 32비트를 넘는 int 인자는 네이티브 bind_param()과 같이 BIGINT 또는 NUMERIC으로 바인딩합니다.

공식 래퍼처럼 모든 컬렉션은 SET 종류로 바인딩되므로(클레임 wrapper-collection-executemany-kinds) MULTISET이나 SEQUENCE 컬럼에는 중복이 제거되고 서버 순서로 정렬된 원소가 들어갑니다. 중복이나 순서를 유지하려면 네이티브 imports(..., kind=...)를 사용합니다.

from pycubrid.compat import cubriddb
from pycubrid.constants import CUBRIDDataType

conn = cubriddb.connect("CUBRID:localhost:33000:testdb:::", "dba", "")
try:
    cur = conn.cursor()
    try:
        sql = "INSERT INTO t (id, tags, scores) VALUES (?, ?, ?)"
        cur.execute(sql, (1, ("a", "b"), (3, 1)))  # STRING, INT로 추론
        cur.execute(sql, (2, ["c"], ["4"]), set_type=[None, None, CUBRIDDataType.INT])
        cur.executemany(sql, [(3, {"d"}, (5,)), (4, (), ())])
    finally:
        cur.close()
finally:
    conn.close()

네이티브 LOB 스트림과 핸들 (lob, fetch_lob, bind_lob)

#441부터 네이티브 기능은 공식 이름으로 BLOB/CLOB 핸들을 조회하고 바인딩합니다. conn.lob()(또는 native.lob(conn))은 비어 있는 native.lob을 반환하고, cur.fetch_lob(col, lob)은 다음 행에서 핸들을 가져와 채우며, cur.bind_lob(index, lob)은 그 핸들을 1부터 시작하는 매개변수에 바인딩합니다. 주된 용도는 저장된 LOB를 Python으로 읽지 않고 다른 행에 복사하는 것입니다. 서버는 문장을 실행할 때 값을 복사하므로 execute() 뒤에는 원본 행을 바꾸거나 삭제해도 됩니다.

  • fetch_lob(col, lob)은 fetch_row()처럼(FETCH 페이징 포함) 현재 SELECT 결과의 다음 행을 소비하고, 1부터 시작하는 col 컬럼의 핸들을 lob에 넣습니다. 그 컬럼의 타입이 BLOB/CLOB를 정합니다. int가 아닌 col은 공식 인자 파서처럼 가장 먼저 TypeError를 냅니다. 결과의 끝에서는 공식 드라이버처럼 컬럼 범위·타입이나 lob 상태를 검사하기 전에 None을 반환하고 아무것도 바꾸지 않습니다. 그 밖에는 BLOB/CLOB가 아니거나 범위를 벗어난 컬럼이 행을 소비하지 않고 ProgrammingError를, 닫힌 lob이나 다른 연결이 만든 lob이 InterfaceError를 냅니다. 공식 드라이버처럼 None을 반환합니다. NULL 셀은 행을 소비하고 lob을 값이 없는 상태로 둡니다. 서버가 보낸 col 셀이 컬럼의 LOB 타입 핸들이 아니면 행을 소비하지 않고 DataError를 내며 연결은 계속 쓸 수 있습니다. 핸들 구조가 손상되었으면 OperationalError를 내고 물리 세션을 폐기합니다.
  • bind_lob(index, lob)은 그 시점에 lob이 가진 핸들을 바인딩하며 execute() 전까지 아무것도 보내지 않습니다. 조회한 핸들은 서버가 새 행에 복사하는 커밋된 저장 값을 가리키므로, 공식 드라이버처럼 다시 바인딩할 수 있고, 열려 있는 다른 연결에서도, 원래 연결이 닫히거나 재접속한 뒤에도 바인딩할 수 있습니다. native.lob이 아닌 값은 공식과 같이 TypeError(공식 인자 파서처럼 int가 아닌 인덱스를 먼저 보고), 범위를 벗어난 인덱스는 ProgrammingError입니다. 닫힌 lob, 값이 없는 lob(채운 적이 없거나 NULL 셀에서 채움), 닫힌 커서나 연결은 요청 전에 InterfaceError를 냅니다. SQL NULL은 bind_param(index, None)으로 바인딩합니다.
  • lob.close()는 핸들을 로컬에서만 버리고 아무것도 보내지 않습니다. CAS 프로토콜에는 LOB 해제 요청이 없고, 조회한 값은 서버의 행에 남습니다. close() 전에 만든 바인딩은 그대로 유효합니다. 닫힌 lob은 다시 채우거나 바인딩할 수 없으므로 conn.lob()으로 새로 만드세요.
  • lob.write(data, type="B", /) -> None은 str(연결 문자셋과 관계없이 UTF-8로 인코딩) 또는 bytes를 받습니다. 첫 쓰기는 기본 BLOB을 만들고 "C"를 주면 CLOB을 만듭니다. 이후의 타입 인자는 기존 핸들 타입을 바꾸지 않습니다. 첫 빈 쓰기도 핸들을 만들지만 LOB_WRITE 요청은 보내지 않습니다. 위치와 핸들 크기는 확인된 바이트 수만큼 증가합니다. CUBRID 외부 저장소는 끝에만 쓸 수 있으므로 먼저 끝으로 이동해야 합니다. 중간이나 끝 너머의 쓰기는 브로커 요청 전에 NotSupportedError를 내며 덮어쓰기나 빈 공간 채우기는 하지 않습니다. 조회한 핸들에 추가하면 그 핸들의 크기는 늘어나지만 원본 행의 선언된 크기는 그대로입니다. 다만 일반 절대 오프셋 읽기로 그 크기를 넘겨 읽으면 공유된 외부 파일의 추가 바이트가 보일 수 있습니다. 새 논리 크기로 저장하려면 갱신한 핸들을 새 행에 바인딩하세요.
  • lob.read(length=0, /) -> str은 현재 바이트 위치에서 읽고 실제 수신한 바이트만큼 이동합니다. 길이를 생략하거나 0을 주면 남은 부분을 읽으며, 큰 값은 제한된 크기의 요청으로 나눕니다. BLOB과 CLOB 모두 유효한 UTF-8에서는 공식 Python 3 확장과 같이 엄격하게 디코딩한 문자열을 반환합니다. 임의 바이너리는 일반 Lob.read()를 사용하세요. 문자를 중간에서 나눠 읽으면 위치가 수신 바이트만큼 이동한 뒤 UnicodeDecodeError가 납니다. 빈 값, EOF, 남은 길이를 넘는 읽기는 안전하게 "" 또는 가능한 텍스트를 반환합니다. 뒤 청크에서 오류가 나도 앞선 응답에서 확인된 바이트만큼의 위치는 유지되며, 세션이 살아 있다면 그 위치에서 다시 읽을 수 있습니다. EOF 오류나 선언 범위를 넘는 버퍼 읽기 같은 공식 CCI 래퍼의 위험한 가장자리 동작은 재현하지 않습니다.
  • lob.seek(offset, whence=SEEK_CUR, /) -> int는 바이트 위치를 바꿉니다. native.SEEK_SET, SEEK_CUR, SEEK_END를 내보냅니다. 공식 드라이버와 같이 SEEK_END는 size - offset이며 양수 offset은 뒤로 이동합니다. seek(0, SEEK_CUR)가 현재 위치 조회를 대신하므로 별도 tell()은 없습니다. 잘못된 whence나 음수·범위 초과 위치는 위치를 바꾸지 않고 거부합니다. 공식 드라이버의 무검사 음수 seek는 따르지 않습니다. 같은 객체에 다른 행의 핸들을 다시 채워도 공식 확장처럼 바이트 위치는 유지됩니다. 처음부터 읽으려면 seek(0, SEEK_SET)을 호출하세요. EOF 이후의 음수가 아닌 위치는 가상 위치로 허용되지만 읽으면 ""이며, 쓰기는 끝에만 가능하므로 빈 공간을 만들지 않습니다.
  • 네이티브 연결은 autocommit으로 시작하지만 set_autocommit(False)로 수동 트랜잭션을 사용할 수 있습니다. autocommit 상태에서 조회한 LOB는 다른 연결에서 바인딩할 수 있습니다. 수동 모드에서 조회한 LOB의 보수적인 비전달 표시기는 나중에 commit()해도 그대로이므로, 연결을 넘어 바인딩하려면 확실한 autocommit 모드에서 다시 조회하세요. LOB_NEW로 만든 핸들은 원래 물리 세션의 임시 파일이며, 처음 바인딩한 autocommit 문장이 실패해도 그 파일을 소비합니다. 이후 같은 핸들을 재사용하면 서버의 오래된 locator 오류가 납니다. 다시 바인딩하려면 커밋된 행을 새 lob으로 조회하세요. bind_lob()은 바인딩 시점의 핸들을 복사하므로 추가 쓰기 뒤에는 다시 바인딩해야 합니다. 읽기·쓰기·seek는 원래 살아 있는 물리 세션이 필요하지만, 커밋된 조회 핸들은 위 설명처럼 다른 연결에서 바인딩할 수 있습니다.
  • 공식 _cubrid.lob에서 이행할 때는 이 명시적인 동기 네임스페이스를 쓰고, 바이트 위치와 공식 SEEK_END 방향을 유지하세요. 닫힌 객체에 다시 쓰지 말고 conn.lob()으로 새 객체를 만드세요. 일반 pycubrid.lob.Lob.read(length, offset=0)/write(bytes, offset=0)은 종전의 절대 오프셋 바이트 API로 남습니다. 네이티브 파일 작업은 아래에서 설명하며, 비동기 LOB는 이 facade에 없습니다.

공식 드라이버와 의도적으로 다른 점은 모두 라이브 차등 주장(tests/fixtures/official_differential_claims.json의 lob-*)으로 고정되어 있습니다. 값이 없거나 닫힌 lob을 바인딩하면 InterfaceError를 냅니다(공식은 NULL 핸들을 바인딩해 SQL NULL을 저장). 닫힌 lob이나 다른 연결이 만든 lob으로 fetch_lob()하면 InterfaceError를 냅니다(공식은 채우며, 공식 close()는 다시 쓸 수 있음). fetch_lob()의 LOB가 아닌 컬럼과 잘못된 bind_lob() 인덱스는 ProgrammingError를 냅니다(공식은 InterfaceError). 공식 드라이버는 col이 아니라 1번 컬럼으로 핸들 타입을 정하므로 바인딩 타입 바이트가 다를 수 있지만, 브로커는 핸들의 타입을 쓰며 CUBRID 10.2와 11.4에서 저장되는 복사본은 같습니다.

from pycubrid.compat import native

conn = native.connect("CUBRID:localhost:33000:testdb:::", "dba", "")
try:
    src, dst = conn.cursor(), conn.cursor()
    try:
        src.prepare("SELECT id, photo FROM images WHERE id = 1")
        src.execute()
        photo = conn.lob()
        src.fetch_lob(2, photo)  # 2번 컬럼은 BLOB
        dst.prepare("INSERT INTO archive (photo) VALUES (?)")
        dst.bind_lob(1, photo)
        dst.execute()
        photo.close()
    finally:
        src.close()
        dst.close()
finally:
    conn.close()

네이티브 LOB 파일 (imports, export)

명시적인 동기 전용 native.lob은 위치 전용 인자 메서드 imports(file: str, type: str = "B", /) -> None과 export(file: str, /) -> None을 제공합니다. CLOB도 원시 바이트로 전송하며 텍스트 디코딩, 개행 변환이나 연결 문자셋 변환을 하지 않습니다. 로컬 읽기·쓰기와 와이어 요청은 최대 64 KiB 청크를 사용하고 전체 값을 합치지 않습니다. 두 메서드 모두 객체의 바이트 위치를 바꾸지 않습니다.

  • 경로와 import 타입은 정확한 내장 문자열이어야 합니다. bytes, PathLike, 문자열 하위 클래스나 파일 객체는 받지 않습니다. 경로의 NUL은 ValueError, 잘못된 인자 타입은 TypeError입니다. 타입은 "B"/"b" 또는 "C"/"c"만 받으며, 다른 문자열은 파일·서버 작업 전에 .code == -30006인 InterfaceError를 냅니다. 상대 경로는 진입 시 한 번 해석하며 물결표·환경 변수를 확장하거나 상위 디렉터리를 만들지 않습니다.
  • imports()는 입력 파일을 먼저 연 뒤 교체용 임시 LOB를 만듭니다. 읽기, 확인된 쓰기와 입력 파일 닫기가 모두 성공한 뒤에만 새 핸들을 채택합니다. 실패하면 호출자가 객체를 변경했거나 세션이 폐기된 경우를 제외하고 기존 상태와 위치를 유지합니다. 앞서 만든 prepared 바인딩은 스냅샷으로 남습니다. 서버 임시 파일의 롤백·회수를 보장하지 않으며, 채택한 핸들은 위에서 설명한 생성 LOB의 원래 세션 및 첫 autocommit 바인딩 수명 규칙을 따릅니다.
  • export()는 원래 살아 있는 물리 세션의 값이 있는 핸들을 요구하며, 현재 위치와 무관하게 오프셋 0부터 읽습니다. 배타적으로 만든 같은 디렉터리의 임시 파일에 쓰고 모든 바이트, flush와 close가 성공한 뒤 목적지를 교체합니다. 값이 있는 빈 LOB는 서버 읽기 없이 빈 파일을 만들지만, 값이 없는 객체는 파일 생성 전에 InterfaceError (-30018)를 냅니다. 첫 autocommit 바인딩이 소비한 생성 핸들이 아니라, 저장된 행에서 새로 조회한 핸들을 export하세요.
  • 로컬 경로·열기·임시 파일 실패는 InterfaceError 코드 -30009, 입력 읽기·닫기 실패는 -30016, 출력 쓰기·flush·닫기·교체 실패는 -30017입니다. 원래 OSError가 있으면 원인으로 유지합니다. args는 공식 정수/메시지 쌍이 아닌 pycubrid의 메시지만 유지하며 errno나 SQLSTATE를 만들어내지 않습니다. 서버·전송 오류는 각각의 기존 클래스를 유지합니다.

단계적 import, 정확한 문자열 검사와 성공 시 교체는 고정된 C 확장의 조기 상태 변경 및 직접 truncate/write/unlink와 의도적으로 다릅니다. 교체는 inode·하드 링크·심볼릭 링크 동작도 바꾸며, 기존 목적지 메타데이터 대신 보통 제한적인 임시 파일 권한을 사용합니다. 디렉터리 샌드박스, fsync·충돌 내구성이나 DB와 파일의 원자적 트랜잭션을 보장하지 않습니다. 지원하는 로컬 I/O 중 객체·세션 변경은 채택·게시 전에 거부하지만 마지막 os.replace() 안의 임의 audit-hook 변경까지 트랜잭션으로 만들지는 않습니다. 신뢰할 수 있는 호출자 소유 경로를 사용하세요. 일반·비동기·래퍼 API는 그대로이며, 성공 주장은 위험한 C 생성·결함 주입이나 빈 파일의 동등성을 인증하지 않습니다.

비동기 모듈 생성자

pycubrid.aio.connect()

async def connect(
    host: str = "localhost",
    port: int = 33000,
    database: str = "",
    user: str = "dba",
    password: str = "",
    decode_collections: bool = False,
    json_deserializer: Any = None,
    ssl: bool | ssl_module.SSLContext | None = None,
    charset: str = "utf-8",
    **kwargs: Any,
) -> AsyncConnection

비동기 연결을 만들고 엽니다.

  • 연결된 AsyncConnection을 반환합니다.
  • pycubrid.connect()와 동일한 컬렉션/JSON 디코딩 kwargs와 charset 옵션을 받습니다.
  • autocommit=True를 지원하며, 연결 수립 후 자동 적용됩니다. AsyncConnection 자체도 이제 autocommit을 (키워드 전용 생성자 인자로) 직접 받으므로, 이 팩토리를 거치지 않고 생성해도 플래그가 조용히 사라지지 않습니다.
  • 동기 API와 유사한 비동기 서피스를 제공합니다 — await conn.ping(reconnect=...) 포함. create_lob()은 동기 전용으로 유지되며, 오토커밋 변경은 속성 세터 대신 await conn.set_autocommit(...)으로 합니다.
  • pycubrid.connect()와 동일한 ssl 파라미터를 받습니다: True, False/None, 또는 커스텀 SSLContext. True면 기본 검증 컨텍스트가 TLS 1.2 최소를 강제합니다. 비동기 TLS는 CUBRID의 STARTTLS 방식 업그레이드를 사용 — CUBRS 핸드셰이크를 평문으로 보낸 뒤, OPEN_DATABASE 전에 asyncio.AbstractEventLoop.start_tls()(ssl_handshake_timeout으로 제한)로 전송을 업그레이드합니다. 전체 내용은 연결 가이드를 참고하세요.
import asyncio
import pycubrid.aio

async def main() -> None:
    conn = await pycubrid.aio.connect(database="testdb")
    cur = conn.cursor()
    await cur.execute("SELECT 1")
    print(await cur.fetchone())
    await cur.close()
    await conn.close()

asyncio.run(main())

Connection 클래스

pycubrid.connection.Connection

CAS 브로커 프로토콜을 통한 CUBRID 데이터베이스 연결 하나를 나타냅니다.

Connection 생성자

class Connection:
    def __init__(
        self,
        host: str,
        port: int,
        database: str,
        user: str,
        password: str,
        autocommit: bool = False,
        **kwargs: Any,
    ) -> None

참고: Connection을 직접 생성하지 마세요. pycubrid.connect()를 사용하세요.

선택된 **kwargs:

이름 타입 기본값 설명
enable_timing bool \| None None connect, execute, fetch, close 연산에 대한 드라이버 수준 타이밍 계측 옵트인. None이면 PYCUBRID_ENABLE_TIMING 환경 변수로 폴백(참값: 1, true, yes, 대소문자 무시). 비활성 시 타이밍 모듈을 임포트하지 않고 connection.timing_stats는 None(오버헤드 0). 타이밍·프로파일링 훅 참고.
ssl bool \| ssl.SSLContext \| None None 동기·비동기 연결이 공유하는 TLS 설정. True면 minimum_version = TLSv1_2인 기본 검증 컨텍스트 사용. 연결 가이드 참고.
read_timeout float \| None None 소켓 송신·수신 호출 하나마다의 타임아웃(초)(비동기 연결은 대신 요청 왕복 전체를 제한). SQL 벽시계 기한이 아닙니다. 타임아웃이 제한하는 범위 참고.
fetch_size int 100 서버 측 fetch 배치 크기.
json_deserializer Callable[[str], Any] \| None None 옵트인 JSON 컬럼 디코더.
charset str "utf-8" 연결 코덱. charset 참고. 재연결 후에도 유지.
decode_collections bool False SET/MULTISET/SEQUENCE 컬럼을 Python 컬렉션으로 디코딩.

Connection 메서드

connect()

def connect(self) -> None

브로커 핸드셰이크와 함께 TCP CAS 세션을 수립하고 데이터베이스를 엽니다. 생성자가 자동 호출합니다. 이미 연결된 인스턴스에서 호출하면 no-op입니다.

발생: 네트워크 실패 시 OperationalError.


close()

def close(self) -> None

연결과 추적 중인 모든 커서를 닫습니다. 서버로 CloseDatabasePacket을 보냅니다. 이미 닫힌 연결에서 호출하면 no-op입니다. close() 이후 다른 메서드 호출은 InterfaceError를 발생시킵니다.

conn = pycubrid.connect(database="testdb")
# ... 작업 ...
conn.close()  # 연결과 모든 커서가 닫힘

commit()

def commit(self) -> None

현재 트랜잭션을 커밋합니다. 닫히지 않은 커서가 가진 쿼리 핸들에 CLOSE_REQ를 보낸 뒤 서버로 CommitPacket을 보냅니다(#485). 이미 받은 행은 계속 읽을 수 있습니다. autocommit 모드에서는 END_TRAN을 보내지 않으므로 커서를 닫아 서버 핸들을 해제하세요. statement pooling 브로커에서는 이 해제가 별도의 CLOSE_REQ 없이 다음 문장에 실려 가며, close() 없이 수거된 커서도 같은 방식으로 해제됩니다 (지연 닫기, #488). 이전 트랜잭션 밖 응답 뒤 CAS가 세션을 재활용했다면 요청 전에 트랜잭션 경계에서 CAS가 재활용되는 경우에 설명한 검증된 재접속이 먼저 수행됩니다.

발생: 연결이 닫혔으면 InterfaceError.


rollback()

def rollback(self) -> None

현재 트랜잭션을 롤백합니다. 닫히지 않은 커서가 가진 쿼리 핸들에 CLOSE_REQ를 보낸 뒤 서버로 RollbackPacket을 보냅니다(#485).

발생: 연결이 닫혔으면 InterfaceError.


cursor()

def cursor(self) -> Cursor

이 연결에 바인딩된 새 Cursor를 만들어 반환합니다. 커서는 연결이 추적하며 연결이 닫힐 때 함께 닫힙니다.

반환: 새 Cursor 인스턴스.

발생: 연결이 닫혔으면 InterfaceError.

conn = pycubrid.connect(database="testdb")
cur = conn.cursor()
cur.execute("SELECT 1 + 1")
print(cur.fetchone())  # (2,)
cur.close()

get_server_version()

def get_server_version(self) -> str

서버 엔진 버전 문자열을 반환합니다 (예: "11.2.0.0378").

conn = pycubrid.connect(database="testdb")
print(conn.get_server_version())  # "11.2.0.0378"

get_last_insert_id()

def get_last_insert_id(self) -> str | None

가장 최근 INSERT 이후 브로커가 보고한 식별자를 캐시에서 문자열로 반환하며, 값이 없으면 None을 반환합니다. 별도 네트워크 요청은 없습니다. 해당 커서의 lastrowid는 독립적인 int | None 스냅샷입니다. 다른 커서의 INSERT가 연결 캐시를 갱신해도 이전 커서의 스냅샷은 바뀌지 않습니다.

commit(), rollback() 및 INSERT가 아닌 문장은 관측한 값을 유지합니다. 새 INSERT 시도 (실패 포함), 비어 있지 않은 executemany_batch(), 물리 연결 폐기/재접속 시 캐시가 초기화됩니다. 식별자 조회 실패, 빈 응답 또는 잘못된 응답이면 None이 유지됩니다. 빈 배치는 기존 값을 유지합니다.

정상적인 트랜잭션 종료 후에는 같은 물리 연결과 캐시가 유지됩니다. 실제 연결 실패 뒤 명시적인 ping(reconnect=True) 복구나 트랜잭션 밖 CHECK_CAS 실패 후의 자동 재접속(#485)이 성공하면 연결 캐시는 초기화되지만(autocommit INSERT 직후 CAS가 재활용되면 INSERT는 커밋되지만 lastrowid는 None이고 WARNING 로그가 남습니다. 자동 재접속이 실패하면 연결은 닫히지 않고 끊긴 상태가 되며 ping(reconnect=True)로 다시 연결합니다), 이전 커서의 lastrowid 스냅샷은 물리 연결 변경 후에도 유지됩니다.

이 값은 서버 응답이 INSERT로 분류한 커서 작업의 스냅샷이며, 이전의 실시간 브로커 상태 조회를 대체합니다. CALL, 저장 프로시저 내부 INSERT 또는 커서 밖의 SQL은 캐시를 갱신하지 않습니다. 프로시저가 삽입한 행의 ID는 프로시저에서 명시적으로 반환하거나 해당 프로시저의 서버 측 규약에 따라 직접 조회하세요. 이 캐시 메서드로 그 값을 알아내려 하지 마세요.

AUTO_INCREMENT 컬럼이 없는 테이블의 INSERT에도 브로커가 이전 식별자를 보고할 수 있으므로 반환값은 현재 문장이 식별자를 생성했다는 증거가 아닙니다. rollback 이후 값이 유지되는 것도 해당 행이 존재한다는 뜻은 아닙니다.

cur.execute("INSERT INTO users (name) VALUES ('alice')")
conn.commit()
print(conn.get_last_insert_id())  # "1"

마이그레이션: 이전의 값 없음 결과 ""를 검사하던 value == ""는 value is None으로 바꾸고, int로 변환하기 전에 확인합니다:

value = conn.get_last_insert_id()
new_id = int(value) if value is not None else None

정상 값은 기존 문자열 타입을 유지하며 cursor.lastrowid는 계속 int | None입니다. 비동기 메서드에도 같은 규칙이 적용됩니다.

빈 배치는 커서의 lastrowid를 None으로 초기화하지만 연결 캐시는 유지합니다. 비어 있지 않은 배치를 시작하기 전에 기존 쿼리 종료가 실패하면 두 식별자 값은 그대로 유지되며 예외가 전달됩니다.


ping(reconnect=True)

def ping(self, reconnect: bool = True) -> bool

정상 세션에서는 SQL 없이 가벼운 CHECK_CAS 헬스 체크를 수행합니다. 자동 모드의 재연결 중에는 애플리케이션 SQL을 받기 전에 읽기 전용 이스케이프 모드 탐색 SELECT를 실행할 수 있습니다.

  • CAS 연결이 살아 있으면 True를 반환합니다. CAS_INFO[0]=0은 연결 해제가 아니라 OUT_TRAN을 뜻하며 이 값만으로 재접속하지 않습니다. OUT_TRAN 응답 뒤의 일반 요청 앞에는 자동 CHECK_CAS가 붙고, 실패할 때만 재접속합니다(#485). 정상적인 ping()도 이 검사로 인정됩니다.
  • reconnect=False이면 열린 소켓을 검사하되 재접속하지 않으며, 연결이 끊겼거나 검사에 실패하면 False를 반환합니다.
  • reconnect=True이면 기존 소켓을 먼저 검사하고, 연결이 끊겼거나 검사 도중 전송/프로토콜 오류가 발생했거나 CHECK_CAS가 음수 코드로 CAS–DB 링크 장애를 보고하면 재접속을 한 번 시도합니다. reconnect=False는 음수 응답을 False로 보고하고 재접속하지 않으며, 비동기 드라이버처럼 그 손상된 세션을 닫습니다(이후 호출은 InterfaceError). 복구 후에는 명시적으로 설정한 autocommit만 복원합니다. 중단된 SQL은 자동 재실행하지 않으므로 재시도 안전성은 호출자가 판단해야 합니다. 자동 no_backslash_escapes 모드는 새 물리 세션에서 사용 전에 다시 감지하며, 명시적 True/False는 유지됩니다. 정상적인 동일 세션 ping은 감지하지 않습니다. 감지 실패 시 대체 세션을 폐기하고 False를 반환하며, 모드를 추측하거나 SQL을 재실행하지 않습니다.
  • 재접속이 실패하면 오류 종류와 관계없이 False를 반환합니다. 잘못된 비밀번호(-171)나 없는 사용자(-165)처럼 서버가 OPEN_DATABASE를 거부한 경우도 포함합니다. #896 이전의 동기 드라이버는 그 DatabaseError를 발생시켰습니다. 비동기 드라이버도 같게 동작합니다.
if not conn.ping():
    raise RuntimeError("database connection is unavailable")

create_lob(lob_type)

def create_lob(self, lob_type: int) -> Lob

서버에 새 LOB(Large Object)를 만듭니다.

파라미터:

파라미터 타입 설명
lob_type int LOB 타입 코드: BLOB은 23, CLOB은 24

반환: 새 Lob 인스턴스.

from pycubrid.constants import CUBRIDDataType

lob = conn.create_lob(CUBRIDDataType.CLOB)  # 24
lob.write(b"Hello, CUBRID!")

중요: Lob 객체는 execute()나 기본 executemany()의 쿼리 파라미터로 전달할 수 없습니다. CLOB/BLOB 컬럼에는 문자열/바이트를 직접 삽입하세요. 자세한 내용은 TYPES.md 참고. 유일한 예외는 autocommit이 꺼진 동기 커서의 executemany(..., prepared=True)로, 같은 연결의 Lob을 핸들로 바인딩합니다.


get_schema_info(schema_type, table_name="", pattern_match_flag=1, *, arg2=None)

def get_schema_info(
    self,
    schema_type: int,
    table_name: str = "",
    pattern_match_flag: int = 1,
    *,
    arg2: str | None = None,
) -> GetSchemaPacket

현재 연결·CAS 세션이 소유하는 스키마 결과를 생성합니다. fetch_schema_info()로 소비하거나 close_schema_info()로 명시적으로 폐기하세요.

파라미터:

파라미터 타입 기본값 설명
schema_type int — 스키마 타입 코드 (CCISchemaType 참고)
table_name str "" 테이블 이름 필터
pattern_match_flag int 1 패턴 매치 플래그
arg2 str \| None None 키워드 전용 두 번째 이름/패턴 (예: ATTRIBUTE 필터)

반환: query_handle, tuple_count, columns를 가진 원래 GetSchemaPacket. 축약 컬럼에는 column_type, scale, precision, name만 있으며 FC9에는 SELECT의 NULL 허용·기본값·제약 메타데이터가 없습니다. NULL(None)과 빈 문자열은 서로 다른 와이어 인자입니다. 모든 ATTRIBUTE 이름을 조회하려면 플래그 2와 arg2="%"를 사용하세요. NULL은 전체 속성 조회의 약식 표현이 아닙니다.

from pycubrid.constants import CCISchemaType

packet = conn.get_schema_info(CCISchemaType.CLASS, "my_table", 0)
try:
    rows = conn.fetch_schema_info(packet)
finally:
    conn.close_schema_info(packet)  # 정상 소비 후에도 안전합니다.
print(rows)

사용 가능한 CCISchemaType 값:

코드 이름 설명
1 CLASS 테이블과 뷰
2 VCLASS 뷰
4 ATTRIBUTE 컬럼
11 CONSTRAINT 인덱스 계열 항목
16 PRIMARY_KEY 기본 키
17 IMPORTED_KEYS 외래 키 (가져온)
18 EXPORTED_KEYS 외래 키 (내보낸)

소유한 객체를 사용하는 실제 서버 매트릭스(#457)는 CUBRID 10.2.18과 11.4.6에서 이 일곱 타입을 sync·async 경로로 검증합니다. 정확한 이름/패턴 필터, 빈 결과, 복합 키, 여러 FETCH에 걸친 ATTRIBUTE 행을 포함합니다. 다른 CCISchemaType 값이나 네이티브 드라이버와의 동등성을 이 매트릭스가 인증하지는 않습니다.

행은 packet.columns의 이름을 기준으로 해석하세요. CLASS에는 일반 테이블 (TYPE=2)뿐 아니라 뷰(TYPE=1)도 포함될 수 있습니다. PRIMARY_KEY 행은 속성 이름 순서로 올 수 있으므로 복합 키의 선언 순서는 KEY_SEQ를 사용하세요. CONSTRAINT는 인덱스 계열을 반환하며 모든 기본/외래 키를 포함하지 않습니다. 해당 관계에는 전용 키 타입을 사용하세요. 페이지/키 필드도 신뢰할 수 있는 인덱스 통계가 아닙니다. owner 접두사가 있는 이름과 브로커 인코딩된 ATTRIBUTE DOMAIN 정수는 그대로 유지됩니다. 이름이 항상 접두사 없거나 DOMAIN이 스칼라 타입 상수와 같다고 가정하지 마세요.

fetch_schema_info(packet)와 close_schema_info(packet)

fetch_schema_info(packet) -> list[tuple[Any, ...]]는 광고된 행 전체를 읽고 원래 핸들을 닫습니다. 0행도 닫으며, 조기 EOF·개수 불일치·정리 실패 시 부분 목록을 성공으로 반환하지 않습니다. close_schema_info(packet) -> None는 명시적 폐기이며 같은 소유자의 종료된 패킷은 반복해서 닫아도 no-op입니다. 다른 연결의 패킷·소유되지 않은 패킷·종료된 결과의 fetch는 I/O 전에 InterfaceError입니다. 공개 패킷 필드를 변경해도 실제 추적 중인 핸들·메타데이터는 바뀌지 않습니다.

commit/rollback은 활성 스키마 핸들을 먼저 닫고 소유권을 종료합니다. 유효한 autocommit이 적용되는 커서 문장도 SQL을 보내기 전에 같은 정리를 수행합니다. 연결의 autocommit이 꺼져 있어도 executemany_batch(..., auto_commit=True)에 같은 규칙이 적용됩니다. 연결의 autocommit이 켜져 있으면 get_server_version()도 자동 커밋 버전 조회 전에 소유한 스키마 핸들을 닫습니다. 종료된 패킷의 fetch는 추가 RPC 전에 로컬에서 InterfaceError를 발생시킵니다. 물리 연결 폐기/재접속 및 연결 종료도 소유권을 종료하며 다른 CAS 세션에서 재실행하지 않습니다. FETCH/CLOSE는 자동 재접속·암묵적 커밋을 하지 않습니다. 스키마 생성· 종료 실패는 불확실한 세션을 폐기합니다. FETCH와 정리가 모두 실패하면 원래 예외를 유지하고 정리 오류를 로그에 기록합니다. 비동기 메서드는 await하며 fetch/정리 동안 연결 락을 유지합니다. 스키마 I/O 도중 취소하면 세션을 폐기하고 취소를 다시 발생시키지만, 락 대기 중 취소는 다른 태스크의 세션을 폐기하지 않습니다. 비동기 스키마 FETCH 도중 KeyboardInterrupt 또는 SystemExit가 발생해도 응답이 남아 있을 수 있는 스트림에 CLOSE를 보내지 않고 불확실한 세션을 폐기합니다.


Connection 속성

autocommit

@property
def autocommit(self) -> bool

@autocommit.setter
def autocommit(self, value: bool) -> None

자동 커밋 모드를 조회하거나 설정합니다. 활성화되면 각 문장이 즉시 커밋됩니다. 이 속성을 설정하면 서버에서 트랜잭션 상태를 플러시하기 위해 SetDbParameterPacket과 CommitPacket을 보냅니다. 두 요청은 하나의 CAS 세션에 적용됩니다. 그 사이 CAS가 재활용되면 대체 세션에 새 값을 먼저 복원한 뒤 그 세션으로 COMMIT을 보냅니다(호출당 재접속은 최대 한 번). COMMIT이 실패하면 연결을 닫고 이전 값을 유지하며 원인을 연결한 OperationalError를 발생시킵니다(#551).

conn = pycubrid.connect(database="testdb")
print(conn.autocommit)  # False

conn.autocommit = True
# 이제 문장이 자동 커밋됨

timing_stats

@property
def timing_stats(self) -> TimingStats | None

연결이 enable_timing=True(또는 PYCUBRID_ENABLE_TIMING 설정)로 열렸을 때 연결별 TimingStats 누산기를 반환합니다. 타이밍이 비활성화되면 None을 반환합니다 — 이 경우 타이밍 모듈은 임포트되지 않으며 핫 경로에 오버헤드가 없습니다.

import pycubrid

conn = pycubrid.connect(database="testdb", enable_timing=True)
cur = conn.cursor()
cur.execute("SELECT 1")
cur.fetchall()

stats = conn.timing_stats
assert stats is not None
print(stats)
# TimingStats(connect=1 calls, 12.345ms total, 12.345ms avg,
#             execute=1 calls, 0.987ms total, 0.987ms avg,
#             fetch=0 calls,
#             close=0 calls)

print(stats.execute_count, stats.execute_total_ns)  # 1 987000

connect()가 새 세션에서 실행하는 이스케이프 모드 probe는 집계하지 않습니다. SELECT 1의 한 행은 execute 응답에 함께 오므로 fetchall()은 FETCH 요청을 보내지 않고 fetch는 0으로 남습니다(#899).

stats.reset()  # 모든 카운터 초기화

전체 가이드는 타이밍·프로파일링 훅을 참고하세요.


Connection 컨텍스트 매니저

Connection은 컨텍스트 매니저 프로토콜(__enter__ / __exit__)을 구현합니다.

  • 성공적 종료 시: commit() 후 close() 호출.
  • 예외 시: rollback() 후 close() 호출.
  • 블록의 예외는 항상 그대로 전파됩니다. 그 예외가 이미 폐기한 세션은 닫기만 하며(롤백이나 재연결 없음), 실패한 롤백은 로그로 남기고 원래 예외의 note에 기록합니다. AsyncConnection(async with)도 같으며, 취소된 블록은 취소 상태로 남습니다. 컨텍스트 매니저 프로토콜을 참고하세요.
with pycubrid.connect(database="testdb") as conn:
    cur = conn.cursor()
    cur.execute("INSERT INTO users (name) VALUES ('bob')")
    # 종료 시 자동 커밋

# 여기서 conn은 닫혀 있음

Cursor 클래스

pycubrid.cursor.Cursor

SQL 문 실행과 결과 조회를 위한 데이터베이스 커서를 나타냅니다.

Cursor 생성자

class Cursor:
    def __init__(self, connection: Connection) -> None

참고: Cursor를 직접 생성하지 마세요. connection.cursor()를 사용하세요.

Cursor 메서드

execute(operation, parameters)

def execute(
    self,
    operation: str,
    parameters: Sequence[Any] | None = None,
) -> Cursor

SQL 문을 준비하고 실행합니다.

파라미터:

파라미터 타입 설명
operation str 선택적 ? 플레이스홀더가 있는 SQL 문
parameters Sequence 또는 None 바인딩할 비문자열 위치 파라미터 값

반환: 커서 자신 (체이닝용).

이전 쿼리 핸들을 닫은 뒤 execute()는 파라미터를 바인딩하거나 새 문장을 보내기 전에 결과 상태를 초기화하며, 버퍼에 남은 행과 보관 중인 FETCH 페이지 오류도 버립니다. 바인딩이나 요청이 실패하면 description은 None, rowcount는 -1, lastrowid는 None이 되고, fetch 메서드는 InterfaceError("No result set available")를 발생시킵니다. 이후 execute()가 성공하면 커서를 다시 사용할 수 있습니다. 이전 핸들을 닫는 데 실패하면 execute()는 버퍼에 남은 결과와 페이지 오류를 유지하지만, 연결 무효화나 재접속 처리가 핸들을 해제할 수 있습니다. 새 요청의 응답을 디코딩할 수 없더라도 그 응답이 새 쿼리 핸들을 열었다면 정리를 위해 계속 추적합니다. 이 동작은 Cursor와 AsyncCursor에 모두 적용됩니다.

발생: - 커서가 닫혔으면 InterfaceError - SQL 오류나 파라미터 불일치 시 ProgrammingError

# 단순 쿼리
cur.execute("SELECT * FROM users")

# 파라미터화 쿼리 (qmark 방식)
cur.execute("SELECT * FROM users WHERE age > ?", [21])

# 파라미터와 함께 INSERT
cur.execute("INSERT INTO users (name, age) VALUES (?, ?)", ["alice", 30])

중요: dict 같은 매핑은 파라미터 바인딩에 지원되지 않습니다. 매핑을 전달하면 ProgrammingError("parameters must be a sequence")가 발생합니다.

지원 파라미터 타입:

Python 타입 SQL 리터럴
None NULL
bool 1 / 0
str 'escaped'
bytes X'hex'
int, float 숫자 리터럴
Decimal 숫자 리터럴
datetime.date DATE'YYYY-MM-DD'
datetime.time TIME'HH:MM:SS'
datetime.datetime DATETIME'YYYY-MM-DD HH:MM:SS.mmm'
Set / Multiset / Sequence SET{...} / MULTISET{...} / SEQUENCE{...}

executemany(operation, seq_of_parameters, *, prepared=False)

def executemany(
    self,
    operation: str,
    seq_of_parameters: Sequence[Sequence[Any]],
    *,
    prepared: bool = False,
) -> Cursor

같은 SQL 문을 서로 다른 파라미터 세트로 반복 실행합니다. 각 원소는 비문자열 시퀀스여야 합니다. 비-SELECT 문의 경우 rowcount는 영향받은 행의 누적 합계로 설정됩니다.

빈 파라미터 목록으로 executemany(operation, [])를 호출하면 SQL을 실행하지 않고 이전 쿼리 핸들을 닫은 뒤 커서를 description=None, rowcount=0, lastrowid=None으로 초기화합니다. 이전 행을 가져올 수 없으며 커서 자체를 반환합니다. 활성 쿼리 핸들이 없으면 요청을 보내지 않습니다. 이전 핸들 닫기가 실패하면 예외가 전파되고 핸들을 계속 추적합니다. 비동기 커서에도 같은 계약이 적용됩니다.

data = [("alice", 30), ("bob", 25), ("carol", 28)]
cur.executemany("INSERT INTO users (name, age) VALUES (?, ?)", data)
print(cur.rowcount)  # 3
부분 실패: 실패한 행

DML 배치의 일부 행이 실패해도 서버는 나머지 행을 모두 실행하고 실패한 행만 되돌립니다(#841). autocommit이 켜져 있으면 성공한 행마다 커밋하고, 꺼져 있으면 commit()은 성공한 행을 남기고 rollback()은 모두 지웁니다. executemany()는 첫 실패 행의 오류를 이전과 같은 클래스, 메시지, errno, sqlstate로 내며, 예외에 모든 행의 결과가 담깁니다.

속성 타입 의미
batch_index int 이 예외가 가리키는 행(첫 실패 행, 또는 멈춘 준비된 배치의 중단된 행)의 seq_of_parameters 안 위치
batch_errors tuple[tuple[int, DatabaseError], ...] 실패한 모든 행의 (index, error), 행 순서. 각 error는 발생한 예외와 같은 매핑을 따르고 자신의 batch_index를 가집니다
batch_rowcounts tuple[int, ...] 실행된 행마다 하나씩, 행 순서: 그 행이 영향을 준 행 수, 실패한 행은 -1

부분 실패 뒤에도 rowcount는 이전처럼 -1입니다. 배치 전체는 실패했고, autocommit이 꺼져 있으면 적용된 행도 아직 롤백될 수 있기 때문입니다. 적용된 행 수는 sum(c for c in exc.batch_rowcounts if c >= 0)입니다.

try:
    cur.executemany("INSERT INTO users (id) VALUES (?)", [(1,), (2,), (1,), (3,)])
except pycubrid.IntegrityError as exc:
    print(exc.batch_index)  # 2
    print([i for i, _error in exc.batch_errors])  # [2]
    print(exc.batch_rowcounts)  # (1, 1, -1, 1)
    print(cur.rowcount)  # -1

배치 행 실패로 발생하지 않은 예외에서는 batch_index, batch_errors, batch_rowcounts가 모두 None입니다. DML이 아닌 문(예: SELECT)은 execute()로 한 행씩 실행되어 첫 실패에서 멈추며, 이 속성을 설정하지 않습니다. 서버가 문장을 중단하면(ER_INTERRUPTED) 거기서 멈추므로 batch_rowcounts는 중단된 행에서 끝나고, 그 끝 뒤의 행은 실행되지 않은 것입니다(마지막 행이 중단된 경우에는 배치 전체를 덮습니다). 이때도 리터럴 배치는 첫 실패 행의 오류를 냅니다. 비동기 커서와 executemany(..., prepared=True)에도 같은 계약이 적용됩니다.

준비된 배치: prepared=True

executemany(..., prepared=True)(#839)는 INSERT, UPDATE, DELETE, MERGE를 PREPARE 한 번과, 모든 행의 값을 타입이 있는 호스트 변수로 담은 EXECUTE_ARRAY 요청(함수 코드 21) 한 번으로 보냅니다. 기본값(prepared=False)은 위의 리터럴 배치를 그대로 사용합니다. CUBRID 10.2-11.4에서 10,000행 배치는 INSERT, UPDATE, DELETE가 리터럴 배치 시간의 0.33-0.62배, MERGE가 0.12-0.21배 걸렸습니다 (PERFORMANCE.md 참고).

cur.executemany(
    "INSERT INTO users (name, age) VALUES (?, ?)",
    [("alice", 30), ("bob", None), ("carol", 2**40)],
    prepared=True,
)
  • 문. 첫 키워드가 INSERT, UPDATE, DELETE, MERGE인 문만 받습니다(INSERT/*x*/INTO처럼 동사 바로 뒤의 주석은 키워드를 끝냅니다, #898). 그 밖의 문 (SELECT, CALL, WITH ... INSERT 포함)은 NotSupportedError를 내며 리터럴 배치로 대체하지 않습니다. 모든 자리표시자는 서버가 타입을 정할 수 있어야 합니다. MERGE의 소스에는 CAST(? AS INT)처럼 씁니다.
  • 값. PARAMETER_BINDING.md의 준비된 호스트 변수 타입입니다. None, bool, int(범위에 따라 INT, BIGINT, NUMERIC), float, Decimal, str, 비어 있지 않은 bytes/bytearray, date, time, datetime. 값마다 자기 타입 바이트를 가지므로 한 컬럼에 행마다 다른 타입을 섞을 수 있습니다. pycubrid.types.Set, Multiset, Sequence는 서버가 변환하는 문자열 원소(str, int, None)의 컬렉션으로 보냅니다. 브로커가 MULTISET 바인딩 종류를 거부하므로 Multiset은 SEQUENCE로 보냅니다. pycubrid.Lob 값은 autocommit이 꺼진 동기 커서에서 받습니다(아래 참고).
  • 전송 전 검사. seq_of_parameters는 행의 어떤 이터러블이든 됩니다(제너레이터는 한 번 소비합니다. 타입 주석은 기본 경로처럼 Sequence[Sequence[Any]]로 둡니다). 요청을 보내기 전에 모든 행을 인코딩합니다. 시퀀스가 아닌 행, 길이가 다른 행, 위 집합 밖의 값은 위치를 담아 예외(ProgrammingError, DataError, NotSupportedError)를 냅니다. 예: rows[2][1]: nan and inf are not supported by CUBRID. 빈 행은 NotSupportedError를 냅니다. 메시지에 값 자체는 들어가지 않습니다. PREPARE 뒤 서버의 표시 개수가 행 길이와 다르면 ProgrammingError를 내고 어떤 행도 실행하지 않습니다.
  • 부분 실패와 rowcount. 리터럴 배치처럼 서버는 모든 행을 실행하고 실패한 행만 되돌립니다. autocommit이 켜져 있으면 성공한 행마다 커밋하고, 꺼져 있으면 commit()은 성공한 행을 남기고 rollback()은 모두 지웁니다. 첫 실패 행의 오류를 리터럴 배치와 같은 예외 매핑과 같은 행별 속성(부분 실패)으로 내고 rowcount는 -1로 둡니다. 성공하면 rowcount는 행별 개수의 합입니다. lastrowid는 None입니다. autocommit이 꺼진 상태에서 교착 상태나 서버 종료가 일어나면 트랜잭션 전체가 중단되고 요청 전체가 실패합니다. 중단된 행(ER_INTERRUPTED)이 있으면 서버가 멈추며 그 뒤의 행은 실행되지 않고, 앞선 행의 오류 대신 중단 오류를 냅니다. 이때 batch_index는 중단된 행이고, batch_errors는 여전히 첫 실패 행부터 시작하며, batch_rowcounts는 중단된 행에서 끝나므로 그 끝 뒤의 행은 실행되지 않은 것입니다. 마지막 행이 중단된 경우도 같은 방식으로 냅니다.
  • 서버 메시지. 값은 리터럴이 아니라 호스트 변수로 변환되므로 변환 오류는 "Cannot coerce host var to type ..."(오류 -494, 리터럴 배치와 같은 예외 클래스)로 나옵니다.
  • LOB 값. 이 연결의 create_lob()으로 만든 Lob은 핸들로 바인딩합니다. autocommit이 켜져 있으면 행마다의 커밋이 세션의 임시 LOB 파일을 모두 지워 두 번째 행이 -1016으로 실패하므로, Lob이 든 배치는 아무것도 보내기 전에 NotSupportedError를 냅니다. autocommit을 끄고 배치 뒤 commit()을 호출하십시오. 닫혔거나, 다른 연결의 것이거나, 재접속 전에 만든 Lob은 InterfaceError를 냅니다. 비동기 커서는 Lob 값을 NotSupportedError로 거부합니다.
  • 핸들. 준비된 문 핸들은 다른 결과와 같이 해제됩니다. 커서의 다음 문, commit()/rollback(), close()에서 해제됩니다. autocommit이 켜져 있고 브로커 문장 풀링이 켜져 있으면 별도 CLOSE_REQ 대신 다음 문의 요청(PREPARE_AND_EXECUTE나 다음 배치의 PREPARE)에 실려 해제됩니다. autocommit이 켜져 있을 때 브로커가 PREPARE 직후 연결을 닫으면 배치는 OperationalError로 실패하며 자동으로 재시도하지 않습니다.

prepared=False는 리터럴 배치의 동작을 모두 유지합니다. bool이 아닌 prepared는 ProgrammingError를 냅니다. AsyncCursor.executemany()에도 같은 계약이 적용됩니다.

비동기 인코딩과 취소(#882). prepared=True인 AsyncCursor.executemany()와 INSERT, UPDATE, DELETE, MERGE의 기본 리터럴 배치는 첫 요청 전에 모든 행을 동기적으로 인코딩하거나 렌더링하므로, 그동안 이벤트 루프의 다른 태스크는 실행되지 않고 취소도 이를 중단할 수 없습니다. 그 밖의 문(예: SELECT)은 행마다 await하는 execute()를 한 번씩 실행하므로, 행 사이에 다른 태스크가 실행되고 나중 행에서 요청된 취소는 앞선 행의 요청 뒤에 반영됩니다. 걸리는 시간은 행 인코딩 비용과 비슷합니다(동기와 비동기 특성 참고). 루프의 응답성이 중요하면 아주 큰 배치는 나누세요. 느리거나 블로킹되는 행 이터레이터도 같은 방식으로 루프를 막습니다. 취소가 반영되는 지점:

  • 행을 인코딩하는 동안 요청된 취소: 인코딩이 끝난 뒤, 어떤 요청도 보내기 전에 발생합니다. 세션은 열린 채로 유지되며 서버에서 실행된 것은 없습니다.
  • PREPARE를 보낸 뒤: 응답이 나중에 도착할 수 있으므로 세션을 폐기하며, EXECUTE_ARRAY는 보내지 않습니다.
  • EXECUTE_ARRAY(리터럴 배치는 EXECUTE_BATCH)를 보낸 뒤: 행이 실행되었을 수 있고, autocommit이면 커밋되었을 수 있습니다. 세션을 폐기하며 배치를 다시 보내지 않습니다. 재시도하기 전에 데이터를 확인하세요.

nextset()

def nextset(self) -> None

DB-API 호환 메서드. pycubrid는 이 인터페이스로 여러 서버 결과 집합을 노출하지 않으므로, nextset()은 커서가 열려 있는지 확인한 후 항상 None을 반환합니다.

assert cur.nextset() is None

executemany_batch(sql_list, auto_commit)

def executemany_batch(
    self,
    sql_list: list[str],
    auto_commit: bool | None = None,
) -> list[tuple[int, int]]

여러 개의 서로 다른 SQL 문을 서버로 단일 배치 요청으로 실행합니다. SQL 문이 제각각일 때 execute()를 루프로 호출하는 것보다 효율적입니다.

파라미터:

파라미터 타입 설명
sql_list list[str] 완전한 SQL 문의 리스트
auto_commit bool 또는 None 이 배치의 자동 커밋 오버라이드 (기본: 연결 설정)

반환: (statement_type, result_count) 튜플의 리스트.

results = cur.executemany_batch([
    "CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY, val VARCHAR(50))",
    "INSERT INTO t1 (val) VALUES ('hello')",
    "INSERT INTO t1 (val) VALUES ('world')",
])
# results: [(4, 0), (20, 1), (20, 1)]
# statement_type 4 = CREATE_CLASS, 20 = INSERT

참고: executemany_batch는 pycubrid 확장이며 PEP 249의 일부가 아닙니다. 이전 쿼리 핸들을 닫은 뒤 배치 요청 전에 커서 결과 상태를 초기화합니다. 전송 또는 응답 파싱 오류를 포함한 배치 실패 시 description=None, rowcount=-1, lastrowid=None이며 이전 행을 가져올 수 없습니다. 문별 오류는 해당 데이터베이스 예외를 발생시키며 일부 성공 결과로 최종 행 수를 설정하지 않습니다. 예외에는 sql_list 안 위치로 batch_index, batch_errors, batch_rowcounts가 담깁니다(부분 실패 참고). 이전 핸들 닫기가 실패하면 배치를 전송하지 않고 핸들을 계속 추적합니다. executemany_batch에 직접 넘긴 SQL은 호출자가 렌더링한 것이므로 세대 검사를 하지 않으며, 자동 재접속(#485) 뒤에도 그대로 새 세션으로 보냅니다. 바인딩 뒤 세션이 바뀌었을 때 거부되는 것은 execute()/executemany()가 파라미터로 렌더링한 SQL뿐입니다.


fetchone()

def fetchone(self) -> tuple[Any, ...] | None

쿼리 결과 집합의 다음 행을 가져옵니다. 더 이상 행이 없으면 None을 반환합니다. 로컬 버퍼가 소진되면 서버에서 자동으로 더 가져옵니다 (fetch당 100행).

cur.execute("SELECT name, age FROM users")
row = cur.fetchone()
if row:
    name, age = row

명시적 연결 복구에 관한 참고: 정상적인 CAS_INFO[0]=0 응답은 세션을 교체하거나 커서를 무효화하지 않습니다. 일부 행만 버퍼에 있는 상태에서 실제 연결 실패 후 ping(reconnect=True) 복구가 성공하면 버퍼의 행은 계속 읽을 수 있습니다. 버퍼가 소진된 뒤 fetchone()/fetchmany()/fetchall()은 기존 서버 핸들이 유효하지 않아 result set lost due to broker reconnect mid-fetch 메시지의 OperationalError를 발생시킵니다. 쿼리는 자동 재실행되지 않으므로 명시적으로 다시 실행해야 합니다. execute()와 close()는 무효화 플래그를 초기화합니다.

트랜잭션 경계 이후 fetch: commit()과 rollback()은 쿼리 핸들을 무효화하지만 이미 로컬 버퍼로 받은 행은 유지합니다. 캐시된 행은 읽을 수 있으며, 전체 행을 받은 결과나 소진된 결과는 정상 EOF 동작을 유지합니다. 미완료 결과가 무효화된 핸들로 추가 서버 FETCH를 요구하면 동기·비동기 fetchone()/fetchmany()/fetchall()은 조용히 EOF를 반환하는 대신 InterfaceError를 발생시킵니다. 이 경계를 넘는 fetchmany()·fetchall()은 일부 행 리스트를 성공 결과로 반환하지 않지만, 오류 전에 로컬 행을 이미 소비했을 수 있습니다. 계속하려면 새 쿼리를 명시적으로 실행하세요. SELECT의 투명 재실행이나 holdable 결과를 보장하지 않으며, 재연결 무효화의 별도 OperationalError는 유지합니다.

이후 fetch 페이지의 데이터 오류 (#507): FETCH 페이지에 pycubrid가 표현할 수 없는 값(연결 charset으로 유효하지 않은 텍스트 #492, 해석할 수 없는 타임존 #413, 0 날짜 #512)이 들어 있으면, 그 페이지에 도달한 fetchone(), fetchmany(), fetchall() 호출(또는 반복 단계)이 DataError를 발생시킵니다. 잘못된 값보다 앞선 행을 포함해 페이지 전체가 반환되지 않습니다. 그 호출이 이미 모은 행은 사라지지 않고, 다음 fetch 호출이 서버에 요청하지 않고 반환합니다. 따라서 오류 뒤의 fetchmany()나 fetchall()은 그 행들을 반환합니다(요청한 수보다 적을 수 있음). 그 뒤로는 execute() 또는 close() 전까지 모든 fetch가 페이지를 다시 요청하지 않고 같은 DataError를 다시 발생시키며, 실패한 페이지와 그 이후의 행은 반환되지 않습니다. 연결은 계속 사용할 수 있고 커서는 서버 핸들을 유지합니다. 동기·비동기 커서의 동작은 같습니다. 해당 값 이후를 읽으려면 SQL에서 값을 변환(0 날짜 또는 날짜시간 값 참고)한 뒤 다시 실행하세요.


fetchmany(size)

def fetchmany(self, size: int | None = None) -> list[tuple[Any, ...]]

다음 size행을 가져옵니다. size를 생략하거나 None을 전달하면 cursor.arraysize를 사용합니다. 명시적인 size는 일반 Python int여야 합니다. 실수, 불리언과 그 밖의 타입은 행을 소비하거나 다음 페이지를 요청하기 전에 ProgrammingError를 발생시킵니다. 0과 음의 정수는 행을 소비하거나 서버에 접속하지 않고 빈 리스트를 반환하며 DeprecationWarning을 발생시킵니다(아래 참고). AsyncCursor.fetchmany()에도 같은 규칙을 적용하며, 닫힌 커서와 결과 집합 유무를 먼저 검사합니다.

cur.execute("SELECT * FROM users")
batch = cur.fetchmany(10)  # 최대 10행

사용 중단 예정: size <= 0인 fetchmany(size). pycubrid 1.13.0부터 동기·비동기 fetchmany()는 명시적인 정수 size가 0 또는 음수이면 호출한 줄을 가리키는 DeprecationWarning을 발생시킵니다. 1.x에서는 결과가 바뀌지 않습니다 ([] 반환, 행 소비 없음, FETCH 전송 없음). pycubrid 2.0에서는 대신 ProgrammingError("size must be a positive integer")를 발생시키며, 이는 arraysize 설정자가 이미 적용하는 규칙과 같습니다 (#678). 생략/None과 양의 정수는 경고하지 않으며, pycubrid.compat 래퍼는 size <= 0에 대해 경고 없이 계속 []를 반환합니다. 계산된 크기가 0이 될 수 있으면 호출을 건너뛰세요. 이 방식은 1.x와 2.0 모두에서 동작합니다:

rows = cur.fetchmany(remaining) if remaining > 0 else []

# AsyncCursor
rows = await cur.fetchmany(remaining) if remaining > 0 else []

fetchall()

def fetchall(self) -> list[tuple[Any, ...]]

쿼리 결과의 남은 행 전체를 가져옵니다. 남은 행이 없으면 빈 리스트를 반환합니다.

cur.execute("SELECT * FROM users")
all_rows = cur.fetchall()
for row in all_rows:
    print(row)

callproc(procname, parameters)

def callproc(
    self,
    procname: str,
    parameters: Sequence[Any] = (),
) -> Sequence[Any]

저장 프로시저를 호출합니다. CALL procname(?, ?, ...) 문을 구성해 실행합니다. procname은 단일 식별자이거나 비어 있지 않은 식별자를 점으로 구분한 이름이어야 합니다 (예: schema.my_procedure). schema..proc처럼 빈 부분이 있으면 SQL을 실행하기 전에 ProgrammingError가 발생합니다.

반환: 원본 parameters 시퀀스 (PEP 249에 따라).

저장 함수의 반환값은 결과 집합의 한 행입니다. fetchone()으로 가져옵니다. execute("CALL ...")(예: CALL find_user('dba') ON CLASS db_user 같은 메서드 호출 포함)와 EVALUATE도 같습니다. 각 값은 와이어에서 자신의 타입을 함께 전달하며 해당 타입의 컬럼 값처럼 디코딩됩니다(INT는 int, VARCHAR는 str, DATETIME은 datetime, 객체는 "OID:@page|slot|volume" 문자열, SQL NULL은 None). #542 이전에는 이 값들이 원시 bytes로 반환되었습니다. 브로커가 이 컬럼의 타입을 알려주지 않으므로 description의 컬럼 타입은 NULL(0)입니다.

cur.callproc("my_procedure", [1, "hello"])
cur.callproc("my_function")
(value,) = cur.fetchone()

setinputsizes(sizes)

def setinputsizes(self, sizes: Any) -> None

DB-API no-op. 호환성을 위해 받아들입니다.


setoutputsize(size, column)

def setoutputsize(self, size: int, column: int | None = None) -> None

DB-API no-op. 호환성을 위해 받아들입니다.


close()

def close(self) -> None

커서를 닫고 활성 쿼리 핸들을 해제합니다. 이미 닫힌 커서에서 호출하면 no-op입니다.


Cursor 속성

description

@property
def description(self) -> tuple[DescriptionItem, ...] | None

마지막으로 실행된 문의 결과 집합 메타데이터를 반환합니다. 쿼리가 실행되지 않았거나 마지막 문이 행을 반환하지 않았으면 None입니다.

각 항목은 7-튜플입니다:

(name, type_code, display_size, internal_size, precision, scale, null_ok)
#  str    int        None           None          int       int    bool
인덱스 필드 타입 설명
0 name str 컬럼 이름
1 type_code int CUBRID 데이터 타입 코드 (CUBRIDDataType 참고)
2 display_size None 사용 안 함
3 internal_size None 사용 안 함
4 precision int 컬럼 정밀도
5 scale int 컬럼 스케일
6 null_ok bool 컬럼의 NULL 허용 여부

null_ok는 브로커가 NULL 허용을 보고하면 True, NOT NULL과 기본키 컬럼이면 False입니다. CAS는 반대 의미의 is_non_null 플래그를 전송하며, pycubrid는 동기·비동기 커서에서 이를 PEP 249 의미로 변환합니다. 크기 필드는 계속 None, 컬렉션 타입 코드는 계속 16/17/18이며, 이 교정으로 CUBRIDdb 호환 프로필을 선택하지 않습니다.

cur.execute("SELECT name, age FROM users")
for col in cur.description:
    print(f"{col[0]}: type={col[1]}, precision={col[4]}, nullable={col[6]}")

rowcount

@property
def rowcount(self) -> int

마지막 execute() 호출이 영향받은 행 수. SELECT 문이거나 실행된 문이 없으면 -1을 반환합니다. 배치가 실패하면(일부 행이 적용되었더라도) -1입니다.


lastrowid

@property
def lastrowid(self) -> int | None

INSERT 문이 생성한 마지막 auto-increment 식별자. INSERT가 실행되지 않았거나 테이블에 auto-increment 컬럼이 없으면 None입니다.

cur.execute("INSERT INTO users (name) VALUES ('alice')")
print(cur.lastrowid)  # 예: 1

arraysize

@property
def arraysize(self) -> int

@arraysize.setter
def arraysize(self, value: int) -> None

fetchmany()의 기본 행 수. 기본값은 1입니다.

값은 양의 정수여야 합니다. 불리언과 실수는 허용하지 않습니다. AsyncCursor.arraysize에도 같은 검증을 적용합니다.

발생: 양의 정수가 아닌 값으로 설정하면 ProgrammingError. 잘못된 값을 대입해도 이전 값은 변경되지 않습니다.


이터레이터 프로토콜

Cursor는 이터레이터 프로토콜을 구현해 결과 행을 직접 반복할 수 있습니다:

cur.execute("SELECT name, age FROM users")
for name, age in cur:
    print(f"{name} is {age} years old")

내부적으로 fetchone()을 호출합니다. 행이 없으면 StopIteration을 발생시킵니다.


Cursor 컨텍스트 매니저

with conn.cursor() as cur:
    cur.execute("SELECT 1")
    print(cur.fetchone())
# cur는 자동으로 닫힘

AsyncConnection 클래스

pycubrid.aio.connection.AsyncConnection

asyncio에서 사용하는 Connection의 비동기 대응물 — 메서드 하나하나의 완전한 동일성이 아니라 유사한 서피스를 제공합니다.

선택된 메서드

메서드 시그니처 비고
cursor() def cursor(self) -> AsyncCursor 비동기 커서를 즉시 반환. await하지 마세요
commit() async def commit(self) -> None 현재 트랜잭션 커밋
rollback() async def rollback(self) -> None 현재 트랜잭션 롤백
close() async def close(self) -> None 연결과 추적 중인 커서 종료
ping() async def ping(self, reconnect: bool = True) -> bool 네이티브 CHECK_CAS 헬스 체크 (선택적 재연결)
get_server_version() async def get_server_version(self) -> str 엔진 버전 조회
get_last_insert_id() async def get_last_insert_id(self) -> str \| None 캐시된 브로커 식별자 문자열 또는 None 반환
get_schema_info() async def get_schema_info(...) -> GetSchemaPacket 파싱된 패킷 객체 반환
fetch_schema_info() async def fetch_schema_info(packet) -> list[tuple[Any, ...]] 전체 행을 읽고 원래 핸들 정리
close_schema_info() async def close_schema_info(packet) -> None 명시적·멱등 폐기
set_autocommit() async def set_autocommit(self, value: bool) -> None SetDbParameterPacket과 CommitPacket 전송

AsyncConnection은 동기 Connection.ping()과 동등한 비동기 ping()을 노출합니다. create_lob()은 동기 전용으로 유지됩니다. 같은 AsyncConnection의 동시 awaiter는 연결별 asyncio.Lock으로 직렬화되므로 공유 사용이 안전하지만, 요청은 여전히 한 번에 하나씩 실행됩니다.

await conn.connect()가 세션을 열고 설정(백슬래시 이스케이프 probe, autocommit)하는 동안 — ping(reconnect=True)와 close() 후 connect()의 재연결도 포함 — 같은 연결에 대한 다른 task의 작업은 설정이 끝날 때까지 대기합니다. 설정이 실패하면 세션은 폐기되고 대기 중인 각 task는 자신만의 예외를 발생시킵니다(#554). pycubrid 오류는 같은 클래스(하위 클래스의 생성자가 다르면 가장 가까운 pycubrid.exceptions 클래스)와 같은 code, errno, sqlstate, error_source(#952)를 가진 새 인스턴스로(원래 예외는 __cause__), 그 밖의 오류는 그 오류를 명시한 OperationalError (connection setup failed in another task: TimeoutError())로, 취소된 설정은 OperationalError로 발생합니다. 즉 connect()를 실행하는 task를 취소해도 그 task만 취소됩니다. 대기 중인 task 자체가 취소되면 여전히 asyncio.CancelledError가 발생합니다. 요청 내부의 CHECK_CAS 복구(#485)는 대신 연결 lock 아래에서 실행되며, 그 실패는 복구를 일으킨 요청에서 발생하고, 이후 요청은 연결이 닫힌 상태(InterfaceError)를 봅니다.

설정이 시작되기 전에 이 대기를 통과한 요청이, 설정 task가 이스케이프 probe를 위해 lock을 놓은 사이에 lock을 얻으면 아무것도 보내지 않고 재시도 가능한 OperationalError("connection setup in progress; retry operation")를 발생시킵니다(#894). 이 거부는 설정 중인 세션을 끊거나 교체하지 않습니다. get_schema_info(), commit(), rollback()과 커서 문장은 정리 작업이나 CHECK_CAS 복구보다 먼저 이 오류를 발생시키므로, 설정 task의 probe와 ping(reconnect=True)는 계속 성공합니다. 교체된 세션의 스키마 결과는 이미 폐기되었으므로 fetch_schema_info()는 여전히 InterfaceError("schema result has been retired")를 발생시키고 close_schema_info()는 여전히 아무 작업도 하지 않습니다.

AsyncConnection.__init__은 키워드 전용 autocommit: bool = False 인자를 받으며, await conn.connect()가 처음 완료될 때 자동 적용됩니다 — await conn.set_autocommit(True)과 같은 효과이지만, pycubrid.aio.connect() 팩토리를 거치지 않고 AsyncConnection을 직접 생성할 때도 사용할 수 있습니다.

async with await pycubrid.aio.connect(database="testdb") as conn:
    cur = conn.cursor()
    await cur.execute("INSERT INTO logs (msg) VALUES (?)", ["ok"])
    await conn.set_autocommit(True)
    await cur.close()

set_autocommit(value)

AsyncConnection.autocommit은 읽기 전용입니다. 변경하려면 await conn.set_autocommit(True)을 사용하세요. 동기 세터처럼 SetDbParameterPacket과 CommitPacket을 하나의 CAS 세션에서 보내며, CAS 재활용 및 실패 시 동작도 같습니다(#551).

ping(reconnect=True)

async def ping(self, reconnect: bool = True) -> bool

정상 세션에서는 SQL 없이 가벼운 네이티브 CHECK_CAS 헬스 체크를 수행합니다. 재연결 중에는 읽기 전용 이스케이프 모드 탐색 SELECT를 실행할 수 있습니다.

  • CAS 연결이 살아 있으면 True 반환.
  • 소켓이 열려 있으면 네이티브 CHECK_CAS 왕복을 수행합니다. CAS_INFO[0]=0은 트랜잭션 종료 후의 OUT_TRAN 상태이지 연결 해제가 아닙니다. OUT_TRAN 응답 뒤의 일반 요청 앞에는 자동 CHECK_CAS가 붙고, 실패할 때만 재접속합니다(#485).
  • reconnect=False이면 재접속하지 않으며 연결이 끊겼거나 검사에 실패하면 False를 반환합니다.
  • reconnect=True이면 기존 소켓을 먼저 검사하고, 연결이 끊겼거나 전송/프로토콜 오류가 발생했거나 CHECK_CAS가 음수 코드로 CAS–DB 링크 장애를 보고하면 재접속을 한 번 시도합니다. reconnect=False는 음수 응답을 False로 보고하고 재접속하지 않으며, 동기 드라이버처럼 그 손상된 세션을 닫습니다(이후 호출은 InterfaceError). 명시적으로 설정한 autocommit만 복원하며 임의의 SQL을 자동 재실행하지 않습니다. 자동 no_backslash_escapes 모드는 새 물리 세션마다 감지하지만 정상적인 동일 세션 검사에서는 감지하지 않습니다. 명시적 True/False는 유지됩니다. 감지 실패 시 대체 세션을 폐기하고 False를 반환합니다. 이전 세션 세대에서 바인딩한 비동기 파라미터 SQL은 전송 전에 거부하며, 자동 재바인딩·재실행 대신 호출자가 재시도 여부를 결정합니다.
  • 재접속이 실패하면 동기 드라이버처럼 오류 종류와 관계없이 False를 반환합니다. 잘못된 비밀번호(-171)처럼 서버가 OPEN_DATABASE를 거부한 경우도 포함합니다.
if not await conn.ping(reconnect=False):
    await conn.ping(reconnect=True)

AsyncCursor 클래스

pycubrid.aio.cursor.AsyncCursor

Cursor의 비동기 대응물.

선택된 메서드

메서드 시그니처 비고
execute() async def execute(self, operation: str, parameters: Sequence[Any] \| None = None) -> AsyncCursor 시퀀스 전용 파라미터 바인딩, 동기 커서와 동일 규칙
executemany() async def executemany(self, operation: str, seq_of_parameters: Sequence[Sequence[Any]], *, prepared: bool = False) -> AsyncCursor 비-SELECT 문용 배치 경로. prepared=True는 PREPARE + EXECUTE_ARRAY를 보냅니다(Lob 값 불가)
fetchone() async def fetchone(self) -> tuple[Any, ...] \| None 한 행 또는 None 반환
fetchmany() async def fetchmany(self, size: int \| None = None) -> list[tuple[Any, ...]] 생략/None은 arraysize 사용; 일반 int만 허용하고 0·음수는 DeprecationWarning과 함께 [] 반환(2.0은 ProgrammingError)
fetchall() async def fetchall(self) -> list[tuple[Any, ...]] 남은 행 반환
nextset() async def nextset(self) -> None DB-API 호환 메서드. 항상 None 반환
close() async def close(self) -> None 활성 쿼리 핸들 해제
cur = conn.cursor()          # await 불가
await cur.execute("SELECT * FROM users WHERE age > ?", [21])
rows = await cur.fetchall()
assert await cur.nextset() is None

Lob 클래스

pycubrid.lob.Lob

CUBRID 대형 객체(BLOB 또는 CLOB)를 나타냅니다.

Lob은 생성되거나 감싸진 물리 세션에 속합니다(#932). ping(reconnect=True) 또는 트랜잭션 밖에서 실패한 CHECK_CAS 검사가 그 세션을 교체하면, 이전 Lob의 read()와 write()는 요청을 보내기 전에 InterfaceError("LOB handle belongs to an earlier physical session")를 발생시킵니다. 재접속, 재시도, 새 세션 채택은 하지 않습니다. 호출 자신의 CHECK_CAS 검사에서 교체가 일어나면 그 호출은 OperationalError("LOB handle lost due to broker reconnect")를, 이후 호출은 InterfaceError를 발생시킵니다. connection.create_lob()으로 LOB를 다시 만들거나, 행을 다시 조회해 그 핸들을 새 Lob으로 감싸십시오. Lob은 핸들이 생성된 것인지 조회된 것인지 기록하지 않으므로 커밋된 행의 핸들에도 같은 규칙이 적용됩니다. 비동기 연결은 LOB를 지원하지 않으며, compat.native lob은 자체 세션 검사를 유지합니다.

Lob 팩토리 메서드

Lob.create(connection, lob_type)

@classmethod
def create(cls, connection: Connection, lob_type: int) -> Lob

서버에 새 LOB 객체를 만듭니다. connection.create_lob()을 사용하는 것을 권장합니다.

파라미터:

파라미터 타입 설명
connection Connection 활성 데이터베이스 연결
lob_type int CUBRIDDataType.BLOB (23) 또는 CUBRIDDataType.CLOB (24)

발생: lob_type이 BLOB이나 CLOB이 아니면 ValueError.


Lob 메서드

write(data, offset)

def write(self, data: bytes | bytearray | memoryview, offset: int = 0) -> int

offset부터 LOB에 바이트를 씁니다.

offset은 음수가 아닌 Python int여야 합니다. type(offset) is not int이면 거부되므로, bool(int의 서브클래스)과 float, str 등 다른 타입은 열린 LOB 검사 이후 연결 확인이나 패킷 전송 전에 InterfaceError를 발생시킵니다.

data는 bytes, bytearray, memoryview여야 합니다(#898). str과 None을 포함한 그 밖의 값은 offset 검사 뒤, 연결 확인이나 패킷 전송 전에 InterfaceError를 발생시킵니다. bytearray와 memoryview는 버퍼 프로토콜로 복사하므로, 여러 바이트 항목의 memoryview도 항목 수가 아니라 바이트 수(nbytes)를 쓰고 반환합니다.

이 타입들의 빈 값은 기존 열린 LOB·offset 타입/범위·연결 상태 검사를 거친 뒤 브로커 요청 없이 0을 반환합니다. 이 로컬 반환 전에 기존 와이어 인자 검증도 유지합니다. LOB의 바이트와 핸들은 바뀌지 않으며, 비어 있지 않은 쓰기는 서버 ACK 검사를 유지합니다. 비동기 LOB 지원은 추가하지 않습니다.

부호 있는 64비트 패킹 오류는 실제 연결의 비어 있지 않은 쓰기 경로와 동일하게 직렬화 오류를 원인으로 하는 DataError를 발생시킵니다.

반환: 쓴 바이트 수.


read(length, offset)

def read(self, length: int, offset: int = 0) -> bytes

offset부터 LOB에서 최대 length바이트를 읽습니다.

length와 offset은 음수가 아닌 Python int여야 합니다. bool과 float, str 등 다른 타입은 연결 확인이나 패킷 전송 전에 InterfaceError를 발생시킵니다.

읽기는 브로커가 요구하는 만큼 여러 번의 왕복으로 수행됩니다. CUBRID는 단일 LOB_READ 응답을 고정 크기로 제한하므로, read()는 반환된 바이트만큼 오프셋을 전진시키면서 length바이트를 모으거나 LOB의 끝에 도달할 때까지 내부적으로 반복합니다. read(0)은 서버 왕복 없이 b""를 반환합니다.

반환: 읽은 바이트.


Lob 속성

lob_handle

@property
def lob_handle(self) -> bytes

서버 통신에 사용되는 raw LOB 핸들 바이트.

핸들에는 LOB 크기가 들어 있으며, 핸들을 다시 보내면 서버는 이 크기를 그대로 믿습니다. write()가 성공하거나 일부만 성공할 때마다 크기 필드는 offset + bytes written까지 올라가고 내려가지는 않으며, 공식 CCI 드라이버와 같습니다(#441). 이 수정 전에는 쓴 뒤에도 핸들이 생성 시점의 크기(대개 0)를 유지했습니다.


lob_type

@property
def lob_type(self) -> int

LOB 타입 코드 (23 = BLOB, 24 = CLOB).


LOB 사용 참고

LOB 컬럼은 fetch 시 Lob 객체가 아니라 dict를 반환합니다:

cur.execute("SELECT clob_col FROM my_table")
row = cur.fetchone()
lob_info = row[0]
# {'lob_type': 24, 'lob_length': 1234, 'file_locator': '...', 'packed_lob_handle': b'...'}

LOB 데이터를 삽입하려면 문자열/바이트를 직접 전달하세요:

cur.execute("INSERT INTO my_table (clob_col) VALUES (?)", ["large text content"])

TimingStats 클래스

pycubrid.timing.TimingStats — pycubrid.TimingStats로도 재내보내기됩니다.

선택적 드라이버 수준 타이밍 계측의 연결별 누산기. 연결이 enable_timing=True(또는 PYCUBRID_ENABLE_TIMING 설정)로 열릴 때 자동 생성되며 Connection.timing_stats으로 노출됩니다.

언제 사용하나: cProfile이나 외부 프로파일러 없이, 실제 시간이 어디로 가는지(connect vs execute vs fetch vs close) 빠르게 프로세스 내 진단할 때. 깊은 핫 경로 분석은 성능 조사의 독립형 프로파일링 스크립트를 권장합니다.

카운터

모든 카운터는 일반 int 속성입니다. 지속 시간은 time.perf_counter_ns()로 캡처한 나노초입니다. 갱신은 내부 threading.Lock으로 직렬화되어, 연결을 구동하는 스레드가 아닌 다른 스레드에서 읽어도 안전합니다.

속성 타입 설명
connect_count / connect_total_ns int Connection.connect() 호출 총 횟수와 누적 경과 시간. 실패 시에도 기록됨.
execute_count / execute_total_ns int 동기·비동기 커서에서 성공한 execute()와 executemany() 호출과 누적 경과 시간. executemany() 한 번은 경로(EXECUTE_BATCH, prepared=True, 행별 루프)와 관계없이 한 번으로 집계합니다. 새 세션의 이스케이프 모드 probe 같은 드라이버 내부 요청은 집계하지 않습니다(#899).
fetch_count / fetch_total_ns int fetchone() / fetchmany() / fetchall()이 이미 버퍼에 있는 행 이후의 행을 위해 보내는 서버 FETCH 요청과 누적 경과 시간. execute 응답에 함께 온 행은 FETCH가 필요 없으므로, 버퍼에서 처리한 fetch 호출은 집계하지 않습니다.
close_count / close_total_ns int Connection.close() 호출과 누적 경과 시간.

메서드

reset()

def reset(self) -> None

모든 카운터를 원자적으로 0으로 리셋합니다. 워밍업 단계 이후 특정 코드 구간을 측정할 때 유용합니다.

__repr__()

사람이 읽을 수 있는 요약을 반환합니다. 예:

TimingStats(connect=1 calls, 12.345ms total, 12.345ms avg,
            execute=10 calls, 4.200ms total, 0.420ms avg,
            fetch=10 calls, 1.500ms total, 0.150ms avg,
            close=0 calls)

비동기 동등성

pycubrid.aio.AsyncConnection도 동일한 enable_timing 키워드를 받고 동일한 의미의 timing_stats 속성을 노출합니다. 비동기 지속 시간에는 이벤트 루프 스케줄링 지연이 포함됩니다 — 순수 서버 시간이 아니라 종단 간 클라이언트 측 지연 시간으로 다루세요.


예외 계층

pycubrid는 PEP 249 예외 계층 전체를 구현합니다:

graph TD
    exception[Exception]
    warning[Warning]
    error[Error]
    interface[InterfaceError]
    database[DatabaseError]
    data[DataError]
    operational[OperationalError]
    integrity[IntegrityError]
    internal[InternalError]
    programming[ProgrammingError]
    notsupported[NotSupportedError]

    exception --> warning
    exception --> error
    error --> interface
    error --> database
    database --> data
    database --> operational
    database --> integrity
    database --> internal
    database --> programming
    database --> notsupported

모든 예외는 msg(str)와 code(int) 속성을 가집니다. DatabaseError와 그 하위 클래스는 추가로 errno(int | None)와 sqlstate(str | None)를 가집니다.


Warning

class Warning(Exception):
    def __init__(self, msg: str = "", code: int = 0) -> None

중요한 경고에 사용됩니다 (예: 삽입 중 데이터 절단).


Error

class Error(Exception):
    def __init__(self, msg: str = "", code: int = 0) -> None

모든 pycubrid 오류의 기본 클래스.


InterfaceError

class InterfaceError(Error)

데이터베이스 인터페이스 관련 오류에 사용됩니다 — 닫힌 연결/커서에서 메서드 호출, 잘못된 인자 등.


DatabaseError

class DatabaseError(Error):
    def __init__(
        self,
        msg: str = "",
        code: int = 0,
        errno: int | None = None,
        sqlstate: str | None = None,
    ) -> None

데이터베이스 측 오류의 기본 클래스. 서버가 보고한 오류 상세를 위한 errno와 sqlstate를 포함합니다.

executemany() 또는 executemany_batch() 배치의 실패 행 때문에 발생한 예외는 batch_index(int), batch_errors(tuple[tuple[int, DatabaseError], ...]), batch_rowcounts(tuple[int, ...])도 가집니다. 의미는 부분 실패에 있습니다(#841). 그 밖의 DatabaseError에서는 None입니다. pickle과 copy에서도 유지되며, str()과 repr()에는 포함되지 않습니다.

error_source(읽기 전용, str | None, #903)는 브로커가 보고한 오류를 누가 만들었는지 알려 줍니다. 브로커는 각 오류 코드 앞에 인디케이터를 넣습니다(CUBRID src/broker/cas.h의 CAS_ERROR_INDICATOR 또는 DBMS_ERROR_INDICATOR):

error_source 인디케이터 예
"cas" -1: CAS 자체 잘못된 스키마 타입으로 호출한 get_schema_info(): errno=-1015
"server" -2: 데이터베이스 서버 구문 오류(-493), 잘못된 비밀번호(-171), 실패한 배치 행
None 없음 닫힌 연결이나 잘못된 형식의 응답처럼 드라이버가 직접 발생시킨 오류

errno와 code는 와이어의 번호를 그대로 유지합니다. 드라이버는 새 CAS 번호 체계를 요청하지 않으므로 CAS 오류는 이전 값(-1000부터 -1200, CAS 코드에 9000을 더한 값)을 유지합니다. 이 범위는 서버 코드와 겹치며, error_source로 둘을 구분합니다. CAS_ER_NOT_AUTHORIZED_CLIENT(-1018, 브로커의 접근 제어가 클라이언트 주소를 거부)는 서버 인디케이터로 전송되므로 "server"를 보고합니다. error_source는 pickle과 copy에서도 유지되며, str()과 repr()에는 포함되지 않습니다.


DataError

class DataError(DatabaseError)

데이터 처리 문제에 사용됩니다 (0으로 나누기, 숫자 오버플로 등). 조회한 문자 값(CHAR, VARCHAR, NCHAR, ENUM)이나 컬럼 이름이 연결 charset으로 유효하지 않을 때(JSON 값은 UTF-8 기준), 서버로 보낼 텍스트를 그 코덱으로 인코딩할 수 없을 때(해당 요청은 전혀 전송되지 않음)도 발생하며, TIMESTAMPTZ/TIMESTAMPLTZ/DATETIMETZ/DATETIMELTZ 값의 리전을 클라이언트의 IANA 타임존 데이터베이스로 해석할 수 없거나(tzdata 설치 필요) 오프셋이 ±24시간을 벗어날 때도 발생합니다(#413). 응답은 모두 읽었으므로 일반 커서에서는 연결을 계속 사용할 수 있으며, 이후 fetch 페이지에서 발생한 경우 그 페이지 전에 모은 행은 먼저 반환됩니다(fetchone()의 이후 fetch 페이지의 데이터 오류 참고, #507). get_schema_info()는 해석할 수 없는 FC9 응답을 받으면 연결을 폐기합니다. 명시적 prepared API(pycubrid.compat.native)는 대신 OperationalError를 발생시키고 세션을 폐기합니다.


OperationalError

class OperationalError(DatabaseError)

데이터베이스 연산 오류에 사용됩니다 (예기치 않은 연결 해제, 메모리 오류, 트랜잭션 실패, 연결 유실).


IntegrityError

class IntegrityError(DatabaseError)

관계형 무결성이 영향받을 때 사용됩니다 (외래 키 위반, 중복 키, 제약조건 위반). 참조하는 외래 키 때문에 거부된 DELETE, UPDATE, TRUNCATE도 포함합니다.


InternalError

class InternalError(DatabaseError)

내부 데이터베이스 오류에 사용됩니다 (잘못된 커서 상태, 동기화되지 않은 트랜잭션).


ProgrammingError

class ProgrammingError(DatabaseError)

프로그래밍 오류에 사용됩니다 (SQL 구문 오류, 테이블 없음, 파라미터 수 불일치, 지원되지 않는 파라미터 타입).


NotSupportedError

class NotSupportedError(DatabaseError)

지원되지 않는 메서드나 API가 호출될 때 사용됩니다.


UnknownConnectionOptionWarning

class UnknownConnectionOptionWarning(UserWarning)

PEP 249 예외가 아니라 Python 경고 범주입니다. 위 계층 구조 밖에 있으며, pycubrid.Warning(예외로 발생하는 PEP 249 데이터베이스 경고)과 의도적으로 구분됩니다.

연결 생성자가 인식하지 못하는 키워드를 받으면 발생합니다. 키워드는 그대로 무시되지만 경고가 그 이름을 알려 주고, 비슷한 이름이 있으면 철자 제안도 덧붙입니다. 그래서 read_timout=30 같은 오타가 조용히 묻히지 않습니다.

import warnings
import pycubrid

# 엄격: 알 수 없는 연결 옵션을 오류로 만듭니다.
warnings.simplefilter("error", pycubrid.UnknownConnectionOptionWarning)

# 느슨: 경고를 끕니다(예: 임의의 kwargs를 넘겨 주는 래퍼).
warnings.simplefilter("ignore", pycubrid.UnknownConnectionOptionWarning)

알 수 없는 옵션을 참고하세요.


오류 분류

pycubrid는 메시지 문구보다 숫자 오류 코드를 우선하여 서버 오류를 분류합니다. 네이티브 -631 (ER_NULL_CONSTRAINT_VIOLATION), -922 (ER_FK_INVALID, 부모가 없는 자식 행의 삽입 또는 수정), -924 (ER_FK_RESTRICT, 참조되는 부모 행의 삭제 또는 수정), -1284 (ER_TRUNCATE_PK_REFERRED, CUBRID 11.4에서 참조되는 부모 테이블의 TRUNCATE; 10.2는 -924를 반환)는 메시지 언어와 관계없이 SQLSTATE 23000의 IntegrityError를 발생시킵니다. 참조되는 기본 키의 삭제(-923, ER_FK_CANT_DROP_PK_REFERRED)는 스키마 변경 거부이므로 DatabaseError로 유지합니다. 단일 문장과 배치의 개별 문장 오류는 원래 숫자 값을 code와 errno에 모두 보존합니다. 알 수 없는 코드는 제약조건 같은 메시지가 있어도 DatabaseError로 유지합니다.

네이티브 -493 (ER_PT_SYNTAX)과 -494 (ER_PT_SEMANTIC)는 ProgrammingError / 42000을 사용하며 설명은 각각 구문 오류와 의미 오류입니다. -493은 잘못된 SQL과 존재하지 않는 클래스 모두에서 반환될 수 있으므로 코드만으로 테이블 부재 SQLSTATE를 판단할 수 없습니다. -671 (ER_CSS_RECV_OR_SEND)은 무결성 오류가 아닌 OperationalError / 08S01입니다. -4 (ER_INTERRUPTED, 살아 있는 세션에서 중단된 문장)는 풀과 ORM이 끊긴 연결로 취급하는 08S01 통신 링크 실패가 아니라 OperationalError / HY008(작업 취소)입니다(#895). 연결은 열린 채로 유지되지만 서버가 인터럽트된 트랜잭션을 이미 철회(abort)했을 수 있으므로(cubrid killtran 뒤 다음 문장은 -111을 보고함), 다시 사용하기 전에 롤백하세요. -3 (ER_OUT_OF_VIRTUAL_MEMORY)과 I/O 오류 -13, -14, -15 (ER_IO_READ, ER_IO_WRITE, ER_IO_WRITE_OUT_OF_SPACE)는 OperationalError, -2 (ER_GENERIC_ERROR)는 InternalError입니다. 인증 실패 -165 (ER_AU_INVALID_USER)와 -171 (ER_AU_INVALID_PASSWORD)은 두 드라이버 모두 OperationalError / 28000(잘못된 권한 지정)입니다(#903). 이전 릴리스에서는 DatabaseError / HY000이었습니다. SQLSTATE는 드라이버가 네이티브 의미를 변환한 값입니다. 배치도 단일 문장과 동일한 알려진 코드 SQLSTATE 조회 방식을 사용하며, 알 수 없는 코드는 기존 클래스 기본값을 유지합니다.

사용자는 -493만으로 또는 생성된 설명 문자열로 테이블 부재를 판단하면 안 됩니다. 관련 SQLAlchemy 리플렉션 수정은 sqlalchemy-cubrid #454에서 추적합니다.

네이티브 식별자는 CUBRID 서버 헤더 src/base/error_code.h (release/11.4, release/10.2는 -1284를 제외하고 값이 같음)와 공식 CCI 사본에 정의돼 있습니다. 드라이버 표의 모든 코드는 이 서버 코드입니다. CCI CCI_ER_*나 JDBC UErrorCode 같은 클라이언트 측 드라이버 코드는 브로커가 보내지 않으므로 표에 없습니다. 일반 실패 코드 -1 (ER_FAILED)의 프로토콜 오류 응답은 아래의 기존 메시지 기반 폴백을 유지하며, 배치의 개별 문장 오류는 코드로만 분류합니다:

오류 메시지 키워드 발생하는 예외
unique, duplicate, foreign key, constraint violation IntegrityError
syntax, unknown class, does not exist, not found ProgrammingError
그 외 전부 DatabaseError

get_error_description()

pycubrid.get_error_description(code: int) -> str | None

네이티브 CUBRID 서버 오류 코드에 대해 드라이버가 가진 짧은 영어 설명을 반환하고, 드라이버 표에 없는 코드이면 None을 반환합니다. 이 표는 드라이버에 내장된 고정 부분 집합이며, 조회는 서버에 질의하지 않습니다. 표에 항목이 없다는 것은 설명이 없다는 뜻일 뿐입니다. 조회는 예외를 내지 않으며, 표에 없는 코드의 서버 오류도 여전히 DatabaseError로 발생합니다.

import pycubrid

pycubrid.get_error_description(-493)    # 'Syntax error'
pycubrid.get_error_description(-4)      # 'Interrupted'
pycubrid.get_error_description(-99999)  # None

알려진 코드를 가진 예외의 repr()에도 같은 문구가 나옵니다. 예: ProgrammingError('...', errno=-493, description='Syntax error'). 이 문구는 진단용 보조 정보입니다. 오류는 위에서 설명한 대로 예외 클래스와 code/errno로 분류하고, 이 문구로 분류하지 마세요.


타입 객체

cursor.description 타입 코드와 비교하기 위한 PEP 249 타입 객체:

타입 객체 설명 매칭되는 CUBRID 타입
STRING 문자열 타입 CHAR, STRING, NCHAR, VARNCHAR, ENUM, CLOB, JSON
BINARY 바이너리 타입 BIT, VARBIT, BLOB
NUMBER 숫자 타입 SHORT, INT, BIGINT, FLOAT, DOUBLE, NUMERIC, MONETARY
DATETIME 날짜/시간 타입 DATE, TIME, DATETIME, TIMESTAMP
ROWID 행 ID 타입 OBJECT
import pycubrid

cur.execute("SELECT name FROM users")
type_code = cur.description[0][1]

if type_code == pycubrid.STRING:
    print("It's a string column")

타입 생성자

PEP 249 생성자 함수:

생성자 반환 타입 설명
Date(y, m, d) datetime.date 날짜 생성
Time(h, m, s) datetime.time 시간 생성
Timestamp(y,m,d,h,m,s) datetime.datetime 타임스탬프 생성
DateFromTicks(t) datetime.date Unix ticks로부터 날짜
TimeFromTicks(t) datetime.time Unix ticks로부터 시간
TimestampFromTicks(t) datetime.datetime Unix ticks로부터 타임스탬프
Binary(s) bytes bytes로부터 바이너리 문자열
import pycubrid

d = pycubrid.Date(2025, 1, 15)
t = pycubrid.Time(14, 30, 0)
ts = pycubrid.Timestamp(2025, 1, 15, 14, 30, 0)
b = pycubrid.Binary(b"\x00\x01\x02")

타입 지정 컬렉션 파라미터

class Set(elements: Iterable[Any] = ())
class Multiset(elements: Iterable[Any] = ())
class Sequence(elements: Iterable[Any] = ())

pycubrid.types에 정의되고 pycubrid에서 export됩니다(#567에서 추가). 각각 원소를 불변 tuple로 감싸며, 일반 동기/비동기 커서의 execute()/executemany()에서 타입이 지정된 CUBRID 컬렉션 리터럴 하나로 바인딩됩니다. 일반 set/list/tuple 파라미터는 계속 거부됩니다.

클래스 리터럴 서버 의미
Set SET{...} 중복 제거, 순서 유지 안 함
Multiset MULTISET{...} 중복 유지, 순서 유지 안 함
Sequence SEQUENCE{...} (LIST{...}와 같은 타입) 중복과 순서 유지
멤버 설명
.elements 저장된 원소 tuple
iter(), len() 원소 순회 / 개수
==, hash() 같은 클래스이면서 원소가 같을 때만 같음(세 타입 모두 순서를 구분)
  • 원소는 스칼라 파라미터 타입(None, bool, int, float, Decimal, str, bytes, bytearray, date, time, datetime)을 받으며 같은 보호된 렌더러로 렌더링됩니다. 중첩 컬렉션을 포함한 그 밖의 값은 ProgrammingError를 발생시킵니다.
  • 단일 str/bytes/bytearray 인자는 TypeError, 하위 클래스 생성은 TypeError, 속성 설정은 AttributeError를 발생시킵니다.
  • dict 인자는 세 클래스 모두에서 TypeError를 발생시킵니다(키만 조용히 쓰이고 값은 버려지기 때문). Sequence는 set/frozenset 인자에도 TypeError를 발생시킵니다(순회 순서가 보장되지 않기 때문). Set과 Multiset은 set/frozenset을 그대로 받습니다.
  • 이 인스턴스들은 copy.copy()(항상 같은 객체를 반환), copy.deepcopy()(모든 원소가 그 자체로 불변이면 같은 객체를 반환하고, bytearray처럼 가변인 원소가 있으면 원소까지 독립적으로 복사한 별개의 객체를 반환)와 pickle(동등한 인스턴스로 왕복)에 안전합니다. 기존 인스턴스에서 __init__을 다시 호출해도 아무 효과가 없으며 변경할 수 없습니다.
  • 조회한 컬렉션은 이 클래스로 반환되지 않습니다: decode_collections=True이면 여전히 frozenset(SET)과 list(MULTISET/SEQUENCE)입니다.
  • Sequence는 typing/collections.abc에도 있는 이름입니다. from pycubrid import *는 (Set과 함께) 이 이름을 이 클래스들로 가립니다. 같은 모듈에서 둘 다 필요하다면 from pycubrid.types import Sequence as CubridSequence처럼 명시적으로 import하세요.
from pycubrid import Multiset, Sequence, Set

cur.execute(
    "INSERT INTO t (tags, words, steps) VALUES (?, ?, ?)",
    (Set([1, 2, 3]), Multiset(["a", "a"]), Sequence([3, 1, 2])),
)

파라미터 바인딩을 참고하세요.