manywaypark's Blog
개발, 검색, 함수

synopsis - the bug
mysql 데이터를 oracle로 옮기는 아주 단순한 모듈을 clsql을 사용해서 만들었다.
처음에는 oracle 서버 설정이 잘못되어 oci를 통해 oracle에 접근하는 것이 불가능했다.
ORA-12705: Cannot access NLS data files or invalid environment specified.
서버 설정은 다음과 같이 하면 별 무리가 없을 듯했다.
# in .bash_profile of user oracle
export NLS_LANG=(KOREAN_KOREA|AMERICAN_AMERICA).UTF8
export ORA_NLS33=$ORACLE_HOME/ocommon/nls/admin/data      ## 9i  이하 버전
export ORA_NLS10=$ORACLE_HOME/nls/data                    ## 10g 이상 버전

클라이언트 쪽은 utf-8 환경의 우분투이므로,
export NLS_LANG=.UTF8

서버/클라이언트 설정을 맞추고 나자 다음과 같은 서버 에러를 내면서 레코드 삽입이 멈추는 심각한 상태가 되어 버렸다. 운좋게 들어간 몇몇 레코드들은 한글이 모조리 깨져있었다.
ORA-01756 quoted string not properly terminated
처음에는 인코딩 문제인것같아 환경변수 설정을 바꿔가면서 해봤다. 테스트용 모듈이 가끔 정상적인 한글을 insert할 때가 있었는데, 그 경우는 lisp 소스에 한글이 static하게 박혀있고 그 모듈을 (load "test.lisp")의 형태로 읽어들일 경우에만 한글이 제대로 오라클 DB로 전송되었다(지금도 이 부분이 정확히 어떻게 해서 성공적으로 그렇게 되었는지는 잘 이해가 안가는 상태다. lisp reader의 농간으로 추측되긴한다). 즉, 같은 소스를 emacs에서 읽어들여 function을 eval하면 깨진 한글이 전송되는... 신기하긴 하지만 돌아버릴 것같은 상황이 연출된 것이다. Orz...

이것이 눈물겨운 hacking의 시작이었다.

problem solving - the patch
clsql-oracle 및 uffi의 소스를 뒤졌다. oracle-sql.lisp에서 일단 실마리를 찾았다. sql-stmt-exec에서 oci interfact를 통해 sql statement를 실행하는데, uffi를 사용해 foreign string으로 변환 후에 oci interface를 통해 서버측으로 전달되게 되어있었다.
인코딩의 문제라는 강력한 심증이 있었으므로, lisp string 에서 변환된 foreign string을 lisp string으로 다시 변환(convert-from-foreign-string)해서 로그를 찍어보았다. 로그는 출력되지 않고 encoding error가 났다.
debugger invoked on a SB-INT:C-STRING-DECODING-ERROR in thread
#<THREAD "initial thread" {10025DEED1}>:
c-string decoding error (:external-format :UTF-8):
the octet sequence 3 cannot be decoded.

문제의 근원 (root cause)이 파악되는 순간이었다 (반이나 왔다고 긍정적으로 생각할 수도, 반이나 남았다고 비관적으로 생각할 수도 있는... go냐 stop이냐를 정말 갈등하게 하는 순간이다). '그냥 bug report하고 기다릴까? 테스트 케이스까지만 만들어 주고 report and wait??' 이런 유혹에 잠시 빠질려는 순간 어디선지 모르게 승부욕이 용솟음쳤다. 내나라의 아름다운 글을 바르게 표시하는 것은 내손으로 하자. 막판에는 사명감까지 생겼다. --;;

이제, 지금 이순간 필요한 것은 확실한 test case의 작성!!
(defparameter *tests* '("english" "한글"))
(dolist (test *tests*)
  (let* ((f    (uffi:convert-to-foreign-string test))
         (back (uffi:convert-from-foreign-string f)))
    (format t "~%test=~a, back=~a" test back)
    (assert (string= test back))))

역시 한글에서만 error가 났다. 용의자는 convert-to-foreign-string 아니면 convert-from-foreign-string 으로 압축되었다.

테스트 코드를 좀 끼워넣어보고, 함수를 재정의(lisp이 정말 강력한 부분)해보고 해서 찾은 진범은 바로,
uffi:convert-to-foreign-string

unicode에 대한 고려가 안되어있었다.

sbcl의 unicode관련 코드를 좀 살펴보고, uffi쪽 코드도 좀 살펴보고 해서 uffi:convert-to-foreign-string를 수정해서 상기 테스트 코드가 통과되게 만들었다.
이젠 DB쪽 삽입을 테스트할 차례. 자신감으로 충만해서 잠시 잊었던 나의 원래 목적인 (제일 처음 작성했던!!) mysql2oracle migration 모듈을 실행했다.
그런데 또 위에서 나왔던 ORA-01756에러가 났다. Orz...

이상하게 이번에는 전혀 짜증나지도 않았고, 해결할 수 없을지도 모른다는 생각도 들지 않았다. 이미 uffi와 clsql-oracle의 내부를 어느정도 파악하고 있었기 때문에 할 수 있다는 생각이 들었다.
잠시 살펴본 결과 sql-stmt-exec에서 oci interface로 전달하는 파라미터 중에 sql statememt의 길이를 전달하는 부분이 있었는데 여기에 원래 lisp string의 길이를 전달하고 있었다 (ascii string은 utf-8로 인코딩해도 길이가 같기 때문에 문제가 안되지만 한글등의 non-ascii는 당연히 짤려버려서 에러가 난다).
그래서 간단히 uffi:foreign-string-length를 불러서 해결하려했으나, 이 함수는 아무도 안쓰는지 완전히 잘못되어 있었다. macro의 body를 defun으로 정의해놓았고, 그나마 그것도 문법오류를 포함하고 있었고, unicode 고려도 안되어있었다. 악의 축같은 함수(매크로) 였지만, 이미 익숙해져 있는 모듈이었으므로 간단히 고칠 수 있었다.

이제 남은 것은 기존 테스트 케이스 돌려보기 및 upstream 반영을 위해 maintainer에게 patch 전달하기.



happy hackin'
1 
분류 전체보기 (306)
잡담 (20)
함수형 언어 (65)
emacs (16)
java (18)
tips & tricks (154)
사랑 (1)
가사 (0)
독서 (4)
mobile (6)
비함수형 언어 (2)

공지사항

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

03-28 15:58