redhat 7.2 + oracle 9i 설치하기


참고(여기저기 있는 문서들 합쳐서 설치한번 해봤슴돠)
otn.oracle.co.kr/포럼란(질답)
http://free-zg.hinet.hr/denisk/oracle/901install_en.html


테스트 시스템사양

펜3 600
램 390
커널 2.4.13
스왑 2G
오라클 설치공간 8G
(오라클사에서는 메모리 512, 스왑 1.5 G,수세7.2, gligc 2.2, jre1.3.1 기타등등..이 필요 하다더군요.)

필요한 패키지

j2re-1_3_1_01-linux-i386.bin
binutils-2.10.91.0.4-1.i386.rpm


1. 커널컴파일시 커널파라미터 수정


/usr/src/linux/include/asm/shmparam.h 이라는 파일을 열어서

#define SHMMAX 4294967295 ## 공유메모리 세크먼트의 최대크기(바이트 단위로서 약 4GB)
#define SHMMIN 1 ## 한개의 공유 메모리 세그먼트 최소 크기
#define SHMMNI 100 ## 시스템 내 공유메로리 세그먼트의 최대 개수
#define SHMSEG 10 ## 사용자 프로세스하나가 사용할수 있는 공유메모리 세그먼트의 최대 개수

/usr/src/linux/include/linux/sem.h 에는
#define SEMMNI 128
#define SEMMSL 250 ## 한개의 세마포어세트에 존재할수 있는 세마포어의 최대개수
#define SEMMNS (SEMMNI*SEMMSL)
#define SEMOPM 100 ## Semop call 당 operations 의 최대 개수
#define SEMVMX 32767 ## 세마포어의 최대값을 결정

/usr/src/linux/include/linux/msg.h 에는
#define MSGMNI 128
#define MSGMAX 8192
#define MSGMNB 81920

이렇게 써주고 커널컴파일을 한다.

2. jdk 설치, binutil 설치


jdk 설치

http://java.sun.com/j2se/1.3/download-linux.html 에서 jdk를 다운.

#cd /usr/local
#./j2re-1_3_1_01-linux-i386.bin
#ln -s jdk1.3.1_01 jdk
#ln -s jdk1.3.1_01 java

binutil 설치

Oracle9i Database를 설치하는 과정에서 relink 관련하여
에러가 발생합니다. 이에 대한 해결방법이니 참고 바랍니다.
==================================================================
O/S: Redhat 7.1 (이하 RH7.1이라 함)
원인: RH7.1에 포함된 ld 프로그램의 문제로 야기됨.
해결: binutil-2.10.91.0.2-2 패키지를 v2.10.91.0.4-1으로 업그레이드함.

binutil-2.10.91.0.4-1은 http://www.kernel.org에서 다운
http://www.kernel.org/pub/linux/devel/binutils/ 에 있음.
===================================================================
#rpm -Uvh binutils-2.10.91.0.4-1.i386.rpm

3. oracle 압축해제

http://otn.oracle.co.kr 에서 리눅스용 9i를 다운받으세용.

# gzip -d Linux9i_Disk1.cpio.gz
# gzip -d Linux9i_Disk2.cpio.gz
# gzip -d Linux9i_Disk3.cpio.gz

# cpio -idmv < Linux9i_Disk1.cpio
# cpio -idmv < Linux9i_Disk2.cpio
# cpio -idmv < Linux9i_Disk3.cpio

4. oracle 홈디렉토리 생성

# groupadd dba
# useradd -g dba oracle
# passwd oracle *******

# mkdir -p /home/oracle/product/9.0.1
# chown -R oracle.oinstall /home/oracle/*

/home/oracle/.bash_profile 에 다음내용 추가

export ORACLE_HOME=/home/oracle/product/9.0.1
export ORACLE_BASE=/home/oracle
export NLS_LANG=American_Amerca.KO16KSC5601 #한글설정
export ORACLE_SID=ORCL
export ORA_NLS33=$ORACLE_HOME/ocommon/nls/admin/data
export ORACLE_TERM=xterm
export NLS_SORT=xcroatian
export PATH=$PATH:$ORACLE_HOME/bin:/usr/local/java/bin
export TNS_ADMIN=$ORACLE_HOME/config

if [ -z $LD_LIBRARY_PATH ]
then
export LD_LIBRARY_PATH=$ORACLE_HOME/lib
else
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib
fi
if [ -z $CLASSPATH ]
then
CLASSPATH=$ORACLE_HOME/JRE:$ORACLE_HOME/jlib:$ORACLE_HOME/rdbms/jlib
CLASSPATH=$CLASSPATH:$ORACLE_HOME/network/jlib
export CLASSPATH
else
CLASSPATH=$CLASSPATH:$ORACLE_HOME/JRE:$ORACLE_HOME/jlib
CLASSPATH=$CLASSPATH:$ORACLE_HOME/rdbms/jlib:$ORACLE_HOME/network/jlib
export CLASSPATH
fi


5. 메모리 부족을 위한 해결책(스왑추가)

오라클 9i 를 설치하기 위한 사양이 상당히 높더군요. 기본 512 램에 스왑 1.5 G 이렇게 안되시는 분은 다음과 같이 스왑을 좀 잡아주시는게 좋겠죠?

# dd if=/dev/zero of=tempswap bs=1k count=300000
# chmod 600 tempswap
# mke2fs tempswap
# mkswap tempswap
# swapon tempswap

이렇게 하고 free 를 해보시면 스왑이 추가된걸 볼수 있습니다.

6. oracle 설치

oracle 유저로 로그인하고 X를 구동시킨후 /Disk1/runInstaller 를 실행하시면 됩니다.

참고1

화면보호기는 꺼두시고 하심이...그냥한번 했다가. 시스템이 꼬진(?)시스템이다 보니..
화면보호기가 뜨면서 화면이 하얗게 변한다음...돌아 오질 않았슴..구래서 다시 설치했음..쿠쿠쿠...^^;

참고2

설치시디가 3장 이잖습니까? 이거 첫번째 시디를 넣고 두번째 시디를 넣어 달라기는 하는데...
인스톨러가 실행된 상태라 언마운트가 안되더군요...쿠쿠...강제 언마운트를 할려다가...기양...^^;
그래서 하드에 모조리 카피해놓고 설치를 시작했습니다. 그랬더니..모..묻지도 않고 알아서 하더군요..

참고3

하드는 넉넉하게 잡으시는게 조을거 같습니다. 거의 모 4GB 가 조금안되게 깔리더군요....
넉넉한 하드에서 하시길......

참고4

커널컴파일 한다음 binutils 를 설치하시기 바랍니다. 7.1 에선 어떤지 모르겠는데
7.2에서 binutils 을 다운그레이드 한다음 커널을 컴파일 하려니깐....에러가 나더군요...
커널을 꼭 먼저 컴파일 하시고 binutils를 설치하시길..아니면 저같인 몇번을 다시 설치해야하는..수가....쿠쿠

'Oracle' 카테고리의 다른 글

SQL문 기본  (0) 2007.01.21
Pro*C에서 변수의 사용  (0) 2007.01.21
proc 파헤치기  (0) 2007.01.21
LINUX + ORACLE 10g 10.1 Install  (0) 2006.06.11
Installing Oracle9iR2(9.2.0.4) on RH AS4  (0) 2006.06.11
, .

PostgreSQL 설치

PostgreSQL 2006. 6. 11. 01:29

SQL개론 - PostgreSQL 설치

요약: 이 짧은 코스는 세 부분으로 이루어진다. 첫번째 부분에서는 PostgreSQL이라는 공개 데이터베이스 서버를 이용하여 SQL에 대한 일반적인 내용을 설명한다. 두번째 부분에서는 SQL 명령어에 대해 더욱 자세하게 공부해 볼 것이다. 마지막으로 세번째 부분에서는 SQL 명령어의 고급 옵션들과 우리 프로젝트에 관련될만한 PostgreSQL만의 함수들을 알아보고 마지막으로 모든 것들을 하나로 모아 조그만 C 프로그램을 살펴볼 것이다.




들어가는 글

개론

이 간략한 개론에서 우리는 데이터베이스(DB)에 대해서만 언급하겠다. 다른 형태의 자료구조도 있지만 그것들에 관한 것은 이 코스의 범위를 벗어날 것이다.

최근까지 데이터 아이템에 액세스는 데이터베이스 계통의 잘 정의된 링크를 통해 내부적으로 연관된 개체들을 통해 이루어졌었다. 이러한 종류의 액세스는 주로 액세스 속도면에서 장점을 지니고 있었지만 커다란 단점이 있다. 예를 들면 다음과 같이 현재 존재하는 링크를 통해서만 데이터를 액세스할 수 있고,

country -> states -> counties아래와 같이는 할 수가 없다. country -> counties"->"는 링크를 의미한다.

위의 두번째와 같은 관계를 만들려면 계통을 다시 정의하고 다시 컴파일해야 할 것이다.

실제로, 계층적 DB에서는 다양한 개체들 사이의 관계는 정적이며 DB 계통을 변경하고 그것을 다시 컴파일해야만 수정될 수 있다.

관계형 데이터베이스의 배후에 있는 기본적인 아이디어는 정적인 링크를 필요로 하지 않고 대신에 하나의 레지스터를 다른 것과 연결할 수 있도록 구분자를 이용하여 정확하게 질의 순간에 데이터를 연결시키는 것이다.

위의 글을 읽으려면 아스피린이라도 먹어야겠군요. :)

관계형 데이터베이스 매니저는 개체들의 계층을 따라 내려가기 위해 정적인 링크를 필요로 하지 않는다. 대신에 질의의 결과로서 일시적인 관계를 만드는 동안에 이러한 개체를 인지하게 되는 단일 코드를 사용한다.

그러한 인지는 코드일 뿐이다. 예를 들면 내 전화번호는

1234567

이 아니라

34 6 1234567

이다.

명확하게 내 전화번호는 country code(34), state code(6) 그리고 적절한 device number (1234567)로 구분된다.

  • country의 집합에서 코드 34(스페인)은 유일하다.
  • state의 집합에서 코드 34-6 (스페인/발렌치아)는 유일하다.
  • device의 집합에서 코드 34-6-1234567(스페인/발렌치아/내 전화번호)는 유일하다.

방금 언급한 것을 묘사하는 첫번째 기본내용을 정립해보자.

모든 county들은 state와 country에 속하는 코드를 지니고 있다.
모든 state들은 country에 속하는 코드를 지니고 있다.
모든 country들은 코드를 지니고 있다.

state안의 모든 county를 찾기 위해 country 코드와 county코드를 통해 county를 state와 연관시킨다. country안의 모든 county를 찾기 위해 country 코드에 의해 county를 country와 연관시킨다. 이러한 관계들은 일시적이며 질의되는 동안에만 존재한다.

조금 무미건조하고 이해하기 힘들겠지만 처음의 몇 예제들로 코드와 소속에 대한 개념이 명확해졌기를 바란다.

DB 매니저에 첫번째 질의를 보내면 DB 매니저는 모든 관계된 데이터 아이템들을 리턴한다. 하지만 어떤 데이터를 내가 받게 될까? 그것은 country와 county 아이템의 결합이며 모든 county에 대해 연관된 country 아이템을 받게 될 것이다.

첫번째 질의에서 이름이 없는 새로운 개체가 갑자기 생성되며 그것은 country와 county의 복제를 포함한다. 이러한 새로운 개체는 다시 질의가 끝나고 나면 사라지게 된다.

전에 우리는 데이터의 집합을 "파일"이라고 불렀다. 파일은 레지스터로 이루어져 있으며 각각의 레지스터는 "필드"를 가지고 있다. 관계형 데이터베이스에서는 "파일"은 테이블이며 테이블은 행(row)를 가지게 되고, 각각의 행에는 column이 있다. 이것은 단지 약간의 표면적인 변화일 뿐이다. ;-)

어떠한 DB 매니저들은 액세스 언어로서 SQL을 사용한다는 점을 말하고 싶다. 하지만 이것은 특수한 경우일 뿐이다. SQL언어는 거의 관계형 데이터베이스 매니저가 독점적으로 가지고 있는 특질 중의 하나이다.

SQL의 사용법을 알아보기 위해 우리는 관계형 데이터베이스 매니저인 PostgreSQL을 사용할 것이다. SQL 규칙과 완전히 부합되지는 않지만 우리의 목적에는 충분히 쓸 수 있고, 더욱 고난이도의 작업에도 알맞는 매우 좋은 데이터베이스 매니저이다.

이 글의 목적은 SQL임을 감안하여 설치 과정은 간략하게만 설명해 보겠다. 우선 www.postgresql.org로부터 필요한 소스를 다운로드 받도록 하자. 물론 사용 가능한 패치들도 다운로드 받는다. 소스들을 임의의 디렉토리에 압축을 풀고(tar zxvf), cd postgresql-6.3

cd src
./configure --prefix=/the/desired/path
make all >& make.log &
tail -f make.log

export PATH=$PATH:/the/desired/path/pgsql/bin
export MANPATH=$MANPATH:/the/desired/path/pgsql/man
export PGLIB=/the/desired/path/pgsql/lib
export PGDATA=/the/desired/path/pgsql/data

initdb
createdb test
psql test
Welcome to the POSTGRESQL interactive sql monitor:
Please read the file COPYRIGHT for copyright terms of POSTGRESQL

type \? for help on slash commands
type \q to quit
type \g or terminate with semicolon to execute query
You are currently connected to the database: postgres

test=>이것이 postgres의 프롬프트이다. 이제 명령어를 실행시킬 수 있다. mytest=>create table mytest (field1 varchar(10));
CREATE

mytest=>insert into mytest values ('hello');
INSERT number 1

mytest=>commit work;
NOTICE:EndTransactionBlock and not inprogress/abort state
END

mytest=>select * from mytest;
field1
------
hello
(1 row)

mytest=>drop table mytest;
DROP

mytest=>Ctrl-d이렇게 하면 SQL 콘솔에서 빠져나간다.

Postgres95의 컴파일과 설치에서 문제가 생긴다면 배포판의 루트디렉토리에 있는 INSTALL 파일을 참조하기 바란다.

또 다른 면에 대해 언급해보자. 관계형 데이터베이스 서버는 일반적으로 다음과 같은 부분들로 이루어진다.

  1. Data access layer
  2. SQL processing layer
  3. SQL parser layer
  4. Communications layer
우리는 클라이언트로 4번째 레이어에 연결되며, SQL 명령어를 보내면 parser layer로 전달된다. 이것이 명령어를 번역하고 에러가 없는 경우에 요구사항을 두번째 레이어로 보낸다. 모든 질의와 처리는 data access layer의 도움을 받아서 두번째 레이어에서 이루어진다. communications layer를 통해서 데이터의 수집, 에러의 전송 등이 이루어진다. SQL processing layer는 정확한 데이터의 전송과 업무처리, 인터럽트의 제어를 관리하면서 클라이언트와의 대화를 구축한다.

첫번째 단계

다음으로는 지금까지 기술된 내용을 예제와 함께 설명하겠다. 세 개의 테이블(또는 파일)을 만들어보자.

File: countries.sql
create table countries (cod_country integer, name varchar(30));
insert into countries values (1, 'country 1');
insert into countries values (2, 'country 2');
insert into countries values (3, 'country 3');
commit work;File: states.sql
create table states (cod_State int,
cod_country int,
nam_State varchar(30));
insert into states values (1, 1, 'State 1, Country 1');
insert into states values (2, 1, 'State 2, Country 1');
insert into states values (1, 2, 'State 1, Country 2');
insert into states values (2, 2, 'State 2, Country 2');
insert into states values (1, 3, 'State 1, Country 3');
insert into states values (2, 3, 'State 2, Country 3');
commit work;File: counties.sql
create table counties (cod_country int,
cod_state int,
cod_county int,
nam_county varchar(60));
insert into counties values (1, 1, 1, 'County 1, State 1, Country 1');
insert into counties values (2, 1, 1, 'County 2, State 1, Country 1');
insert into counties values (3, 1, 1, 'County 3, State 1, Country 1');
insert into counties values (1, 2, 1, 'County 1, State 2, Country 1');
insert into counties values (2, 2, 1, 'County 2, State 2, Country 1');
insert into counties values (3, 2, 1, 'County 3, State 2, Country 1');
insert into counties values (1, 3, 1, 'County 1, State 3, Country 1');
insert into counties values (2, 3, 1, 'County 2, State 3, Country 1');
insert into counties values (3, 3, 1, 'County 3, State 3, Country 1');
insert into counties values (1, 1, 2, 'County 1, State 1, Country 2');
insert into counties values (2, 1, 2, 'County 2, State 1, Country 2');
insert into counties values (3, 1, 2, 'County 3, State 1, Country 2');
insert into counties values (1, 2, 2, 'County 1, State 2, Country 2');
insert into counties values (2, 2, 2, 'County 2, State 2, Country 2');
insert into counties values (3, 2, 2, 'County 3, State 2, Country 2');
insert into counties values (1, 3, 2, 'County 1, State 3, Country 2');
insert into counties values (2, 3, 2, 'County 2, State 3, Country 2');
insert into counties values (3, 3, 2, 'County 3, State 3, Country 2');
insert into counties values (1, 1, 3, 'County 1, State 1, Country 3');
insert into counties values (2, 1, 3, 'County 2, State 1, Country 3');
insert into counties values (3, 1, 3, 'County 3, State 1, Country 3');
insert into counties values (1, 2, 3, 'County 1, State 2, Country 3');
insert into counties values (2, 2, 3, 'County 2, State 2, Country 3');
insert into counties values (3, 2, 3, 'County 3, State 2, Country 3');
insert into counties values (1, 3, 3, 'County 1, State 3, Country 3');
insert into counties values (2, 3, 3, 'County 2, State 3, Country 3');
insert into counties values (3, 3, 3, 'County 3, State 3, Country 3');
commit work;

SQL 명령어로 만들어진 파일은 pgsql에서 다음과 같은 방법으로 실행될 수 있다.

\i file_name

물론 간단하게 cut & paste기능을 이용하여 명령어를 집어넣을 수도 있다.

이제 어떤 county들이 있는지를 알아보자.

manu=> select * from counties;
cod_country|cod_state|cod_county|nam_county
-----------+---------+----------+----------------------------
1| 1| 1|County 1, State 1, Country 1
2| 1| 1|County 2, State 1, Country 1
3| 1| 1|County 3, State 1, Country 1
1| 2| 1|County 1, State 2, Country 1
2| 2| 1|County 2, State 2, Country 1
3| 2| 1|County 3, State 2, Country 1
1| 3| 1|County 1, State 3, Country 1
2| 3| 1|County 2, State 3, Country 1
3| 3| 1|County 3, State 3, Country 1
1| 1| 2|County 1, State 1, Country 2
2| 1| 2|County 2, State 1, Country 2
3| 1| 2|County 3, State 1, Country 2
1| 2| 2|County 1, State 2, Country 2
2| 2| 2|County 2, State 2, Country 2
3| 2| 2|County 3, State 2, Country 2
1| 3| 2|County 1, State 3, Country 2
2| 3| 2|County 2, State 3, Country 2
3| 3| 2|County 3, State 3, Country 2
1| 1| 3|County 1, State 1, Country 3
2| 1| 3|County 2, State 1, Country 3
3| 1| 3|County 3, State 1, Country 3
1| 2| 3|County 1, State 2, Country 3
2| 2| 3|County 2, State 2, Country 3
3| 2| 3|County 3, State 2, Country 3
1| 3| 3|County 1, State 3, Country 3
2| 3| 3|County 2, State 3, Country 3
3| 3| 3|County 3, State 3, Country 3
(27 rows)
manu=>27개의 행이 출력되었으며 pgsql은 다음 명령어를 기다리게 된다.
다음과 같은 명령을 내려보자. manu=> select * from countries, states;
cod_country|name |cod_state|cod_country|nam_state
-----------+---------+---------+-----------+------------------
1|country 1| 1| 1|State 1, Country 1
2|country 2| 1| 1|State 1, Country 1
3|country 3| 1| 1|State 1, Country 1
1|country 1| 2| 1|State 2, Country 1
2|country 2| 2| 1|State 2, Country 1
3|country 3| 2| 1|State 2, Country 1
1|country 1| 1| 2|State 1, Country 2
2|country 2| 1| 2|State 1, Country 2
3|country 3| 1| 2|State 1, Country 2
1|country 1| 2| 2|State 2, Country 2
2|country 2| 2| 2|State 2, Country 2
3|country 3| 2| 2|State 2, Country 2
1|country 1| 1| 3|State 1, Country 3
2|country 2| 1| 3|State 1, Country 3
3|country 3| 1| 3|State 1, Country 3
1|country 1| 2| 3|State 2, Country 3
2|country 2| 2| 3|State 2, Country 3
3|country 3| 2| 3|State 2, Country 3
(18 rows)18행 ??? 우리는 3개의 country와 6개의 state를 입력했고, 모두 한 개의 country를 의미한다. 어떻게 18개의 행이 나올 수 있을까?

마지막 명령어는 두 테이블 사이의 유니온을 수행했다. 우리는 country의 테이블과 county의 테이블을 연관시켰다. union exclusion rule을 전혀 지정해주지 않았기 때문에 pgsql은 모든 state와 관련된 가능한 country를 모두 (예를 들면 country의 3 곱하기 state의 6을 하면 18개가 된다) 돌려준다. 이러한 결과는 명백하게 비논리적이고 쓸모없는 것이다. 다음과 같이 해 주었다면 더 좋았을 것이다.

manu=> select * from countries, states
manu-> where countries.cod_country = states.cod_country;
cod_country|name |cod_state|cod_country|nam_state
-----------+---------+---------+-----------+------------------
1|country 1| 1| 1|State 1, Country 1
1|country 1| 2| 1|State 2, Country 1
2|country 2| 1| 2|State 1, Country 2
2|country 2| 2| 2|State 2, Country 2
3|country 3| 1| 3|State 1, Country 3
3|country 3| 2| 3|State 2, Country 3
(6 rows)이제 조금 나아 보인다. 6개의 행, 맞지 않은가?

그렇다. 6개의 county가 있으며 모든 county들은 country 안에 있다. country가 county의 자격을 주므로 county의 개수와 똑같은 수의 행을 결과로 받는 것이 합당하다. 우리는 방금 country의 테이블을 country 코드를 통해 county의 테이블과 연관시켰다. country는 코드를 지니며, county들은 그들이 속해 있는 나라의 코드를 지닌다는 것을 기억하자.

countries.cod_country = states.cod_country 인가 ?

country의 테이블 안의 country 코드는 cod_country이며 county의 테이블에서도 마찬가지이다. 그러므로

cod_country = cod_country는 비논리적이다. 인터프리터는 둘 중에 어느 쪽을 사용할지 결정하지 못하고 에러를 리턴할 것이다. select * from countries, states
where cod_country = cod_country;

ERROR: Column cod_country is ambiguous 다음으로, 우리는 컬럼들에 대해서 aliasis를 사용할 수 있다. manu=> select * from countries a, states b
manu-> where a.cod_country = b.cod_country;
cod_country|name |cod_state|cod_country|nam_state
-----------+---------+---------+-----------+------------------
1|country 1| 1| 1|State 1, Country 1
1|country 1| 2| 1|State 2, Country 1
2|country 2| 1| 2|State 1, Country 2
2|country 2| 2| 2|State 2, Country 2
3|country 3| 1| 3|State 1, Country 3
3|country 3| 2| 3|State 2, Country 3
(6 rows)매니저가 리턴하는 값들은?: cod_country, name, cod_state, cod_country y nam_state.

"select * from countries, states"라는 질의를 했는데 와일드카드 *가 모든 것을 의미하므로 우리는 countries에 해당하는 두 개의 컬럼과 counties에 해당하는 세 개의 컬럼을 얻게 된다. 조금 더 구 체적으로 들어가보자.

manu=> select a.cod_country, cod_state, name, nam_state
manu-> from countries a, states b
manu-> where a.cod_country = b.cod_country;
cod_country|cod_state|name |nam_state
-----------+---------+---------+------------------
1| 1|country 1|State 1, Country 1
1| 2|country 1|State 2, Country 1
2| 1|country 2|State 1, Country 2
2| 2|country 2|State 2, Country 2
3| 1|country 3|State 1, Country 3
3| 2|country 3|State 2, Country 3
(6 rows) 마지막 명령어에서 country code, state code, country와 state 의 이름들을 명백하게 요구했다. a.cod_country와 같이 어떤 컬럼 들은 테이블을 명시했고, nam_state와 같이 어떤 컬럼들은 명시하지 않았는데, 이는 nam_state는 states에만 존재하는 반면 cod_country는 두 테이블 모두에 존재하기 때문이다. 유일한 컬럼 이름은 특별한 지시자(qualifier)를 필요로 하지 않는다.

조금 더 복잡하게 만들어보자:

manu=> select a.cod_country, cod_state, name, nam_state
manu-> from countries a, states b
manu-> where a.cod_country = b.cod_country
manu-> and a.cod_country = 3;
cod_country|cod_state|name |nam_state
-----------+---------+---------+------------------
3| 1|country 3|State 1, Country 3
3| 2|country 3|State 2, Country 3
(2 rows)이번에는 country 번호가 3인 것만을 검색하도록 제한했다.

함수

행 수를 세는 함수인 count()의 예를 들어보자: select count(*) from states;

count
-----
27
(1 row)위의 명령은 county의 테이블에 들어 있는 행의 수를 리턴한다: manu=> select cod_country, count(*) from states
manu-> group by cod_country;
cod_country|count
-----------+-----
1| 2
2| 2
3| 2
(3 rows)위의 예는 같은 country code를 가진 행의 수를 돌려준다. 그렇게 하기 위해서 cod_country를 사용한 것이다.

다음과 같은 더 좋은 예도 있다:

manu=> select name, count(*) from countries a, states b
manu-> where a.cod_country = b.cod_country
manu-> group by name;
name |count
---------+-----
country 1| 2
country 2| 2
country 3| 2
(3 rows)똑같이 세 개의 행이 출력되었지만 이번에는 리턴된 정보가 더욱 명확하다.

지금까지는 개론만 이야기했다. 워밍업이었을 뿐이죠 :-)

개념 Review

지금까지 SQL의 몇몇 기본적인 개념들을 알아보았다. 가장 의미있는 것은 SQL 자체의 개념에 대한 것이다. 우리는 이제 더 이상 고형화된 데이터를 다루는 것이 아니라 데이터 본체를 다룬다. 데이터 본체는 데이터베이스의 추상적인 개념이다. 단순화시킨다면 "가능한 모든 것 중에서 오직 리턴된 부분"이라고 이해할 수 있을 것이다.

아래와 같은 명령어들을 알아보았다:

CREATE TABLE 해당하는 컬럼들을 가진 테이블을 생성한다.
DROP TABLE테이블을 삭제한다.
SELECT이 명령어는 SQL의 기초가 되는 명령어로, 필요한 데이터 아이템만을 포함하는 임시 테이블을 만들게 해 준다. SELECT는 함수, 복합구문은 물론 sub_selects도 인자로 받을 수 있다: select count(*) from states
where cod_country in (select cod_country from countries);

count
-----
27
(1 row)
BEGIN WORK 또 다른 기본 명령어 중의 하나이다. 이 명령어는 DB 매니저가 BEGIN WORK 부터 행해진 모든 변동사항들을 처리하도록 한다. 우리가 다루는 DB 매니저에서 BEGIN WORK는 transaction의 초기화를 의미하며, 다른 매니저들에서는 데이터베이스의 내용을 바꾸는 첫번째 명령어부터 초기화된다. PostgreSQL에서는 BEGIN WORK가 전에 없다면 데이터를 변경시키는 모든 동작들은 바로 처리된다.

NOTE: 데이터베이스의 구조를 바꾸는 명령어들은 COMMIT WORK를 실행하므로 transaction이 열린 상태에서 그러한 명령어가 시행되면 transaction은 바로 닫히며 ROLLBACK WORK 를 실행하는 것은 불가능해진다.

한 사용자가 열린 transaction을 가지고 있을 경우에 그 사용자는 그의 데이터에 대한 다른 사용자들의 접근권한을 선언할 수 있다:

  • Modified data
  • Original Data previous to the transaction
  • Block data access
  • COMMIT WORK 변동사항들을 처리하면서 transaction 을 닫는다. ROLLBACK WORK는 데이터를 현재의 transaction 이전의 상태로 되돌려 준다.


    transaction의 개념은 에러가 있을 경우에 이전의 상태로 되돌려준다는 점에서 매우 중요하다. 다음과 같은 작업을 해 보자. 우선 "rollback work" 명령으로 이전의 모든 transaction을 닫자:

    manu=> select * from countries;
    cod_country|name
    -----------+---------
    1|country 1
    2|country 2
    3|country 3
    (3 rows)세 개의 행이 있다.
    begin work;transaction을 시작한다. insert into countries values (5, 'Country Not True');행을 하나 삽입하였다. 이제 모든 행이 있다는 것을 확인해보자.
    manu=> select * from countries;
    cod_country|name
    -----------+----------------
    1|country 1
    2|country 2
    3|country 3
    5|Country Not True
    (4 rows)보시다시피 모든 행이 존재한다. 다음으로,
    rollback work;라고 하면 transaction을 취소하게 된다. manu=> select * from countries;
    cod_country|name
    -----------+---------
    1|country 1
    2|country 2
    3|country 3
    (3 rows)행의 개수를 확인해 보면 원래의 3개의 행으로 되돌아갔음을 알 수 있다.

    INSERT 이미 본 바와 같이 이 명령어는 테이블에 명령어를 집어넣는다.
    CREATE TABLE또 다른 중요한 명령어로 테이블과 컬럼들을 생성한다. 다룰 수 있는 자료유형을 알아보자:
    char(range):(range)바이트만큼의 고정된 길이를 가진 문자열
    varchar(rango):(range)바이트까지 저장할 수 있는 고정폭 문자열
    int2:2바이트의 정수형 2**-15 - 2**15
    int4:4바이트의 정수형 2**-31 - 2**31
    money:고정소수점 숫자형, 예를 들면 money(6,3)은 소수점이하 3자리인 6개의 숫자로 된 수를 가리킨다.(123.456 765.987 과 같은식으로..)
    time:시간, 분, 초, 100분의 1초단위의 임시적인 시간 자료(HH:MM:SS:CCC)
    date:연도, 월, 일을 포함하는 날짜자료. YYYY/MM/DD
    timestamp:YYYY/MM/DD:HH:MM:SS:CCC 와 같이 표현되는 날짜, 시간 자료형.
    float(n):단정도 실수형 자료.
    float3:배정도 실수형 자료.
    자료형의 정의는 각 종류의 SQL 매니저에 따라 다르다. 물론 SQL 표준형(가장 최근의 것은 ANSI/92또는 SQL/3이 있다)이 그 특징과 함께 몇몇 자료형을 정희하기도 한다. 이 강좌에서 우리는 PostgreSQL에 국한된 자료형을 거의 보지 못할 것이다.
    DELETE 테이블의 행을 지운다.
    UPDATE 테이블의 행의 컬럼들을 수정한다.

    요약

    조금 산만하긴 했지만, SQL과 관계형 데이터베이스의 설치 등에 대해 알아보았다.

    SQL은 우리의 자료의 추출된 층을 형성하며 우리의 필요에 따라 관리할 수 있게 해 준다.

    지금까지 살펴본 것을 보고 어떤 사람이 이와 같이 물을 수 있다: application 안에서 SQL을 어떻게 쓸 것인가?

    질문의 답은 한 번에 하나씩 알게 될 것이다. 세번째 글에서 우리는 SQL을 사용하는 짧은 C 어플리케이션을 리뷰할 것이다.


    한글 번역: 서환수

    'PostgreSQL' 카테고리의 다른 글

    [PostgreSQL] \? for help width psql commands  (0) 2008.04.24
    Postgresql 프로그래밍  (0) 2008.04.23
    UNION, INTERSECT, EXCEPT  (0) 2007.09.19
    Postgresql JDBC 드라이버 설치하기  (0) 2007.06.13
    PostgreSQL 7.2 설치하기  (0) 2006.06.17
    , .
    저자: Gavin Andresen, 한동훈 역
    원문:
    http://www.onlamp.com/pub/a/php/2006/03/16/autofill-forms.html

    나는 타이핑하는 것은 싫어하지만 코드를 작성하는 것은 좋아한다. 한동안 PHP에서 폼을 다루기 위해 필요한 기계적인 코드들을 타이핑했던 적이 있었는데, 그 이후로 "보다 좋은 방법"을 찾아나서기 시작했다. 이 글에서는 내가 PHP 정규식을 사용해서 폼을 처리하는 데 필요한 많은 작업들을 어떻게 제거했으며, 내가 싫어하는 타이핑을 줄이고, 코드를 작성하거나 넷핵(
    NetHack)을 즐기는 데 시간을 할애하게 되었는지 설명할 것이다.

    문제

    제프 콕스웰(Jeff Cogswell)은
    PHP와 CSS를 사용한 사용자 친화적인 폼 검증에서 일반적인 문제를 기술했다. 즉, 폼을 화면에 표시하고, 입력을 검증하고, 감사 페이지를 보여주거나, 검증이 실패하면 사용자가 입력한 값과 폼에 에러가 난 부분들을 표시해서 다시 보여주는 등의 일반적인 문제를 기술했다. 나는 깔끔하고 세련돼 보이는 웹 사이트를 위해 많은 돈을 지불하는 고객들을 위해 폼을 작성하기 때문에, 폼은 항상 멋있어야 하고, 사이트의 나머지 부분들과도 잘 조화되어야 한다.

    지루한 입력에 대한 해결책

    문제를 해결하는 가장 쉬운 방법은 위지윅 HTML 에디터를 사용해서 멋있는 폼을 만들고, 적절한 위치에 폼 값과 에러 메시지를 표시하기 위한 PHP 코드를 추가하는 것이다. 예를 들어, 폼에 "email,"이라는 필드 이름을 갖고 있다면, 나는 이 필드를 검증하기 위해 다음과 같은 PHP 코드를 사용한다.
    $validationData = array();
    $validationData['email'] =
    array('isRequired', type='emailAddress');

    $formErrors = validateForm($_POST, $validationData)
    나쁘지 않아 보인다. validateForm() 함수를 한번 작성하기만 하면 계속에서 이것을 사용할 수 있고, 다른 값을 처리할 필요가 있을 때 마다 그에 해당하는 처리를 확장하면 된다.

    에러와 올바른 값을 폼에 표시하는 것은 다소 지저분하다. 조금 간단한 HTML은 다음과 같다.
    <td align="right">Email:</td>
    <td><input name="email" value="" /></td>
    이제, HTML과 PHP로 섞인 미로가 시작된다. <td align="right"
    <?php if (isset($formErrors['email'])) {
    echo 'class="error"';
    } ?> >
    Email:</td>
    <td><input name="email"
    <?php if (isset($_POST['email'])) {
    echo 'value="'.$_POST['email'].'"';
    } ?> /></td>
    폼에 있는 모든 필드에 대해서 이를 반복하는 것은 번거롭다. 특히, 50개나 되는 주를 선택하는 "살고 있는 주를 선택하세요" 같은 드롭다운 목록에 사용된 <option> 태그의 value에 이 같은 코딩을 하는 것은 너무 끔찍한 일이어서 차라리 배관공이 되는 게 나을 것이다.

    문제, 다시 설명하는 버전

    PHP 코드가 없는 HTML 폼부터 시작해보자. 폼 검증 오류시 표시할 값들을 PHP 배열로 갖고 있다. 배열과 HTML을 넘겨받아서 적절한 위치에 값을 써넣은 수정된 HTML을 반환하는 PHP 함수가 필요하다. 예를 들어, <input name="email">과 $_POST['email'] = "gavin@mailinator.com"을 넘겨주면 <input name="email" value="gavin@mailinator.com">을 반환하는 것이다. PHP 설명서와 웹을 뒤적인 다음에 이런 역할을 하는 코드가 없다는 사실을 알았다. 그래서 스스로 그런 함수를 작성하고, 함수 이름을 fillInFormValues()라고 붙였다.

    fillInFormValues의 동작

    fillInFormValues는 $html, $values, $errors 같은 인자를 넘겨받는다.
    1. $html은 폼에 사용할 HTML 마크업을 포함한다. fillInFormValues는 어떤 HTML을 전달해도 관계없다. 즉, HTML3, HTML4, XHTML을 처리할 수 있으며, HTML 전체를 전달하거나 폼의 입력 필드가 있는 HTML의 일부분을 전달해도 상관없다. fillInFormValues는 표준 HTML 테스트를 통과한 것이면 된다. HTML을 전달할 때 </textarea>와 같은 닫기 태그를 생략하면 안된다. 일부 브라우저는 이를 허용하지 않기 때문에 화면에 표시되지 않을 수 있다.
    2. $values는 폼에서 표시할 값들을 갖고 있는 PHP 배열이며, 형식은 $values['fieldName'] = fieldValue이다. 폼 검증 오류 때문에 폼을 다시 표시하려면 $_POST, $_GET, $_REQUEST 중에 하나를 전달하면 된다.
    3. $errors는 검증 오류 메시지를 갖고 있는 PHP 배열이며, 형식은 $errors['fieldName'] = "error message"이다. fillInFormValues는 HTML에서 <ul class="error"></ul> 요소를 찾아서, 에러 메시지를 삽입한다. 폼 텍스트를 마크업 하려면 <label> 태그를 사용해야 한다. <label for="address">Street Address:</label>
      <input name="address" id="address" />
      fillInFormValues는 $errors['address']가 설정되어 있으면 있으면 해당 <label>에 class="error"을 추가한다. CSS는 label.error: color:red;와 같이 간단한 규칙을 정의하고, 에러가 발생한 부분을 빨간색으로 표시한다. <label> 태그를 사용하면 스크린 리더기나 다른 보조 도구를 사용하는 사람들에게 보다 접근성을 높일 수 있으며, 사용자가 Street Address 텍스트를 클릭하면 address 필드에 커서를 위치할 것을 브라우저에게 지시해준다. 레이블은 입력 필드의 id 속성에서 일치시키는 것이며, name 요소로 일치시키는 것이 아니라는 것에 주의해야 한다.(내 경우엔 name과 ID에 모두 같은 이름을 사용한다)
    fillInFormValues는 $values와 $errors를 표시할 수 있는 $html을 가능한한 수정하지 않은 형태로 반환한다. 빈 배열을 전달하면 $html은 변경되지 않으며, 처음에는 이런식으로 화면을 표시하는 것이 쉽다. 완전히 동작하는 예제는 이곳에서 볼 수 있다. <?php
    ob_start();
    ?>
    <html>
    <head>
    <title>fillInFormValues: short example</title>
    <style>
    .error { color: red; }
    </style>
    </head>
    <body>
    <h1>Sign up for our newsletters</h1>
    <ul class="error"><li>PLACEHOLDER FOR FORM ERRORS</li></ul>
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="GET">
    <table border="0">
    <tr>
    <td><label for="email">Your Email Address:</label></td>
    <td><input type="text" name="email" id="email"></td>
    </tr>
    <tr>
    <td><label for="newsletter">Sign up for these newsletters:</label></td>
    <td>
    <input type="checkbox" name="news" id="news">
    <label for="news">News</label><br />
    <input type="checkbox" name="security" id="security">
    <label for="security">Security Notices</label><br />
    <input type="checkbox" name="specials" id="specials">
    <label for="specials">Specials</label>
    </td>
    </tr>
    <tr><td> </td>
    <td><input type="submit" name="submit" value="Sign Up"></td>
    </tr>
    </table>
    </form>
    </body>
    </html>

    <?php
    $html = ob_get_contents();
    ob_end_clean();

    require_once("fillInFormValues.php");
    require_once("validateForm.php");

    $request = (get_magic_quotes_gpc() ?
    array_map('stripslashes', $_REQUEST), $_REQUEST);

    $validationData['email'] = array('isRequired', 'type' => 'email');
    if (isset($request['submit'])) {
    $formErrors = validateForm($request, $validationData);
    if (count($formErrors) == 0) {
    // Normally there would be code here to process the form
    // and redirect to a thank you page...
    // ... but for this example, we just always re-display the form.
    $info = "No errors; got these values:".
    nl2br(htmlspecialchars(print_r($request, 1)));
    $html = preg_replace('/<body>/', "<body><p>$info</p>", $html);
    }
    }
    else {
    $formErrors = array();
    }

    echo fillInFormValues($html, $request, $formErrors);

    ?>
    페이지의 HTML을 fillInFormValues에 전달할 수 있는 PHP 문자열로 얻기 위해 PHP의 출력 버퍼링 루틴인 ob_start, ob_get_contents, ob_end_clena을 사용했다. fillInFormValues는 $validationData 배열에 값을 채우는 것 뿐만 아니라 어떤 종류의 폼에 대해서도 사용할 수 있다. 예를 들어, 보험회사의 웹 사이트에서 볼 수 있는 끔찍한 폼에 대해서도 이 함수를 사용해서 값을 채울 수 있다.

    fillInFormValues의 구현

    fillInFormValues는 HTML에서 수정할 부분을 찾기 위해 preg_replace_callback을 사용한다. 예를 들어, class="error"를 추가하기 위해 <label> 태그를 찾기 위한 정규표현식은 다음과 같다.
    /<label([^>]*)>/i
    왼쪽에서 오른쪽으로 읽어가면 된다. 첫번째 /는 정규식의 시작을 의미한다. <label은 정확히 일치해야하는 부분을 의미한다. 괄호 ()안에 들어있는 [^>]*과 같은 이상한 표현은 "> 문자를 제외한 모든 문자"를 의미한다. 괄호 다음에 >은 문자 '>'을 의미한다. /i는 정규식의 끝을 의미하며, i는 <LABEL ...>이나 <label ...> 등을 찾을 수 있게끔 지정하는 것으로 대소문자 구분을 하지 않는 것을 의미한다.

    이것은 HTML을 분석하는 간단한 방법인 동시에 지저분한 방법이기도 하다. 예를 들어, 이 표현식은 HTML 주석으로 둘러싼 경우도 고려하지 않는다. 즉, 문제가 되는 것은 아니지만 주석으로 표시된 label 태그도 변경한다. <input value="<label for='foo'*>"*>과 같이 HTML4 코드안에 속성 값을 직접 전달하면 문제가 생길 수 있다. 따라서, 이런식으로 코드를 사용하면 된다. 만약, HTML 파서가 필요하다면
    XML_HTMLSax을 사용해야 한다.

    이제, label 태그를 알아보고 처리할 수 있는 콜백 함수를 위한 코드를 작성해야 한다. 이 함수의 역할은 경우에 따라 label 태그를 변경하지 않거나 class="error"와 같은 문자열을 추가하는 것이다.
    function fillInLabel($matches)
    {
    global $formErrors;
    global $idToNameMap;

    $tag = $matches[0];

    $for = getAttributeVal($tag, "for");
    if (empty($for) or !isset($idToNameMap[$for])) { return $tag; }
    $name = $idToNameMap[$for];
    if (array_key_exists($name, $formErrors)) {
    return replaceAttributeVal($tag, 'class', 'error');
    }
    return $tag; // No error.
    }
    preg_replace_callback에 전달된 콜백 함수는 항상 인자가 하나 있다. 이 인자는 정규식으로 검색된 내용에 대한 배열이다. $matches[0]는 전체 내용을 의미하며, $matches[1]은 괄호의 첫번째 집합에 의해 일치된 내용이 된다. 여기서 $matches[0]는 전체 <label> 태그이다. label의 속성을 가져오고, $formErrors 배열에서 해당하는게 있는지 확인한다. 만약, 같은 것을 발견하면 label의 class 속성을 class="error"와 같이 변경한다. label에서 해당 에러가 없으면 함수는 태그를 변경하지 않는다. fillInLabel()에 필요한 정보를 전달하기 위해 전역변수를 사용했다.

    <input>, <select>, <textarea>, <ul class="error">에 대한 패턴과 콜백도 이와 유사하지만 조금 더 복잡하다. <select>의 콜백이 가장 복잡하다. 이 콜백은 <select>와 </select> 태그 사이의 <option> 태그를 찾아서 변경하기 위해 preg_replace_callback을 재귀적으로 사용한다.HTML4에서는 다음과 같이 value를 사용하는 것이 허용되기 때문에 <input> 태그에 대한 정규표현식은 복잡하다.
    <input name="foo" value="hello <smile>">
    인용부호 안의 > 문자는 ">을 제외한 모든 문자"([^>]*)를 의미하며, input 태그 안에서는 동작하면 안 된다. XML과 XHTML의 속성 값에는 <과 >을 사용하는 것이 허용되지 않는다. 이 값들은 반드시 &lt;와 &gt;로 사용되야 한다. HTML4가 사라지게되면 삶은 보다 단순해질 것이다. getAttributeVal과 replaceAttributeVal 함수는 PHP에서 매우 강력한 정규표현식 함수인 preg_match_all을 사용한다. 이들 함수는 HTML 태그안의 속성들을 찾기 위해 정규식을 사용한다. /(\w+)((\s*=\s*".*?")|(\s*=\s*'.*?')|(\s*=\s*\w+)|())/s
    대충 봐서는 이게 무엇을 의미하는지 이해하는 것이 쉽지 않다. 다음과 문자열이 전달된다고 가정해보자. name="foo" value='123' style=purple checked
    정규식의 첫번째 부분인 (\w+)는 name, value, style, checked 등을 검색한다. 이 표현식은 하나 이상의 "단어"들을 일치시킨다. 나머지 정규표현식은 HTML에서 속성 값을 지정할 수 있는 네 가지 방법을 지정하기 위한 것이다. (\s*=\s*".*?")는 ="foo"와 같은 형태를 찾기 위한 것이며, (\s*=\s*'.*?')는 ='123'과 같은 형태를, (\s*=\s*\w+)는 =purple과 같은 형태를 찾기 위한 것이다. ()는 어떤 값도 사용하지 않는 checked와 같은 요소를 찾기 위한 것이다.

    최대한 많이 일치시키려고 하기 때문에 정규표현식은 탐욕스럽지만 preg_match_all은 여러분이 원하는 작업을 정확하게 수행해줄 것이다. name="foo" value='123' style=purple checked를 전달하면 4개가 일치되었다고 알려줄 것이다.

    getAttributeVal의 전체 코드는 다음과 같다.
    /**
    * Returns value of $attribute, given guts of an HTML tag.
    * Returns false if attribute isn't set.
    * Returns empty string for no-value attributes.
    *
    * @param string $tag Guts of HTML tag, with or without the <tag and >.
    * @param string $attribute E.g. "name" or "value" or "width"
    * @return string|false Returns value of attribute (or false)
    */
    function getAttributeVal($tag, $attribute) {
    $matches = array();
    // This regular expression matches attribute="value" or
    // attribute='value' or attribute=value or attribute
    // It's also constructed so $matches[1][...] will be the
    // attribute names, and $matches[2][...] will be the
    // attribute values.
    preg_match_all('/(\w+)((\s*=\s*".*?")|(\s*=\s*\'.*?\')|(\s*=\s*\w+)|())/s',
    $tag, $matches, PREG_PATTERN_ORDER);

    for ($i = 0; $i < count($matches[1]); $i++) {
    if (strtolower($matches[1][$i]) == strtolower($attribute)) {
    // Gotta trim off whitespace, = and any quotes:
    $result = ltrim($matches[2][$i], " \n\r\t=");
    if ($result[0] == '"') { $result = trim($result, '"'); }
    else { $result = trim($result, "'"); }
    return $result;
    }
    }
    return false;
    }
    preg_match_all에 PREG_PATTERN_ORDER를 전달하면 $matches[1][$i]에서 속성 이름을, $matches[2][$i]에서 속성 값을 반환한다. replaceAttributeVal의 코드도 모든 속성들의 위치를 찾기 위해 PREG_OFFSET_CAPTURE(PHP 4.3.0 이상에서 지원)를 전달하고, 기존 값을 변경하거나 HTML 태그에 값을 추가하기 위해 substr_replace를 사용한다는 점만 제외하면 동일하다.

    정리

    fillInFormValues()의 첫번째 버전은 인자들을 전역변수에 집어넣고, 이들을 실제로 처리하는 콜백 함수에 인자를 전달하는 것입니다. 모든 콜백 함수는 PHP 함수 네임스페이스(namespace)에 등록됩니다.

    "helper" 클래스에서 인자와 콜백 함수를 캡슐화하였고, array( &$this, "function" )과 같이 콜백 인자를 갖는 모든 PHP 함수들이 사용하는 콜백 구문을 사용한다. fillInFormValues()는 헬퍼 객체를 생성하고, 해당 객체에서 모든 작업을 처리할 메서드를 호출한다.
    function fillInFormValues($formHTML, $request = null, $formErrors = null)
    {
    if ($request === null) {
    // magic_quotes on: gotta strip slashes:
    if (get_magic_quotes_gpc()) {
    function stripslashes_deep(&$val) {
    $val = is_array($val) ? array_map('stripslashes_deep', $val)
    : stripslashes($val);
    return $val;
    }
    $request = stripslashes_deep($_REQUEST);
    }
    else {
    $request = $_REQUEST;
    }
    }
    if ($formErrors === null) { $formErrors = array(); }

    $h = new fillInFormHelper($request, $formErrors);
    return $h->fill($formHTML);
    }

    /**
    * Helper class, exists to encapsulate info needed between regex callbacks.
    */
    class fillInFormHelper
    {
    var $request; // Normally $_REQUEST, passed into constructor
    var $formErrors;
    var $idToNameMap; // Map form element ids to names

    function fillInFormHelper($r, $e)
    {
    $this->request = $r;
    $this->formErrors = $e;
    }

    function fill($formHTML)
    {
    $s = fillInFormHelper::getTagPattern('input');
    $formHTML = preg_replace_callback("/$s/is",
    array(&$this, "fillInInputTag"), $formHTML);

    // Using simpler regex for textarea/select/label, because in practice
    // they never have >'s inside them:
    $formHTML = preg_replace_callback('!(<textarea([^>]*>))(.*?)(</textarea\s*>)!is',
    array(&$this, "fillInTextArea"), $formHTML);

    $formHTML = preg_replace_callback('!(<select([^>]*>))(.*?)(</select\s*>)!is',
    array(&$this, "fillInSelect"), $formHTML);

    // Form errors: tag <label> with class="error", and fill in
    // <ul class="error"> with form error messages.
    $formHTML = preg_replace_callback('!<label([^>]*)>!is',
    array(&$this, "fillInLabel"), $formHTML);
    $formHTML = preg_replace_callback('!<ul class="error">.*?</ul>!is',
    array(&$this, "getErrorList"), $formHTML);

    return $formHTML;
    }

    /**
    * Returns pattern to match given a HTML/XHTML/XML tag.
    * NOTE: Setup so only the whole expression is captured
    * (subpatterns use (?: ...) so they don't catpure).
    * Inspired by http://www.cs.sfu.ca/~cameron/REX.html
    *
    * @param string $tag E.g. 'input'
    * @return string $pattern
    */
    function getTagPattern($tag)
    {
    $p = '('; // This is a hairy regex, so build it up bit-by-bit:
    $p .= '(?is-U)'; // Set options: case-insensitive, multiline, greedy
    $p .= "<$tag"; // Match <tag
    $sQ = "(?:'.*?')"; // Attr val: single-quoted...
    $dQ = '(?:".*?")'; // double-quoted...
    $nQ = '(?:\w*)'; // or not quoted at all, but no wacky characters.
    $attrVal = "(?:$sQ|$dQ|$nQ)"; // 'value' or "value" or value
    $attr = "(?:\s*\w*\s*(?:=$attrVal)?)"; // attribute or attribute=
    $p .= "(?:$attr*)"; // any number of attr=val ...
    $p .= '(?:>|(?:\/>))'; // End tag: > or />
    $p .= ')';
    return $p;
    }

    /**
    * Returns value of $attribute, given guts of an HTML tag.
    * Returns false if attribute isn't set.
    * Returns empty string for no-value attributes.
    *
    * @param string $tag Guts of HTML tag, with or without the <tag and >.
    * @param string $attribute E.g. "name" or "value" or "width"
    * @return string|false Returns value of attribute (or false)
    */
    function getAttributeVal($tag, $attribute) {
    $matches = array();
    // This regular expression matches attribute="value" or
    // attribute='value' or attribute=value or attribute
    // It's also constructed so $matches[1][...] will be the
    // attribute names, and $matches[2][...] will be the
    // attribute values.
    preg_match_all('/(\w+)((\s*=\s*".*?")|(\s*=\s*\'.*?\')|(\s*=\s*\w+)|())/s',
    $tag, $matches, PREG_PATTERN_ORDER);

    for ($i = 0; $i < count($matches[1]); $i++) {
    if (strtolower($matches[1][$i]) == strtolower($attribute)) {
    // Gotta trim off whitespace, = and any quotes:
    $result = ltrim($matches[2][$i], " \n\r\t=");
    if ($result[0] == '"') { $result = trim($result, '"'); }
    else { $result = trim($result, "'"); }
    return $result;
    }
    }
    return false;
    }
    /**
    * Returns new guts for HTML tag, with an attribute replaced
    * with a new value. Pass null for new value to remove the
    * attribute completely.
    *
    * @param string $tag Guts of HTML tag.
    * @param string $attribute E.g. "name" or "value" or "width"
    * @param string $newValue
    * @return string
    */
    function replaceAttributeVal($tag, $attribute, $newValue) {
    if ($newValue === null) {
    $pEQv = '';
    }
    else {
    // htmlspecialchars here to avoid potential cross-site-scripting attacks:
    $newValue = htmlspecialchars($newValue);
    $pEQv = $attribute.'="'.$newValue.'"';
    }

    // Same regex as getAttribute, but we wanna capture string offsets
    // so we can splice in the new attribute="value":
    preg_match_all('/(\w+)((\s*=\s*".*?")|(\s*=\s*\'.*?\')|(\s*=\s*\w+)|())/s',
    $tag, $matches, PREG_PATTERN_ORDER|PREG_OFFSET_CAPTURE);

    for ($i = 0; $i < count($matches[1]); $i++) {
    if (strtolower($matches[1][$i][0]) == strtolower($attribute)) {
    $spliceStart = $matches[0][$i][1];
    $spliceLength = strlen($matches[0][$i][0]);
    $result = substr_replace($tag, $pEQv, $spliceStart, $spliceLength);
    return $result;
    }
    }

    if (empty($pEQv)) { return $tag; }

    // No match: add attribute="newval" to $tag (before closing tag, if any):
    $closed = preg_match('!(.*?)((>|(/>))\s*)$!s', $tag, $matches);
    if ($closed) {
    return $matches[1] . " $pEQv" . $matches[2];
    }
    return "$tag $pEQv";
    }

    /**
    * Returns modified <input> tag, based on values in $request.
    *
    * @param array $matches
    * @return string Returns new guts.
    */
    function fillInInputTag($matches) {
    $tag = $matches[0];

    $type = fillInFormHelper::getAttributeVal($tag, "type");
    if (empty($type)) { $type = "text"; }
    $name = fillInFormHelper::getAttributeVal($tag, "name");
    if (empty($name)) { return $tag; }
    $id = fillInFormHelper::getAttributeVal($tag, "id");
    if (!empty($id)) { $this->idToNameMap[$id] = $name; }

    switch ($type) {
    /*
    * Un-comment this out at your own risk (users shouldn't be
    * able to modify hidden fields):
    * case 'hidden':
    */
    case 'text':
    case 'password':
    if (!array_key_exists($name, $this->request)) {
    return $tag;
    }
    return fillInFormHelper::replaceAttributeVal($tag, 'value', $this->request[$name]);
    break;
    case 'radio':
    case 'checkbox':
    $value = fillInFormHelper::getAttributeVal($tag, "value");
    if (empty($value)) { $value = "on"; }

    if (strpos($name, '[]')) {
    $name = str_replace('[]', '', $name);
    }

    if (!array_key_exists($name, $this->request)) {
    return fillInFormHelper::replaceAttributeVal($tag, 'checked', null);
    }
    $vals = (is_array($this->request[$name])?$this->request[$name]:array($this->request[$name]));

    if (in_array($value, $vals)) {
    return fillInFormHelper::replaceAttributeVal($tag, 'checked', 'checked');
    }
    return fillInFormHelper::replaceAttributeVal($tag, 'checked', null);
    }
    return $tag;
    }
    /**
    * Returns modified <textarea...> tag, based on values in $request.
    *
    * @param array $matches
    * @return string Returns new value.
    */
    function fillInTextArea($matches) {
    $tag = $matches[1]; // The <textarea....> tag
    $val = $matches[3]; // Stuff between <textarea> and </textarea>
    $endTag = $matches[4]; // The </textarea> tag

    $name = fillInFormHelper::getAttributeVal($tag, "name");
    if (empty($name)) { return $matches[0]; }
    $id = fillInFormHelper::getAttributeVal($tag, "id");
    if (!empty($id)) { $this->idToNameMap[$id] = $name; }

    if (!array_key_exists($name, $this->request)) { return $matches[0]; }
    return $tag.htmlspecialchars($this->request[$name]).$endTag;
    }
    /**
    * Returns modified <option value="foo"> tag, based on values in $vals.
    *
    * @param array $matches
    * @return string Returns tag with selected="selected" or not.
    */
    function fillInOption($matches)
    {
    $tag = $matches[1]; // The option tag
    $valueAfter = $matches[2]; // Potential value (stuff after option tag)
    $val = fillInFormHelper::getAttributeVal($tag, "value");
    if (empty($val)) { $val = trim($valueAfter); }
    if (in_array($val, $this->selectVals)) {
    return fillInFormHelper::replaceAttributeVal($tag, 'selected', 'selected').$valueAfter;
    }
    else {
    return fillInFormHelper::replaceAttributeVal($tag, 'selected', null).$valueAfter;
    }
    }

    var $selectVals;

    /**
    * Returns modified <select...> tag, based on values in $request.
    *
    * @param array $matches
    * @return string
    */
    function fillInSelect($matches) {
    $tag = $matches[1];
    $options = $matches[3];
    $endTag = $matches[4];

    $name = fillInFormHelper::getAttributeVal($tag, "name");
    if (empty($name)) { return $matches[0]; }
    $id = fillInFormHelper::getAttributeVal($tag, "id");
    if (!empty($id)) { $this->idToNameMap[$id] = $name; }

    if (strpos($name, '[]')) {
    $name = str_replace('[]', '', $name);
    }
    if (!array_key_exists($name, $this->request)) { return $matches[0]; }

    $this->selectVals =
    (is_array($this->request[$name])?$this->request[$name]:array($this->request[$name]));

    // Handle all the various flavors of:
    // <option value="foo" /> OR <option>foo</option> OR <option>foo
    $s = fillInFormHelper::getTagPattern('option');
    $pat = "!$s(.*?)(?=($|(</option)|(</select)|(<option)))!is";
    $options = preg_replace_callback($pat, array(&$this, "fillInOption"), $options);
    return $tag.$options.$endTag;
    }

    /**
    * Returns modified <label...> tag, based on $formErrors.
    *
    * @param array $matches
    * @return string
    */
    function fillInLabel($matches) {
    $tag = $matches[0];
    $for = fillInFormHelper::getAttributeVal($tag, "for");
    if (empty($for) or !isset($this->idToNameMap[$for])) { return $tag; }
    $name = $this->idToNameMap[$for];

    if (array_key_exists($name, $this->formErrors)) {
    return fillInFormHelper::replaceAttributeVal($tag, 'class', 'error');
    }
    return $tag; // No error.
    }

    /**
    * Returns modified <ul class="error"> list with $formErrors error messages.
    *
    * @return string
    */
    function getErrorList() {
    $result = "";
    foreach (array_unique($this->formErrors) AS $f => $msg) {
    if (!empty($msg)) {
    $result .= "<li>".htmlspecialchars($msg)."</li>\n";
    }
    }
    if (empty($result)) { return ""; } // No errors: return empty string.
    $result = '<ul class="error">'.$result.'</ul>';
    return $result;
    }
    } // End of helper class.

    다른 방법

    패키지들 중에는 PHP 코드에서 HTML 폼을 생성해주는 것들이 있다. 예를 들어,
    HTML_QuickForm과 같은 멋진 도구를 알고 있다. 대부분의 패키지들이 폼 검증과 다시 표시하는 것을 자동화해주지만, 나는 HTML을 생성하기 위해 PHP를 사용하는 것을 좋아하지 않는다. 나는 가능한 한 화면(HTML)과 응용프로그램 로직(PHP 코드)을 분리하는 것을 좋아한다. 웹 페이지 형태로 편집하는 것을 매우 좋아하기 때문에 드림위버와 같은 위지윅 에디터를 사용한다.

    단순한 것이 최선이다

    fillInFormValues()는 매우 간단한 인터페이스를 갖고 있다. 바로 함수 하나로 되어 있다. 구현 코드는 끔찍할 정도로 복잡하지도 않으며, 코멘트를 포함해서 400라인이 안되는 코드이다. 나는 단순한 것을 좋아하며, 이를 보다 큰 프로젝트에 통합시키는 것도 쉽다. 나는 데이터베이스에서 가져온 값을 표시하는 폼을 만들기 위해 fillInFormValues()를 사용한다. 또한, 이 구현을
    스마티(Smarty)의 블록 함수로 등록해두었따. 어쨌거나 이 기능을 사용하기 시작한 이후로는 배관공이 되려는 유혹을 느낄 필요가 없었다.

    소스 다운로드

    이 글의 소스 코드는 여기에서 다운로드 받을 수 있으며, fillInFormValues에 사용할 수 있는 소스 코드와 유닛테스트를 다운받을 수 있는 압축파일로 제공한다.

    , .

    mail_lib.php

    <?

    function GetTimeStamp($date) {
    /* 인자 형식
    YYYY-MM-DD
    YYYY-MM-DD HH:mm:ss
    */
    if (strlen($DATE) == 10) {
    $time = mktime(0,0,0,(int)substr($date,5,2),(int)substr($date,8,2),(int)substr($date,0,4));
    } else {
    $time = mktime((int)substr($date,11,2),(int)substr($date,14,2),(int)substr($date,17,2),
    (int)substr($date,5,2),(int)substr($date,8,2),(int)substr($date,0,4));
    }
    return $time;
    }

    function HtmlHeader($strTitle)
    { ?>
    <html>
    <head>
    <meta http-equiv=Content-Type content=text/html; charset=euc-kr>
    <title><?echo $strTitle;?></title>
    <link rel="stylesheet" href="web_mail.css" type="text/css">
    </head>
    <?
    }

    function DisplayCopyRight()
    {
    global $C_TABLE_SIZE; ?>

    <table width="<?echo $C_TABLE_SIZE;?>" cellspacing=0 cellpadding=0 border=0>
    <tr>
    <td>
    <hr width=100% size=2 color=#007394>
    </td>
    </tr>
    <tr>
    <td align=center><b><font face=굴림 size=2 color=#003354>
    Copyright ⓒ 2000<img src="/img/ihelpers.gif" border=0
    align=absmiddle> All rights reserved.</font></b></td>
    </tr>
    </table>
    <br>
    <?
    }

    function HtmlTail()
    {
    echo "</body></html>";
    }

    function PrintMsg($strMessage)
    {
    ?>
    <script language="javascript">
    <!--
    alert("<?echo $strMessage;?>");
    //-->
    </script>
    <?
    }

    function PrintMsgBack($strMessage)
    {
    ?>
    <script language="javascript">
    <!--
    alert("<?echo $strMessage;?>");
    history.back();
    //-->
    </script>
    <?
    exit;
    }

    function GoUrl($strUrl)
    {
    ?>
    <script language="javascript">
    <!--
    varUrl = '<?echo $strUrl;?>';
    if (varUrl !="") {
    document.location.replace(varUrl);
    }
    //-->
    </script>
    <?
    }

    function RedirectTarget($url,$target)
    { ?>
    <html>
    <body onLoad="document.form1.submit()";>
    <form action="<?echo $url;?
    >" target="<?echo $target;?>" name=form1 method=post>
    <input type=hidden name=name value="">
    </form>
    </body>
    </html>
    <?
    }

    function CheckBroswer($num, $num2)
    {
    global $HTTP_USER_AGENT;

    if (strpos($HTTP_USER_AGENT, "MSIE")) {
    return $num;
    } else {
    return $num2;
    }
    }

    function CompStr($buffer, $value) {
    if (strlen($buffer) <= strlen($value)) return false;

    if (substr($buffer, 0, strlen($value)) == $value) {
    return true;
    } else {
    return false;
    }
    }

    function Decode($val) {
    if(substr($val,0,2) == "=?") { //인코딩 여부 확인
    $code = strpos($val, "?", 3);
    $code = strpos($val, "?", $code+1);
    $val = substr($val, $code+1, strlen($val) - $code -3);
    return imap_base64($val);
    } else {
    return $val;
    }
    }

    function printOutLook($val) {
    $line = split("
    ", $val);
    $val = "";
    $cnt = 0;
    for($i=0;$i<count($line);$i++) {
    if($line[$i]=="") $cnt++;
    if($cnt == 4) $val .= $line[$i] . "
    ";
    }
    echo imap_base64($val);
    }
    ?>
    , .