삽질하는플머

[티끌팁] MySQL 테이블 구조 엑셀로 출력

탐구생활/WEB 관련

블로그에 쌓인 먼지 털기용으로 올리는 티끌만한 팁. 


MySQL의 테이블 구조를 엑셀로 정리하라는 요청을 받음. 

스키마 생성 SQL만 던져주면 딱 좋겠지만, 세상 모든 사람이 개발자는 아니니... 


수백개의 테이블을 하나 하나 예쁘게 정리하는 작업은... 막내에게 시킨다고 해도 입이 한 뼘은 튀어나올 일... 

뭐 좀 편한방법이 없을까 구글신께 의탁하니 다음 신탁을 내려주신다. 


http://sourceforge.net/projects/exportmysqldbst/


오호~ 입맛 당기는데??


테이블을 만들어 테스트 해 보자. 

1 CREATE TABLE  `testdb`.`testtbl` (
2   `ID` INT NOT NULL COMMENT  '아이디',
3   `Name` VARCHAR( 30 ) NOT NULL COMMENT  '이름',
4   `Desc` TEXT NOT NULL COMMENT  '설명'
5 ) ENGINE = MYISAM COMMENT =  '테스트DB';


결과는 흐음... 




약간 아쉽지만 쓸만하다고 생각했는데...

깐깐한 요구자는 필드 설명도 달아달라네. 필드타입도 길이와 분리되었으면 좋겠다고 하고... 


해서 뚝딱뚝딱 수정한 물건. 


MySQL_DB_Structure_to_Excel.7z



SHOW COLUMNS 대신 INFORMATION_SCHEMA.COLUMNS 을 사용했고, 

UTF-8을 인식하지 못하는 엑셀을 위해 리틀엔디안 형식의 UCS-2로 출력한다. 


기존 dbDesingExport.php 대신 수정한 dbDesingExport2.php 를 사용해 출력한 결과는 다음과 같다. 






이제 막내에게 색깔 대충 입히고 줄 몇 개 그어주게 하면... 업무 종료~ ^^



php-xmlrpc 에서 시간함수 문제

탐구생활/WEB 관련

상황은 이렇다. 


델파이로 만든 XMLRPC 클라이언트에서 "시간값"을 인자로 PHP로 만든 XMLRPC 서버의 함수를 실행하고

이 값을 MySQL의 타임스탬프 필드에 저장하자. 


xml-rpc 의 시간표시는 iso 8601 에 따라 다음과 같이 표시된다.

  • 2011-08-24T00:00:00

xmlrpc.inc 에는 이 형식과 유닉스 타임스탬프 사이의 변환을 위해 iso8601_encode, iso8601_decode 함수가 정의되어있다. 
이렇게 변환한 유닉스 타임스탬프값을 MySQL에 집어넣기 위해 MySQL 내장함수인 FROM_UNIXTIME() 을 사용해 봤는데
클라에서 전송한 시간보다 +9 시간 더 나오는 문제가 발생. iso8681_decode의 두번째 인자에 1을 주어 UTC로 풀어도 마찬가지.

RPC 테스트중인 윈도의 PHP는 현재시간이 UTC로 설정되어있다. 그런데 시스템시간은 한국. 그래서 생기는 문제인 듯. 
실제로 다음과 같은 코드로 타임존을 설정하면 제대로 처리된다.


date_default_timezone_set("Asia/Seoul");


NAS, 또는 우분투의 PHP에서 echo date_default_timezone_get(); 을 찍어보면 Asia/Seoul 이 잘 출력되는 것을 보아 윈도용 WAMP의 PHP에서 시스템의 설정을 읽지 못하는 문제로 보인다. 그렇다고 코드에 저걸 무조건 박아넣기도 골룸.


date로 문자열 꾸미기도 시도해 봄. 


1 $tt = iso8601_decode($ExpireDate);
2 $ts = date("Y-m-d G:i:s", $tt);

하지만 iso8601_decode 내부에서 gmmkttime, 또는 mktime 을 실행시키면서 타임존 문제가 생김. 


뭐가 어쨌건 시스템의 날짜 설정과 무관하게 델파이에서 전송한 시간값을 MySQL에 입력하기만 하면 되므로...
iso8601_decode 의 앞부분을 흉내내 이렇게 만들어 봄. 

1 function iso8601_time_to_mysql($idate) {
2     if (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs)) {
3         return sprintf("%s-%s-%s %s:%s:%s", $regs[1], $regs[2], $regs[3], $regs[4], $regs[5], $regs[6]);
4     } else
5         return NULL;
6 }    


요는, php의 시간변환 함수를 사용하지 말자는 것. 
비슷한 요령으로 역변환도 만들어 둠. 

1 function mysql_time_to_iso8601($idate) {
2     if (preg_match('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs)) {
3         return sprintf("%s%s%sT%s:%s:%s", $regs[1], $regs[2], $regs[3], $regs[4], $regs[5], $regs[6]);
4     } else
5         return NULL;
6 }


이제 델파이에서 의도한 시간값이 PHP를 거쳐 MySQL로 제대로 꽂힌다. .



여담으로, date('Z') 로 얻어낸 값에는 현재 설정된 타임존과 UTC 사이의 분단위 시간차이가 들어있다.
GetTimeZoneInformation() 으로 얻어낸 TimeZhoeBias 값과 동일. 그냥 까먹을까봐 적어둠. 




Synology USBStation2 사용기 2. AMP 설치.

이런저런잡다구리/Synology-nas

아래 글을 먼저 읽고 오세요~ ^^;

"Synology USBStation2 사용기 1. 전원 꽂고 optware 깔기."



짤방은 재활용 쓰레기장에서 득템한 외장케이스. 앞부분 래치가 부러져 하드를 고정할 수 없어 버려진 듯. 

하우징 쇠 부분에 드릴로 구멍을 뚫고 클립을 펼쳐 관통시켰더니 잘 고정된다. e-sata, usb 지원. 

함께 입양된 1TB 하드를 넣어 USBStation2 에 물려줌. 



USBStation2의 포지션은 어디까지나 NAS가 아닌 NAS Adapter 라고 하지만, 성능면에 DS110j와 별반 다르지 않은데도 웹서비스가 빠졌다는 점은 아무리 생각해도 좀 깬다. 저렴한 버팔로 NAS도 아파치와 PHP, MySQL 정도는 기본으로 들어가 있는데 말이지... 그리고 들여다 보면 알겠지만 이미 아파치의 httpd 도 두 개나 돌고 있다. 기능이 충분한데도 넣지 않는 건 순전히 정책적인 결정이라고 생각한다. 


그러나 이미 우리 손에 쥐어진 ipkg 라는 전가의 보도!! AMP도 얼마든지 올려볼 수 있다. (버전이 좀 낮기는 해도...)


일단 관련 패키지들 설치. 


# ipkg install apache php mysql sqlite openldap-libs php-apache phpmyadmin 



우분투처럼 깔면 깔린 그대로 돌아가면 좋겠지만... 아쉽게도 몇가지 손을 봐줘야 한다. 

/opt/etc/apache2/httpd.conf 파일을 편집.


먼저 구동시 에러를 내는 74라인의 mod_ext_filter.so 모듈 로딩하는 부분은 앞에 '#'을 붙여 주석처리. 


# LoadModule ext_filter_module libexec/mod_ext_filter.so



USBStation2 에서 웹 기본 포트인 80번 포트는 이미 관리페이지인 5000번으로 리다이렉션 시키는 용도로 사용중이다. 

기본 내장된 아파치가 이 작업을 하는데 어디서 띄우는지는 아직 확인하지 못했다. (귀찮아서...) 어차피 가정용 ISP에서 80포트 막아둔 곳도 많고 필요하다면 공유기에서 포워딩하면 되니까 리슨포트는 8000 그대로 둔다. DS110j와의 가격차이를 생각하면 이 정도 쯤이야 패널티라고 생각하지 뭐.  


Listen 8000



서버명은 다음과 같이 수정. 


ServerName *:8000



LoadModule 어쩌구가 잔뜩 적혀있는 119라인 근처에 다음 두 줄을 추가해준다. 

AddType application/x-httpd-php .php
LoadModule php5_module libexec/libphp5.so


<IfModule dir_module> 부분을 찾아 다음과 같이 수정해준다. (235라인 근처...)

<IfModule dir_module>
    DirectoryIndex index.html index.htm index.cgi index.php index.php3
</IfModule>


아파치 구동. 


# /opt/sbin/apachectl start



http://usbstation2:8000 에 접속해보자. It works! 라는 반가운 메시지가 나오면 일단 성공! 




웹 서비스 루트 기본값은 /opt/share/www 이다. 여기에 다음 내용을 담은 test.php 파일을 만들어주자. 


# vi /opt/share/www/test.php


<?php

phpinfo();

?>



http://usbstation2:8000/test.php 에 접속해서 다음과 같은 화면이 나오면 최종 성공!! 





여기까지 잘 되었다면 조금 전에 설치한 phpmyadmin 도 제대로 동작할 것이다.

http://usbstation2:8000/phpmyadmin/ 에 접속 테스트. 





이제 DB에 적절한 권한을 설정하고 미디어위키를 깔건 그누보드를 깔건 마음대로 가지고 놀면 된다. 


PHP 에서 Int64 숫자값 문제

탐구생활/WEB 관련
PHP의 최대 정수값은 다음 코드로 살펴볼 수 있다. 

echo PHP_INT_MAX;



이 값은 32비트에서는 2,147,483,647. 64비트에서는 9,223,372,036,854,775,807 이다. 하지만 윈도에서는 32비트건 64비트건 관계없이 2,147,483,647 이다. (참고링크 : http://www.pubbs.net/200902/php/3781-re-php-dev-casting-doubles-to-ints.html)
 
내 경우 게임 캐릭터의 사이버머니 또는 현금의 필드값은 BIGINT, 즉 Int64 형을 쓰고 있다. 비교에서 사용될 경우야 단순히 문자열로 간주하면 상관없지만, 이렇게 '돈'에 관련된 경우는 상황이 조금 다르다. 직접적으로 값을 박아넣는 것이 아니라 기존값에 대해 증가시키거나 차감시키는 방식으로 처리하는 '연산'이 필요하기 때문이다. 따라서 PHP의 정수값 한계는 문제가 된다. 

게다가 PHP는 큰 정수의 경우 자동으로 실수값으로 변환해 처리하게 된다. 정수 한계를 벗어나는 큰 수를 변수에 넣고 출력해보자.

$k = 703687441776631235523;

echo $k;
echo sprintf('%f', $k);



출력결과는

7.0368744177663E+20
703687441776631218176.000000



이처럼 입력값과 전혀 관계없는 부정확한 값이 찍히게 된다.

일단 떠오르는 해법은 두가지. 스토어드 프로시저를 써서 MySQL에서 현금값을 차감하는 함수를 만들고 이것을 호출하는 방법. 
두번째는 BCMath(http://kr2.php.net/bc), 또는 GMP(http://www.php.net/manual/en/book.gmp.php) 를 사용하는 방법.

GMP는 잘 모르겠고, BCMath의 경우는 우분투, 윈도용 WAMP, NAS 세군데 모두 번들되어있다. 따라서 이 녀석을 쓰자.

$m = bcadd("703687441776631235523", "1", 0);
echo $m . '<BR>';


 
출력결과는 다음과 같다.

703687441776631235524


 
bcadd 의 마지막 인자는 결과값에서 사용할 소수점 자릿수. 정수만 쓰려면 0을 준다. 
또는 bcscale() 함수로 전역적으로 설정할 수도 있다.

bcscale(0);
$m = bcadd("703687441776631235523", "1");
echo $m . '<BR>';


 
BCMath 에는 이 외에도 빼기, 나누기, 곱하기, 승수 처리함수가 내장되어있다. 두 숫자가 같은지 비교하는 bccomp 도 유용하다. 
이로서 DB에 들어있는 BIGINT 현금값을 문자열로 간주, BCMath로 비교후 차감한 뒤 그 결과값을 돌려줄 수 있게 되었다. 

xmlrpc-epi 로 만든 RPC 서버에서 압축전송된 요청 다루기

탐구생활/WEB 관련
델파이와 PHP 에서 XLMRPC 를 가지고 노는 이야기는 오래전에 위키에 끄적인 적이 있는데... 지금은 날아가버렸다. 
백업된 문서 찾아다 블로그에 정리해야겠다. (... 고 생각한지 벌써 일년이 훌쩍... ㅠㅠ) 

xmlrpc-epi 는 예전에 살펴본 php-xmlrpc 와 기능면에서 거의 같으면서도 C로 만들어진 확장이라 속도가 좀 더 빠르다. 
사용법도 비슷해서 include("xmlrpc.inc"); 를 include("xmlrpc_emu.inc"); 라고 끝에 "_emu" 만 붙여주면 된다. 
무엇보다 대부분의 PHP 배포판에 기본적으로 포함되어있다는 것이 장점이다. 심지어는 시놀로지의 NAS에도 들어있다. 
게다가 나는 팔랑귀이므로 뭐가 조금이라도 좋다고 하면 써봐야 직성이 풀린다. 

 다만 무슨 일인지 우분투 10.04에서는 빠져있는데, 아래와 같이 설치하고 아파치를 재구동하면 올라온다.  (예전에는 들어 있었는데...)

sudo apt-get install php5-xmlrpc



WAMP 에서는 트레이를 클릭하고 PHP -> PHP Extensions -> php_xmlrpc 확장을 선택하면 된다. 





xmlrpc

core library version xmlrpc-epi v. 0.51
php extension version 0.51
author Dan Libby
homepage http://xmlrpc-epi.sourceforge.net
open sourced by Epinions.com



http://xmlrpc-epi.sourceforge.net/ 에서 xmlrpc-epi-php-0.51.tar.gz 를 다운받아 압축을 풀고 util 디렉토리를 include 경로에 포함시키면 사용준비는 완료. 


 

PHP+MySQL+xmlrpc-epi 로 만든 XMLRPC 기반의 데이터 서버와 델파이로 정보를 주고 받는 중인데, DB값의 특성상 키와 값으로 쌍을 이루는 "구조체" 형식을 선호하다 보니 전송 데이터가 상당히 커지게 되었고, 이거 필드에서 제대로 써먹을 수 있을지 슬쩍 겁이나기 시작했다. 

일단 받는 정보부터 압축해보자. PHP.INI 를 다음과 같이 설정하고
 

zlib.output_compression = on
zlib.output_compression_level = 9 



zlib
........

DirectiveLocal ValueMaster Value
zlib.output_compression On On
zlib.output_compression_level 9 9
zlib.output_handler no value no value



RPC 함수 호출시 HTTP 헤더의  Accept-encoding 에 "gzip,deflate" 라고 적어주면 gzip으로 압축된 정보가 날아온다. deflate로 날아온 정보는 ZLib로 바로 풀리고 gzip의 경우는 
DelphiZLib 의 ZLibExGZ 유니트를 사용하면 되므로 일단 한 숨 돌렸는데...
저장하기 위해 서버로 보낼 정보도 만만치 않게 크기 때문에 마찬가지로 압축을 해주기로 하면서 고민이 시작된다.



HTTP 헤더의 Content-Encoding 에 deflate, 또는 gzip 을 적어주고 XML을 압축한 뒤 서버를 호출하니 다음과 같은 에러가 나온다. 

32700: parse error. not well formed. 
error occurred at line 1, column 1, byte index 0



데이터가 요사스러워 첫줄 첫번째 컬럼 제일 앞바이트부터 에러가 난다고 툴툴거리고 있다.
저 에러는 C확장 내부에서 내는 것으로서 한마디로 압축된 그대로 넘겨받았다는 이야기. 


흠... 설정에 output_xxx 만 있을 때 부터 알아봤어야 하는데... 들어오는 정보는 직접 풀어줘야하나보다. 


서버 객체가 정의된 xmlrpcs_emu.inc 파일의 177 라인을 살펴보니 전달된 HTTP_RAW_POST_DATA 를 날것 그대로 던져주고 있다. 
여기에 클라에서 전달받은 Content-encoding 값으로 압축여부를 판단해 압축을 풀어주는 코드를 추가해보자. 

    // public. service the xmlrpc request
    function service() {
       Header("Content-type: text/xml; Content-length: " . strlen($payload));
 
       global $HTTP_RAW_POST_DATA;
       $data=$HTTP_RAW_POST_DATA;
 

      // 데이터가 압축되어있는지 살핀다. 압축되어있다면 풀자. 
         if(isset($_SERVER['HTTP_CONTENT_ENCODING']))
             $content_encoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
         else
             $content_encoding = '';
 
         if ($content_encoding == 'gzip' && function_exists('gzinflate') && $degzdata = @gzinflate(substr($data, 10))) {
             //echo 'GZip decompress!';
             $data = $degzdata;
         } else
         if ($content_encoding == 'deflate' && function_exists('gzuncompress') && $degzdata = @gzuncompress($data)) {
             //echo 'Deflate decompress!';
             $data = $degzdata;
         }



        // call server
       echo xmlrpc_server_call_method($this->xmlrpc_server, $data, $this->dmap, 
                                      array(output_type => "xml", version => "xmlrpc", encoding => "UTF-8"));
    }



녹색으로 둘러친 부분이 추가한 내용이다. 이제 함수호출 정보를 gzip, 또는 deflate 로 압축해 던져도 제대로 반응하게 된다.  

XML은 텍스트로 된 정보이므로 압축률이 높다. 크기가 커질수록 효율은 더욱 좋아진다.
3천바이트 가량의 XML이 1/5인 600바이트 정도로 줄어들어 왔다갔다 하는 것을 보면 답답했던 속이 뻥~ 뚫릴 것이다. 



ps. php-xmlrpc 에는 압축 전송된 요청을 풀어주는 코드가 이미 들어있다.
     따라서 이 고민은 "똑같은 PHP에서 XMLRPC 사용인데 xmlrpc-epi 는 왜 안되는거야!!" 하는 사람에게만 유효하다.