API 참조 (한국어)¶
🌐 API_REFERENCE.md의 번역입니다. 영어 원문이 표준이며, CI가 영어 원문과의 구조 일치를 검사합니다.
CUBRID용 순수 Python DB-API 2.0 드라이버 pycubrid의 완전한 API 문서.
목차¶
- 모듈 수준 속성
- 모듈 수준 생성자
pycubrid.connect()decode_collectionsjson_deserializercharset- 비동기 모듈 생성자
pycubrid.aio.connect()- 명시적 네이티브 호환 기능
- Connection 클래스
- 생성자
- 메서드
- 속성
- 컨텍스트 매니저
- Cursor 클래스
- 생성자
- 메서드
- 속성
- 이터레이터 프로토콜
- 컨텍스트 매니저
- AsyncConnection 클래스
- AsyncCursor 클래스
- Lob 클래스
- 팩토리 메서드
- 메서드
- 속성
- TimingStats 클래스
- 예외 계층
- Warning
- Error
- InterfaceError
- DatabaseError
- DataError
- OperationalError
- IntegrityError
- InternalError
- ProgrammingError
- NotSupportedError
- UnknownConnectionOptionWarning
get_error_description()- 타입 객체
- 타입 생성자
- 타입 지정 컬렉션 파라미터
모듈 수준 속성¶
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, 알 수 없는 코덱·CUBRIDbinary·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()¶
브로커 핸드셰이크와 함께 TCP CAS 세션을 수립하고 데이터베이스를 엽니다. 생성자가 자동 호출합니다. 이미 연결된 인스턴스에서 호출하면 no-op입니다.
발생: 네트워크 실패 시 OperationalError.
close()¶
연결과 추적 중인 모든 커서를 닫습니다. 서버로 CloseDatabasePacket을 보냅니다. 이미 닫힌 연결에서 호출하면 no-op입니다. close() 이후 다른 메서드 호출은 InterfaceError를 발생시킵니다.
commit()¶
현재 트랜잭션을 커밋합니다. 닫히지 않은 커서가 가진 쿼리 핸들에 CLOSE_REQ를
보낸 뒤 서버로 CommitPacket을 보냅니다(#485). 이미 받은 행은 계속 읽을 수
있습니다. autocommit 모드에서는 END_TRAN을 보내지 않으므로 커서를 닫아 서버
핸들을 해제하세요. statement pooling 브로커에서는 이 해제가 별도의 CLOSE_REQ 없이
다음 문장에 실려 가며, close() 없이 수거된 커서도 같은 방식으로 해제됩니다
(지연 닫기, #488). 이전 트랜잭션 밖 응답 뒤 CAS가 세션을 재활용했다면 요청 전에
트랜잭션 경계에서 CAS가 재활용되는 경우에
설명한 검증된 재접속이 먼저 수행됩니다.
발생: 연결이 닫혔으면 InterfaceError.
rollback()¶
현재 트랜잭션을 롤백합니다. 닫히지 않은 커서가 가진 쿼리 핸들에 CLOSE_REQ를
보낸 뒤 서버로 RollbackPacket을 보냅니다(#485).
발생: 연결이 닫혔으면 InterfaceError.
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()¶
서버 엔진 버전 문자열을 반환합니다 (예: "11.2.0.0378").
get_last_insert_id()¶
가장 최근 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로 변환하기 전에 확인합니다:
정상 값은 기존 문자열 타입을 유지하며 cursor.lastrowid는 계속
int | None입니다. 비동기 메서드에도 같은 규칙이 적용됩니다.
빈 배치는 커서의 lastrowid를 None으로 초기화하지만 연결 캐시는 유지합니다.
비어 있지 않은 배치를 시작하기 전에 기존 쿼리 종료가 실패하면 두 식별자 값은
그대로 유지되며 예외가 전달됩니다.
ping(reconnect=True)¶
정상 세션에서는 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를 발생시켰습니다. 비동기 드라이버도 같게 동작합니다.
create_lob(lob_type)¶
서버에 새 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¶
자동 커밋 모드를 조회하거나 설정합니다. 활성화되면 각 문장이 즉시 커밋됩니다. 이 속성을 설정하면 서버에서 트랜잭션 상태를 플러시하기 위해 SetDbParameterPacket과 CommitPacket을 보냅니다. 두 요청은 하나의 CAS 세션에 적용됩니다. 그 사이 CAS가 재활용되면 대체 세션에 새 값을 먼저 복원한 뒤 그 세션으로 COMMIT을 보냅니다(호출당 재접속은 최대 한 번). COMMIT이 실패하면 연결을 닫고 이전 값을 유지하며 원인을 연결한 OperationalError를 발생시킵니다(#551).
conn = pycubrid.connect(database="testdb")
print(conn.autocommit) # False
conn.autocommit = True
# 이제 문장이 자동 커밋됨
timing_stats¶
연결이 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).
전체 가이드는 타이밍·프로파일링 훅을 참고하세요.
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 생성자¶
참고:
Cursor를 직접 생성하지 마세요.connection.cursor()를 사용하세요.
Cursor 메서드¶
execute(operation, parameters)¶
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()¶
DB-API 호환 메서드. pycubrid는 이 인터페이스로 여러 서버 결과 집합을 노출하지 않으므로, nextset()은 커서가 열려 있는지 확인한 후 항상 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()¶
쿼리 결과 집합의 다음 행을 가져옵니다. 더 이상 행이 없으면 None을 반환합니다. 로컬 버퍼가 소진되면 서버에서 자동으로 더 가져옵니다 (fetch당 100행).
명시적 연결 복구에 관한 참고: 정상적인
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)¶
다음 size행을 가져옵니다. size를 생략하거나 None을 전달하면
cursor.arraysize를 사용합니다. 명시적인 size는 일반 Python int여야 합니다.
실수, 불리언과 그 밖의 타입은 행을 소비하거나 다음 페이지를 요청하기 전에
ProgrammingError를 발생시킵니다. 0과 음의 정수는 행을 소비하거나 서버에
접속하지 않고 빈 리스트를 반환하며 DeprecationWarning을 발생시킵니다(아래 참고).
AsyncCursor.fetchmany()에도 같은 규칙을 적용하며, 닫힌 커서와 결과 집합 유무를
먼저 검사합니다.
사용 중단 예정:
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 모두에서 동작합니다:
fetchall()¶
쿼리 결과의 남은 행 전체를 가져옵니다. 남은 행이 없으면 빈 리스트를 반환합니다.
callproc(procname, parameters)¶
저장 프로시저를 호출합니다. 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)입니다.
setinputsizes(sizes)¶
DB-API no-op. 호환성을 위해 받아들입니다.
setoutputsize(size, column)¶
DB-API no-op. 호환성을 위해 받아들입니다.
close()¶
커서를 닫고 활성 쿼리 핸들을 해제합니다. 이미 닫힌 커서에서 호출하면 no-op입니다.
Cursor 속성¶
description¶
마지막으로 실행된 문의 결과 집합 메타데이터를 반환합니다. 쿼리가 실행되지 않았거나 마지막 문이 행을 반환하지 않았으면 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¶
마지막 execute() 호출이 영향받은 행 수. SELECT 문이거나 실행된 문이 없으면 -1을 반환합니다. 배치가 실패하면(일부 행이 적용되었더라도) -1입니다.
lastrowid¶
INSERT 문이 생성한 마지막 auto-increment 식별자. INSERT가 실행되지 않았거나 테이블에 auto-increment 컬럼이 없으면 None입니다.
arraysize¶
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 컨텍스트 매니저¶
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)¶
정상 세션에서는 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를 거부한 경우도 포함합니다.
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)¶
서버에 새 LOB 객체를 만듭니다. connection.create_lob()을 사용하는 것을 권장합니다.
파라미터:
| 파라미터 | 타입 | 설명 |
|---|---|---|
connection |
Connection |
활성 데이터베이스 연결 |
lob_type |
int |
CUBRIDDataType.BLOB (23) 또는 CUBRIDDataType.CLOB (24) |
발생: lob_type이 BLOB이나 CLOB이 아니면 ValueError.
Lob 메서드¶
write(data, offset)¶
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)¶
offset부터 LOB에서 최대 length바이트를 읽습니다.
length와 offset은 음수가 아닌 Python int여야 합니다. bool과 float,
str 등 다른 타입은 연결 확인이나 패킷 전송 전에 InterfaceError를
발생시킵니다.
읽기는 브로커가 요구하는 만큼 여러 번의 왕복으로 수행됩니다. CUBRID는 단일
LOB_READ 응답을 고정 크기로 제한하므로, read()는 반환된 바이트만큼 오프셋을
전진시키면서 length바이트를 모으거나 LOB의 끝에 도달할 때까지 내부적으로
반복합니다. read(0)은 서버 왕복 없이 b""를 반환합니다.
반환: 읽은 바이트.
Lob 속성¶
lob_handle¶
서버 통신에 사용되는 raw LOB 핸들 바이트.
핸들에는 LOB 크기가 들어 있으며, 핸들을 다시 보내면 서버는 이 크기를 그대로
믿습니다. write()가 성공하거나 일부만 성공할 때마다 크기 필드는
offset + bytes written까지 올라가고 내려가지는 않으며, 공식 CCI 드라이버와
같습니다(#441). 이 수정 전에는 쓴 뒤에도 핸들이 생성 시점의 크기(대개 0)를
유지했습니다.
lob_type¶
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 데이터를 삽입하려면 문자열/바이트를 직접 전달하세요:
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()¶
모든 카운터를 원자적으로 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¶
중요한 경고에 사용됩니다 (예: 삽입 중 데이터 절단).
Error¶
모든 pycubrid 오류의 기본 클래스.
InterfaceError¶
데이터베이스 인터페이스 관련 오류에 사용됩니다 — 닫힌 연결/커서에서 메서드 호출, 잘못된 인자 등.
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¶
데이터 처리 문제에 사용됩니다 (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¶
데이터베이스 연산 오류에 사용됩니다 (예기치 않은 연결 해제, 메모리 오류, 트랜잭션 실패, 연결 유실).
IntegrityError¶
관계형 무결성이 영향받을 때 사용됩니다 (외래 키 위반, 중복 키, 제약조건 위반). 참조하는 외래 키 때문에 거부된 DELETE, UPDATE, TRUNCATE도 포함합니다.
InternalError¶
내부 데이터베이스 오류에 사용됩니다 (잘못된 커서 상태, 동기화되지 않은 트랜잭션).
ProgrammingError¶
프로그래밍 오류에 사용됩니다 (SQL 구문 오류, 테이블 없음, 파라미터 수 불일치, 지원되지 않는 파라미터 타입).
NotSupportedError¶
지원되지 않는 메서드나 API가 호출될 때 사용됩니다.
UnknownConnectionOptionWarning¶
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()¶
네이티브 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])),
)
파라미터 바인딩을 참고하세요.