본문 바로가기
빌드노트 검색

빌드노트

PostgreSQL varchar(3): 직접 INSERT는 오류인데 캐스팅하면 잘린다

PostgreSQL의 varchar(3)은 직접 INSERT와 명시적 캐스팅에서 길이 초과 처리 방식이 다르다. 영문·한글·초과 공백·탭을 비교하고, 원본 char_length 검증과 text의 CHECK 제약으로 잘린 값의 저장 대신 입력 거부를 선택한다.

PostgreSQL의 varchar(3) 열로 가져오기가 성공했는데 네 글자인 원본이 세 글자만 남았다면, INSERT에 전달하는 식부터 확인한다. raw_value::varchar(3)이나 CAST(raw_value AS varchar(3))가 있으면 길이 초과를 검사하기 전에 값을 자를 수 있다. 초과 입력을 거부하려면 이 변환을 제거하고, 잘리기 전 원본의 char_length를 검사해야 한다.

PostgreSQL 18의 문자 타입 문서 (새 탭에서 열림)는 길이를 지정한 varchar로 명시 변환하면 초과분을 오류 없이 자른다고 설명한다. 같은 값을 열에 바로 넣는 경우와 동작이 다르다. 아래는 실제 업무 데이터가 아닌 짧은 합성 문자열로 PostgreSQL 18.6, UTF8 환경에서 비교한 결과다.

가져오기 쿼리에서 변환 전후를 비교한다

먼저 저장하지 않고 원본과 변환 결과를 나란히 조회한다. PostgreSQL에 연결된 psql이나 SQL 편집기에서 실행할 수 있으며 테이블은 필요 없다.

SELECT raw_value,
       char_length(raw_value) AS raw_length,
       raw_value::varchar(3) AS cast_value,
       char_length(raw_value::varchar(3)) AS cast_length
FROM (VALUES ('abcd'), ('가나다라')) AS incoming(raw_value);

두 행 모두 원본 길이는 4였지만, 변환 결과는 각각 abc, 가나다이고 길이는 3이었다. 여기서는 아직 INSERT조차 하지 않았다. 가져오기 쿼리에 적용할 때는 VALUES 부분을 실제 원본을 읽는 조회로 바꾸고, raw_value에는 길이 제한 변환을 거치기 전 식을 넣는다. 중간 뷰나 앞선 조회에서 이미 잘랐다면 더 앞의 입력까지 거슬러 올라가야 한다.

varchar(3)의 3은 바이트 수가 아니라 문자 수다. char_length 설명 (새 탭에서 열림)에 따라 문자 길이를 비교한다. 이번 실험에서도 완성형 한글 가나다 세 문자는 그대로 저장됐고, 가나다라 네 문자는 길이 초과 조건이었다. 이 결과를 이모지나 조합 문자의 화면상 글자 수 규칙까지 검증한 것으로 확대하지는 않는다.

같은 열에 넣어도 명시 변환 유무로 결과가 달라진다

임시 테이블을 만들 수 있는 실습 DB의 새 psql -X 대화형 연결에서 다음 문장을 순서대로 실행한다. 의도한 오류 뒤에도 다음 문장을 입력하는 실험이다. 저장점으로 실패 상태를 정리한 뒤 같은 열에 변환한 값을 넣어, 오류와 실제 저장값을 비교한다.

BEGIN;
CREATE TEMP TABLE varchar_probe (value varchar(3));
SAVEPOINT before_insert;

INSERT INTO pg_temp.varchar_probe VALUES ('abcd');
\echo :SQLSTATE
ROLLBACK TO SAVEPOINT before_insert;

INSERT INTO pg_temp.varchar_probe VALUES ('abcd'::varchar(3))
RETURNING value, char_length(value);
\echo :SQLSTATE
ROLLBACK;

직접 넣은 문장은 value too long for type character varying(3) 오류와 SQLSTATE 22001을 반환했다. 저장점으로 돌아간 뒤 명시 변환해서 넣은 문장은 성공 코드 00000, 저장값 abc, 길이 3을 반환했다. SQLSTATE는 psql에서 직전 SQL 문장의 결과 코드를 읽는 변수이므로, 다른 SQL을 실행하기 전에 확인한다. psql 변수 설명 (새 탭에서 열림), 오류 코드 목록 (새 탭에서 열림)

두 번째 문장이 성공한 이유는 열의 제한이 없어진 것이 아니다. 변환 결과인 세 문자만 열에 도착했기 때문이다. 합성 입력을 가나다라로 바꿔 실행해도 직접 입력은 22001, 명시 변환 후 입력은 가나다, 길이 3이었다.

오류 뒤 저장점 복구 없이 다음 SQL을 실행하면 실패한 트랜잭션 상태 때문에 비교가 흐려질 수 있다. 위의 ROLLBACK TO SAVEPOINT는 실패한 문장 뒤 작업을 되돌려 다음 문장을 실행할 수 있게 하고, 마지막 ROLLBACK은 이 연결에서 만든 실습 테이블까지 되돌린다. 저장점 복구 설명 (새 탭에서 열림)

초과 부분이 공백이면 직접 INSERT도 성공한다

명시 변환을 없애도 모든 초과 입력이 거부되는 것은 아니다. 문자 타입 문서에는 지정 길이를 넘는 부분이 모두 공백이면 직접 저장할 때도 초과분을 제거하는 예외가 있다. 같은 임시 테이블에서 입력만 바꾼 관찰은 다음과 같았다.

  • abc 뒤에 일반 공백 두 개를 붙인 'abc '는 원본 길이가 5였지만, 직접 입력과 명시 변환 후 입력 모두 성공하고 abc, 길이 3을 저장했다.
  • ab 뒤에 공백 두 개를 붙인 'ab '는 두 방식 모두 ab 뒤에 공백 하나를 남겨 길이 3을 저장했다. 뒤 공백을 전부 지우는 동작은 아니었다.
  • 탭을 붙인 E'abc\t'는 직접 입력에서 22001로 실패했고, 명시 변환 후에는 abc, 길이 3을 저장했다. 공백 예외를 모든 공백류 문자에 적용한다고 판단하면 안 된다.

따라서 “명시 변환만 없애면 초과 원본은 전부 오류가 난다”는 기준은 부족하다. 공백까지 포함한 원본 길이를 거부 조건으로 삼을지 먼저 정해야 한다.

거부할 길이는 원본에서 검사한다

가져오기 과정에서는 잘리기 전 값을 길이 제한 없는 text로 유지하고, char_length(raw_value) > 3인 행을 확인해 입력 오류로 처리한다. 이 검사에 raw_value::varchar(3)를 넣으면 이미 잘린 길이만 보게 된다. 정상 행만 WHERE로 통과시키는 방식도 초과 행을 조용히 누락할 수 있으므로, 초과 행이 있으면 가져오기를 중단하거나 해당 행의 거부 사실을 명시적으로 남긴다.

DB에서 공백을 포함해 세 문자를 넘는 입력을 거부하는 규칙을 시험하려면 text와 CHECK를 함께 사용할 수 있다. 다음은 기존 테이블 변경 없이 그 차이를 확인하는 별도 임시 테이블이다. 앞 실험과 같은 psql 연결에서 실행하며, 길이 초과 오류 뒤에는 저장점으로 돌아간다.

BEGIN;
CREATE TEMP TABLE strict_probe (
    value text NOT NULL
        CONSTRAINT strict_probe_length CHECK (char_length(value) <= 3)
);
INSERT INTO pg_temp.strict_probe VALUES ('abc')
RETURNING value, char_length(value);

SAVEPOINT before_insert;
INSERT INTO pg_temp.strict_probe VALUES ('abc  ');
\echo :SQLSTATE
ROLLBACK TO SAVEPOINT before_insert;
ROLLBACK;

abc는 그대로 저장됐고, 공백 두 개가 더 붙은 입력은 SQLSTATE 23514로 거부됐다. 별도로 같은 제약에 원본 abcd, 가나다라, 탭이 붙은 문자열을 넣은 경우도 모두 23514였다. CHECK는 저장될 값에 조건을 적용하며, 이 예제의 NOT NULL은 누락값도 거부하려는 별도 규칙이다. CHECK와 NULL 처리 (새 탭에서 열림)

다만 이 테이블도 'abcd'::varchar(3)를 전달하면 abc를 받아 성공했다. 제약은 잘리기 전 문자열을 알 수 없다. 먼저 가져오기 SQL에서 절단하는 변환을 제거하고 원본 길이를 검증해야 한다. 이미 잘려 저장된 값만으로는 사라진 뒷부분을 복원할 수 없으므로 원본 파일이나 별도로 보존한 입력과 대조한다.

직접 확인한 범위는 PostgreSQL 18.6의 SQL 문자열, 임시 테이블, 명시 변환과 원본 길이 제약이다. 실제 가져오기 드라이버, ORM의 매개변수 처리, 운영 스키마 변경은 검증하지 않았다. SQL에서는 원본이 보존되는데 실제 가져오기에서만 잘린다면, 이 실험 결과만으로 원인을 확정하지 말고 드라이버에 전달하기 전 값과 서버에 도착한 값을 비교한다.

← 전체 글 보기