LazarusCompleteGuide:12.3
이용 가능한 TDataset 자손들
앞서 언급하였듯이 TDataset 는 추상 클래스이다. 앞의 절에서 이론적 고찰을 거쳤으니 이제 다양한 실제 구현부를 검토할 것이다. 라자루스 배포판은 여러 TDataset 자손들을 포함하고, 각각은 특정 데이터베이스 유형으로 접근을 제공한다. 표 12.13은 이용 가능한 데이터세트와 그것이 구현된 라자루스 패키지를 열거한다.
Dataset | 설명 |
TDBF | dBase 파일로의 접근에 사용 가능한 데이터세트다. DBFlaz 패키지에 구현된다. |
TMemDataset | IN 메모리 데이터를 포함하는 IN 메모리 데이터세트다. 수동으로 저장하지않는 한 데이터세트가 닫히자마자 데이터가 손실된다. memdslaz 패키지에 위치한다. |
TParadox | Paradox 파일을 읽는 데 사용 가능한 읽기전용 데이터세트다. 외부 Paradox 읽기 라이브러리에 의존하며, lazparadox 패키지에서 구현된다. |
TFixedDataset | sdflaz 패키지에 위치하며, 고정된 길이 값을 가진 ASCII 파일을 읽고 쓸 수 있도록 해준다. |
TSDFDataset | sdflaz 패키지에 위치하며, 콤마로 구분된 값 필드가 있는 ASCII 파일을 (CSV 파일이라고도 알려짐) 읽도록 해준다. |
TSQLQuery | 프리 파스칼의 SQLDB 구현의 일부로, SQLDBLaz 패키지에 위치한다. Firebird, MySQL, Oracle, PostGreSQL, SQLite와 같은 다수의 데이터베이스로 연결 시 사용 가능하다. 이러한 데이터베이스의 경우, 데이터베이스가 제공하는 클라이언트 라이브러리를 이용해 데이터베이스로 네이티브 접근이 사용된다. SQLDB 는 ODBC 드라이버가 있는 다른 데이터베이스로 연결 시 사용할 수도 있다. |
표 12.13: 기본 설치에서 이용 가능한 TDataset 자손 |
컴포넌트 팔레트의 Data Access 페이지 내용을 (표준 데이터세트와 TDataSource 컴포넌트 일부가 포함된) 그림 12.8에 실려 있다. TSQLQuery 와 다양한 연결은 컴포넌트 팔레트의 SQLDB 페이지에서 찾을 수 있다.
라자루스에서 기본적으로 제공하는 패키지 외에 이용 전에 라자루스에 따로 설치를 요하는 외부 패키지도 있다.
패키지 | 설명 |
Zeos | 무료의 많이 개발된(mature) 데이터베이스 라이브러리로, 설계가 SQLDB와 유사하며 SQLDB와 동일한 데이터베이스의 다수로 접근 시 사용된다. |
AnyDAC | 상용 데이터 접근 계층으로, 다양한 데이터베이스로 접근을 위해 설계되었으며, 현재 RemObjects 소프트웨어에서 배포한다. |
UIB | Unified Interbase는 firebird/Interbase 데이터베이스 접근 계층으로, 낮은 수준의 데이터 접근 메커니즘을 제공하지만 TDataset 자손도 제공한다. |
FBLib | Firebird/Interbase 데이터베이스 접근 계층이다. |
ADS | Advantage Database Server는 프리 파스칼과 함께 사용할 수도 있다. 최소 변경으로 컴파일되어야 한다. |
NexusDB | 상용 Nexus DB는 프리 파스칼과 라자루스와 함께 작업하는 것으로 보고된다. |
표 12.14: 라자루스와 함께 작업하는 (외부) 패키지 |
다음 절에서는 라자루스와 함께 배포되는 데이터세트 컴포넌트만 집중적으로 논하고자 한다. 이러한 컴포넌트의 일반적인 사용을 그림 12.9에 표시하였다.
IN 메모리 데이터
memdslaz 패키지는 라자루스 IDE에 TMemDataset 데이터세트를 등록한다. 이것은 메모리 데이터세트(Memory Dataset)로, 그 곳에 작성된 데이터는 모두 메모리 내부에 보관된다. 데이터를 디스크로 로딩 및 저장하는 메소드들을 비롯해 새 데이터세트를 정의하는 메소드를 포함한다. 색인(lookup) 리스트 외에는 TMemDataset의 사용을 권장하지 않지만, 지속성 데이터 저장으로 사용할 수는 있겠다.
TMemDataset 는 외부 데이터베이스 엔진을 필요로 하지 않는다. 이는 데이터를 메모리 내에 레코드 리스트로 보관한다. 컴포넌트는 레코드 검색뿐만 아니라 레코드의 추가, 편집, 삭제를 구현한다.
데이터세트가 닫히면 메모리가 해제되고 데이터를 더 이상 사용할 수 없다. 애플리케이션을 여러 번 실행하는 동안 데이터를 이용 가능한 상태로 유지해야 하는 경우 데이터를 파일로 저장하여 파일로부터 로딩하는 메소드가 제공된다. 저장된 데이터는 메모리 블록의 사본(copy)에 불과하며, 데이터를 색인하거나 검색하는 메소드는 없다. TMemDataset 는 TDataset 의 프로퍼티와 메소드를 모두 상속하고, 표 12.15과 12.16에 표시된 추가 항목들 중 일부를 구현하기도 한다.
프로퍼티 | 설명 |
Filename | 데이터를 저장하거나 로딩시켜야 하는 대상 파일명. |
FileModified | 파일이 로딩된 후 데이터가 변경되었는가? |
표 12.15: TMemDataset의 추가 프로퍼티 |
메소드 | 설명 |
Clear | 데이터를 삭제하고, 필드 정의 리스트를 선택적으로 삭제한다. |
SaveToFile | 데이터와 필드 정의를 파일에 저장한다. |
LoadFromFile | 데이터와 필드 정의를 파일로부터 로딩한다. |
SaveToStream | 데이터와 파일 정의를 스트림에 저장한다. |
LoadFromStream | 데이터와 필드 정의를 스트림으로부터 로딩한다. |
CreateTable | fielddefs 프로퍼티의 내용을 바탕으로 데이터세트를 생성하고 연다. |
CopyFromDataset | 다른 데이터세트 인스턴스로부터 구조, 그리고 선택적으로 데이터를 복사한다. |
표 12.16: TMemDataset의 메소드 |
TMemDataset를 사용하려면 아래 단계를 따라야 한다:
- 테이블 구조를 (메모리 레이아웃) 정의해야 한다. 이는 FieldDefs 집합체(collection)에 항목을 추가함으로써 실행된다: 각 항목은 테이블 내 필드를 나타낸다. FieldDefs 프로퍼티는 IDE의 집합체 에디터를 이용해 편집 가능한 일반 집합체이므로 코드 또는 IDE에서 이를 실행할 수 있다. 각 필드마다 최소한 DataType, Size, Name은 적절한 값으로 설정해야 한다.
- CreateTable을 호출해야 한다. 이를 호출 시 FieldDefs 집합체에 정의된 바와 같이 데이터를 관리하는 데에 필요한 메모리 구조를 생성하고, 데이터세트를 열 것이다. CreateTable은 IDE의 컴포넌트 메뉴를 통해서 (컴포넌트를 오른쪽 마우스로 클릭하여 Create dataset를 선택), 혹은 런타임 시에 호출 가능한데, 가령 데이터세트가 드롭된 폼의 OnCreate 이벤트 핸들러를 통해 호출할 수 있다. 데이터를 편집하고 나면 SaveToFile 명령으로 테이블 구조와 데이터를 파일로 저장할 수 있다. FieldDefs 집합체의 내용과 실제 데이터가 디스크로 작성될 것이다.
데이터는 LoadFromFile 메소드를 이용해 언제든지 로딩할 수 있다. 이는 디스크로부터 FieldDefs 집합체를 읽어올 것이며, CreateTable의 호출은 파일 내 모든 데이터를 메모리로 읽어올 것이다. 열린 데이터세트 또는 지속성 필드가 있는 데이터세트를 이용할 수 있는 경우, CopyFromDataset 메소드를 이용해 그 구조를 TMemDataset 인스턴스의 FieldDefs 프로퍼티로 복사할 수 있다. 선택적으로, 열린 데이터세트 내 데이터는 TMemDataset 인스턴스의 메모리 버퍼로 복사할 수도 있다.
CSV 데이터
CSV (콤마로 구분된 값) 데이터는 데이터 전송에 일반적으로 사용되는 포맷이다. 모든 CSV-구조 데이터는 아래와 비슷한 모양을 한다:
FieldOne,"Field two, which also has commas.",12345,"Field4"
"Field1","Field 2 in the second record.",,"SingleWordFieldFour"
모든 필드는 콤마로 구분된다. 하나의 단어로 된 텍스트 필드는 물론 필수는 아니지만 큰 따옴표로 묶을 수 있다. 이후 CSV 파서가 비텍스트 필드로부터 텍스트를 구별할 수 있으므로 큰 따옴표의 사용을 권한다.
이는 특히 Microsoft Excel로 불러올 때 중요하다. 수치형 필드는 보통 따옴표로 닫지(quoted) 않는다. 필드가 텍스트 값 내에 콤마를 포함한 경우에도 따옴표 문자로 묶어야 한다. 빈(NULL) 필드는 빈 채로 유지된다. 파일 내 각 행은 데이터세트에서 각 레코드를 의미한다. 포맷을 엄격하게 정의하지 않으므로 콤마 대신 세미콜론이나 탭 문자를 필드 구분자(field separator)로 이용할 수도 있다. 사용되는 문자는 어떤 애플리케이션이 파일을 생성했느냐에 따라 달라진다. 일부 애플리케이션은 파일의 첫 행으로 필드 이름을 쓴다. 이는 확실히 보장할 순 없지만 데이터의 해석이 쉬워지므로 사용을 권장한다.
라자루스는 이를 처리하도록 TDataset의 TSDFDataset 자손을 제공하는데, SDFLaz 패키지에 포함되어 있다. 해당 TDataset 자손은 사용 측면에서 TMemDataset 과 약간 다르다.
TSDFDataset 은 TDataset 에 비해 새 메소드를 도입하진 않았지만 CSV 파일을 설명하기 위해 일부 추가 프로퍼티를 제공한다:
TSDFDataset 컴포넌트로 작업할 때는 고려해야 할 점이 두 가지 있다: CSV 데이터가 있는 기존 파일을 이용할 수 있는 경우, 단순히 파일명 설정에 관한 문제이며, 어쩌면 FirstLineAsSchema 프로퍼티와 관련된 문제일지도 모른다. Active 프로퍼티를 True로 설정 시 파일로부터 데이터를 읽을 것이다. 첫 행이 스키마가 (예: 필드의 이름) 아닌 경우, 컴포넌트는 이름 집합을 생성할 것이다. 첫 행이 스키마인 경우, 그 스키마에 주어진 필드명이 사용될 것이다. 모든 필드는 타입 문자열이 된다는 사실을 명심한다.
CSV 파일이 존재하지 않는 상황도 있다. 이러한 경우, Schema 프로퍼티는 파일명으로 채워야 한다: 문자열리스트 내 행별로 하나의 파일명. FileMustExist 프로퍼티는 False로 설정되어야 한다. 파일이 아직 존재하지 않는 경우 Schema 프로퍼티에 이름을 이용해 문자열 필드가 생성될 것이다.
데이터세트가 닫히자마자 FileName 프로퍼티를 이용해 데이터에 일어난 변경내용을 파일로 작성할 것이다.
프로퍼티 | 타입 | 설명 |
Delimiter | 문자열형 | 각 행에서 필드를 구분하는 데에 사용되는 문자. 기본 값은 콤마(,)이다. |
FirstLineAsSchema | 부울형(Boolean) | TSDFDataset 에게 첫 번째 행을 필드명을 포함하는 행으로 취급할 것을 알린다. |
FileMustExist | 부울형 | 파일이 디스크 상에 위치해야 하며, 스키마를 이용해 새 파일을 생성해선 안 된다. |
FileName | 문자열형 | 'Open'을 호출할 때 읽어야 하는 파일명. |
Schema | Tstrings | 새 파일을 생성 시 사용해야 하는 필드명 리스트. |
표 12.17: TSDFDataset 프로퍼티 |
CSV 파일을 로딩하는 방법을 이해하기 위해선 필드 구분 문자의 값을 비롯해 첫 행이 필드명을 포함해야 하는지 여부도 알려져야 한다.
프로그래머가 TSDFDataset 컴포넌트를 이용해 CSV 파일을 생성한 경우라면 물론 필드 구분자로 어떤 문자를 이용했는지 알 것이다. 하지만 제3자의 파일을 열어야 하는 경우 구분 문자가 알려지지 않은 것이 보통이다. 아래 루틴은 구분 문자와 파일의 첫 행에 파일명 존재 여부를 모두 알려줄 것이다:
function DeterminSeparator(AFileName: String;
var HasFieldNames: Boolean): Char;
const Seps: array[1..5] of Char = (',', ';', #9, '@', '#');
var F : TextFile;
S, S2, T: String;
I : Integer;
begin
AssignFile(F, AFileName); // Open file and read first line
Reset(F);
try
ReadLn(F, S);
finally
CloseFile(F);
end;
Result := #0;
// Scan the line for the separator character:
I := 0;
while (Result = #0) and (I < 5) do
begin
Inc(I);
if Pos(Seps[i], S) <> 0 then Result := Seps[i]
end;
// Try and detect the presence of fieldnames:
// no spaces or double separator:
if Result <> #0 then
HasFieldNames := (Pos('', S) = 0) and
(Pos(Result + Result, S) = 0);
end;
파일을 열 때도 사용할 수 있다 (여기서 AFileName으로 명명):
var B: Boolean;
C: Char;
begin
C := DetermineSeparator(AFileName, B);
if C = #0 then Exit;
with CSV do
begin
Delimiter := C;
FirstLineAsSchema := B;
CBFirstLineASSchema.Checked := B;
FileName := AFileName;
Open;
end;
end;
데이터를 보고 어쩌다 수정하게 되면 이제 'SaveFileAs' 메소드로 디스크에 다시 쓸 수 있다:
CSV.SaveFileAs(CSV.FileName);
데이터세트를 열고나면 FirstLineAsSchema 프로퍼티를 변경할 수 없다: 데이터세트를 닫은 후 다시 열어야 효과가 나타난다.
데이터세트는 FirstLineAsSchema 프로퍼티를 설정하기 전에 닫아야 함을 명심하자. 데이터세트가 열려 있을 때 해당 프로퍼티를 설정할 경우 예외가 발생할 것이다. 데이터세트가 처음부터 열려 있을 경우, 프로퍼티를 설정한 후 다시 열어야 한다.
Procedure TMainForm.MINewClick(Sender : TObject);
begin
With SDCSV do
if Execute then
NewCSV(FileName);
end;
TSDFDataset 컴포넌트를 이용해 새 CSV 파일을 생성할 수 있다. 이는 주로 New 메뉴 항목의 OnClick 이벤트 핸들러에서 이루어진다:
새 CSV 파일을 생성하려면 4개의 프로퍼티를 알아야 한다: 파일명 (FileName 프로퍼티에), 필드명 (Schema 프로퍼티에), 파일명을 첫 행에 써야 하는지 (FirstLineAsSchema 프로퍼티에), 마지막으로 필드 구분 문자가 (Delimiter 프로퍼티에) 된다. 이러한 프로퍼티들을 설정하면 파일을 생성할 수 있다. FileMustExist 프로퍼티는 False로 설정되어야 컴포넌트가 기존 파일을 읽어오지 않도록 막을 수 있다:
CSV.Close;
CSV.Schema.Assign(Schema);
CSV.Delimiter := ',';
CSV.FirstlineAsSchema := True;
CSV.FileName := AFileName;
CSV.FileMustExist := False;
CSV.Open;
'Open' 메소드는 데이터세트를 생성하기 위한 작업을 모두 실행할 것이다.
dBase 파일에 접근하기
dBase 파일은 길이가 고정된 레코드를 가진 오래된 데이터 포맷이다. 데이터는 메인 (.dbf) 파일에 바이너리 포맷으로 저장되고, 색인은 구분된 파일에 저장된다. 이들은 그 이름도 오래된 dBase 프로그램에서 소개된 후에 FoxPro와 Visual FoxPro에서 사용을 이어왔다. 내부 구조는 dBase 엔진이 완전히 처리하므로 개발자는 필요한 필드의 이름, 타입, 크기만 알면 된다.
이 데이터 포맷은 이제 오래된 유물에 불과하지만 full-blown SQL 엔진으로 접근할 필요가 없는 작은 데스크톱 애플리케이션에는 전적으로 적합하다. 두 개 또는 세 개의 연결된 테이블 간에 Master-detail 관계를 처리해야 하는 상황에서도 dBase 파일을 사용할 만한 가치가 있는 것이, 이를 처리할 때 외부 코드나 라이브러리를 필요로 하지 않기 때문이다.
프로퍼티 | 설명 |
DateTimeHandling | 델파이 호환 프로퍼티로, 라자루스에서 dtDatetime 으로 설정되어야 한다. |
Exclusive | 해당 부울 프로퍼티는 TDBF 컴포넌트에게 파일로 독점적인 접근성을 얻고자 시도해야 하는지를 알려준다. 새 TDBF 파일을 생성하거나 색인을 조작 시 필수다. |
FilePath | 테이블이 위치하는 디렉터리이다 (현재 작업 디렉터리를 기준으로). |
FilePathFull | 테이블이 위치한 절대 경로이다. 예를 들어, 애플리케이션의 위치가 변경될 때 설정할 수 있겠다. |
Filter | 여기서는 필터 표현식을 입력할 수 있다. |
Filtered | 필더 표현식을 데이터로 적용해야 하는 경우 True로 설정한다 (그 외의 경우는 False로). |
IndexDefs | 해당 dBase 파일에 정의된 Indexes를 포함한다. |
Indexes | IndexDefs와 동일하게(alias) 사용된다. |
IndexFieldNames | 현재 색인의 파일명이다. 해당 프로퍼티는 간단하고, 임시적인 색인을 구성하는 데 사용 가능하며, 명시된 필드별로 데이터세트를 정렬(order)할 것이다. |
IndexName | 현재 활성화된 색인의 이름이다. indexDefs 프로퍼티에 있는 색인 중 하나여야 한다. |
MasterFields | 데이터세트 내 MasterSource가 가리키는 필드의 이름이다. 해당 필드의 이름과 값을 이용해 .DBF 데이터세트에 필터를 구성할 수 있다. |
MasterSource | Master-detail 관계를 구성하는 데 사용될 데이터소스이다. |
OpenMode | 파일이 사용되는 방식을 결정한다. 일반 파일 (omNormal) 또는 자동 생성으로 (omAutoCreate) 처리 가능하다. 예: 파일이 존재하지 않을 경우 파일이 생성될 것이다. 이후에 FieldDefs 프로퍼티를 사용해 파일의 구조를 결정할 수 있다. |
ShowDeleted | 설정 시, dBase 파일에서 삭제된 것으로 표시되는 레코드가 여전히 표시된다. |
Storage | 값으로는 stoFile로 설정되어, 데이터가 파일에 보관됨을 의미한다. stoMemory로 설정 가능하며, 이 경우 데이터가 메모리에 보관된다. |
TableLevel | 3, 4, 7, 25 중 하나가 될 수 있다. 3, 4, 7 level은 각각 dBase III, IV, VII 파일 포맷에 해당한다. Level 25는 Foxpro 파일에 사용된다. |
TableName | dBase 파일의 이름이다. 설정 시, 파일명의 경로 부분이 잘려 나가고 FilePathFull 프로퍼티로 이동한다. 이러한 프로퍼티들 대다수는 이 컴포넌트의 일반 사용에 필요하지 않다. TableName 프로퍼티의 설정만으로 dBase 파일을 열고 보는 데에는 충분하다. |
표 12.18: TDBF 컴포넌트 프로퍼티 |
TDBF 컴포넌트는 본래 델파이용으로 작성되었지만 프리 파스칼의 첫 데이터베이스 컴포넌트 중 하나로 프리 파스칼에 이식되었다. .DBF 파일의 브라우즈에 사용 가능하며, 데이터베이스에서 레코드 순서를 결정 시 사용 가능한 색인 파일도 지원한다. 표현식을 바탕으로 색인을 생성하는 것도 가능하다. TDBF 는 필터링을 양호하게 지원하며, 데이터세트 간 master-detail 관계의 구성도 지원한다. 해당 컴포넌트를 이용해 새 dBase 파일을 생성할 수도 있다. TDBF 컴포넌트의 지원은 DBFlaz 패키지에 위치한다. 컴포넌트 자체는 프리 파스칼 배포판에 포함되어 있다. 해당 패키지의 컴파일과 설치 시 컴포넌트 팔레트의 Data Access 페이지에 TDBF 컴포넌트가 등록될 것이다.
기존 dBase 파일 열기
TDBF 컴포넌트는 그 기능이 많음에도 불구하고 사용이 매우 쉽다. 폼에 (또는 데이터모듈에) 컴포넌트를 드롭하여 파일 선택 대화창이 뜨면 TableName 프로퍼티를 .DBF 파일의 이름으로 설정한다. 그리고 Active 프로퍼티를 설정하면 파일이 열리고 레코드가 표시된다.
표 12.18에 열거된 프로퍼티들은 파일을 어떻게 여는지, 파일 내용을 어떻게 처리하는지에 영향을 미친다. 이 프로퍼티 대다수는 컴포넌트의 일반적인 사용에는 필요하지 않다. TableName 프로퍼티의 설정만으로 dBase 파일을 열고 보기에 충분하다.
dBase 파일로 접근하기 위한 단계별 지침은 아래와 같다:
- Data Access 탭의 TDBF 를 폼에 드롭한다.
- TDatasource를 폼에 (Datasource1) 드롭하고, 그것의 Dataset 프로퍼티를 DBF1로 설정한다.
- Data Controls 탭에서 TDBGrid 와 TDBNavigator 컴포넌트를 폼에 드롭한다.
- DBGrid1과 DBNavigator1의 Datasource 프로퍼티를 Datasource1로 설정한다.
- 오브젝트 인스펙터에서 TableName 프로퍼티를 dBase 파일로 설정한다 (FilePath와 FilePathFull이 자동으로 설정된다).
- DBF1 컴포넌트의 Active 프로퍼티를 True로 설정한다. 이는 테이블 데이터를 로딩하여 격자에 표시할 것이다-IDE에서.
어떤 dBase 파일도 UTF-8 포맷으로 인코딩되지 않는다. 라자루스는 내부에서 유니코드로만 작업한다. dBase 7부터 Borland는 Borland 데이터베이스 엔진(BDE)에 Windows ANSI 문자열을 사용하는데, 윈도우에서는 제어판의 BDE Administrator 엔트리에서 이용할 수 있겠다. 기본 값으로 Windows ANSI 문자 집합과 dBase 버전 7을 제시한다. 해당 설정은 데이터베이스 드라이버를 사용하는 모든 프로그램에서 유효하다. 오래된 버전, 특히 dBase III/II+와 IV는 DOS 프로그램으로, DOS 문자 집합을 사용하였다.
특히 이러한 dBase 버전의 데이터 포맷에서도 똑같다. Dbase III와 dBase IV 파일은 US ASCII 코드 페이지를 이용해 (US ASCII CP 437) 항상 DOS 파일로 취급되어야 한다. 이러한 dBase 포맷의 헤더에는 문자 집합을 지정하는 엔트리가 없다. 문자 집합이 올바르지 않게 해석된 경우, 특수 문자가 (독일어 움라우트와 같은) 손실되어 과정에서 데이터 파일을 사용할 수 없게 된다. 델파이와 작업 시 발생할 수 있는 또 다른 문제가 되겠다.
DOS로 인코딩된 테이블을 올바르기 표시하고 조작하기 위해서는 Open을 호출한 후에 두 가지 문자 해석을 해야 한다.
- 모든 필드의 Transliterate 프로퍼티를 True로 설정한다.
- TDBF 컴포넌트의 OnTranslate 이벤트 핸들러를 구현한다. 아래 메소드는 첫 번째 단계를 실행할 것이다:
procedure TMainForm.SetTransliterate;
var I: Integer;
begin
for I := 0 to DBFFile.Fields.Count-1 do
if DBFFile.Fields[I] is TStringField then
TStringField(DBFFile.Fields[I]).Transliterate := True;
end;
TDataset 구현 자체가 문자 변환(transliteration)을 실행하지는 않는다. 이는 TDBF 컴포넌트의 표준 OnTranslate 이벤트에서 구현되어야 하는데, 아래와 같은 모습이다:
function TMainForm.DBFFileTranslate(Dbf: TDbf; Src, Dest: PChar;
ToOem: Boolean): Integer;
var
S: String; L: Integer;
begin
S := StrPas(Src);
L := Length(S);
if L > 0 then begin
S := ConvertEncoding(S, 'CP437', EncodingUTF8);
if Length(S)>L then SetLength(S, L);
Move(S[1], Dest^, L + 1);
end;
Result := L;
end;
위의 루틴은 널 종료 Src 파라미터에서 문자열을 취하여 Dest 문자열로 변환한다. ConvertEncoding 호출은 LCL의 일부로 (lconvencoding 유닛에 위치), 코드페이지 437에서 라자루스가 사용하는 UTF-8 인코딩으로 변환할 것이다. 길이와 함께 추가로 1 바이트가 결과 버퍼로 이동되는데 종료 NULL 문자 또한 이동해야하기 때문이다.
새 dBase 파일 생성하기
새 dBase 파일을 생성하는 방법에는 두 가지가 있다. 첫 번째이자 가장 간단한 방법은 IDE에서 거의 완전히 실행될 수 있다:
- TDBF 컴포넌트의 FieldDefs 프로퍼티를 각 필드에 대한 항목으로 채운다. TDBF 가 TDataset의 기존 필드 타입을 모두 지원하지는 않음을 명심한다.
- 선택적으로 TDBF 컴포넌트의 IndexDefs 프로퍼티를 색인 정의로 채운다.
- Exclusive 프로퍼티를 True로 설정한다.
- TableName 프로퍼티를 요구되는 파일명으로 설정한다.
그리고 난 후, 런타임 시에 CreateTable 메소드를 호출하면 새 DBF 파일을 생성할 수 있다.
대안 방식으로 TdbfFieldDefs 집합체를 TdbfFieldDefItems 로 채우는 방식이 있다. 이는 DBF에서 FieldDefs 프로퍼티에 해당하지만 TDbfFieldDef 클래스에는 DBF 파일의 구조에 더 정밀한 제어를 제공하는 추가 프로퍼티들이 있다. TdbfFieldDefs 집합체는 이후에 CreateTableEx 메소드로 전달되어 집합체 내 정의에 따라 DBF 파일을 생성 가능하다. 사실 내부적으로 CreateTable 메소드는 CreateTableEx 메소드를 호출하는데, 이것은 FieldDefs 메소드에서 찾을 수 있는 항목들로부터 TdbfFieldDefs 집합체를 구성한다.
SQL 데이터베이스
SQLDB는 SQL가 활성화된 데이터베이스와 통신하는 데에 사용할 수 있는 프리 파스칼의 컴포넌트 집합 이름이다. SQLDB는 각 이용 가능한 데이터베이스로 접근하기 위해 네이티브 클라이언트 라이브러리를 이용함으로써 작동한다. 이로 인해 빠른 작업과 메모리 효율적인 작업이 가능하다. 뿐만 아니라 ODBC를 지원하는 어떤 데이터베이스로든 연결이 가능하다. ODBC 계층은 기본 값으로 윈도우에서 이용 가능하며, 리눅스나 맥(Mac) 운영체제에 쉽게 설치가 가능하다.
SQLDB 지원은 라자루스에서 SQLDBLaz 패키지를 이용해 설치된다. 이는 IDE에 이용 가능한 컴포넌트와 일부 프로퍼티 에디터를 등록한다 (실제 코드는 프리 파스칼에 존재). 모든 컴포넌트는 컴포넌트 팔레트의 SQLDB 페이지에 설치된다. 모든 연결 컴포넌트는 지원 라이브러리를 동적으로 로딩하므로, 특정 데이터베이스 엔진이 설치되어 있지 않더라도 컴포넌트를 설치하여 데이터모듈로 드롭할 수 있다.
연결이 활성화되면 SQLDB는 필요로 하는 적절한 클라이언트 라이브러리를 로딩할 것이다. 클라이언트 라이브러리가 설치되지 않은 경우에는 물론 오류로 이끌 수 있다. 최근에 SQLDB는 Firebird, Interbase, MySQL 4.0, 4.1, 5.0 버전, Oracle, PostgreSQL, SQLLite로 직접 접근을 제공한다. 그 외 데이터베이스는 ODBC 연결로 접근할 수 있겠다. SQLDB를 이용해 데이터베이스로 접근하려면 최소 3가지 컴포넌트가 필요한데, 이를 표 12.19에 소개하겠다.
컴포넌트 | 설명 |
TSQLConnection | 혹은 사실상 TSQLConnection 의 자손. 데이터베이스로의 실제 연결을 표현하고, 연결에 필요한 모든 데이터는 이 곳에 입력되어야 한다. 주로 원하는 데이터베이스 연결의 이름과 함께 사용자 이름과 비밀번호가 해당된다. |
TSQLTransaction | 해당 컴포넌트는 트랜잭션을 제어 시 사용된다. 애플리케이션은 동시에 다수의 독립 트랜잭션을 활성화시킬 수 있다. 네이티브 엔진이 이를 지원하지 않을 시, 동일한 데이터베이스로 다중 연결을 개방하여 가상 실행(emulate)할 수 있다-데이터베이스가 per-connection license scheme(연결별 라이센스 기법)을 사용할 경우 이러한 사실을 인식해야 한다. |
TSQLQuery | 이것은 TDataset 자손으로, SELECT 또는 다른 쿼리를 실행 시 사용할 수 있다. SQLDB 기반의 애플리케이션의 중심 컴포넌트이다. 한 번에 하나의 SQL 문을 실행할 수 있다. |
표 12.19: SQLDB를 사용 할때 필요한 컴포넌트 |
TSQLConnection 은 지원되는 데이터베이스마다 하나의 자손을 가져 총 여러 자손을 가진다.
- TIBConnection Firebird 또는 Interbase 데이터베이스로 연결하기 위해.
- TMySQL40Connection, TMySQL41Connection, TMySQL50Connection
MySQL 클라이언트 라이브러리 버전 4.0, 4.1, 5.0 을 각각 이용해 MySQL 데이터베이스로 연결하기 위해. - TOracleConnection Oracle 데이터베이스로 연결하기 위해.
- TODBCConnection ODBC 라이브러리를 이용해 데이터베이스로 연결하기 우해.
- TPQConnection PostGreSQL 데이터베이스로 연결하기 위해
- TSQLite3Connection SQLite3 데이터베이스로 연결하기 위해
MySQL 데이터베이스의 경우 연결 컴포넌트는 서버 버전이 아니라 설치된 클라이언트 라이브러리에 따라 좌우됨을 명심한다. 일반적으로 4.0 클라이언트 라이브러리는 5.0 서버로 연결 가능하다.
데이터베이스의 타입을 다른 타입으로 바꾸고 싶은 경우에는 연결 컴포넌트를 바꾸기만 하면 된다. 이는 데이터베이스의 구조가 그대로 남아 있고, 데이터베이스 특정적인 기능이 사용되지 않은 경우에만 가능하다.
TSQLConnector 컴포넌트를 이용해 이를 피하는 방법도 있다. 해당 컴포넌트는 TSQLConnection 자손으로, 실제 연결 컴포넌트에 프록시 역할을 한다: 스스로는 어떤 것도 실행하지 않고 위에 언급한 TSQLConnection 자손 중 하나를 생성하고, 단순히 모든 프로퍼티와 메소드 호출을 해당 TSQLConnection 인스턴스로 전달한다. 해야 할 일은 TSQLConnector의 ConnectorType 프로퍼티를 적절한 연결 타입으로 설정하는 것뿐이다.
위에 제시한 3가지 기반 컴포넌트는 (또는 그 자손들은) 밀접한 연관성을 가진다: TSQLConnection 자손은 기본 트랜잭션 컴포넌트를 참조하고, 실행 중인 쿼리와 데이터에 관한 메타데이터를 수집하는 데 사용된다. Transaction 프로퍼티는 기본 트랜잭션을 의미한다. TSQLTransaction은 트랜잭션을 관리하는 TSQLConnection 컴포넌트를 가리킨다. Database 프로퍼티는 이 참조를 포함한다. TSQLQuery 컴포넌트는 TSQLConnection 컴포넌트를 참조하여 (Database 프로퍼티를 통해) 데이터베이스로의 연결을 관리한다. 또한 TSQLTransaction 컴포넌트도 참조하여 (Transaction 프로퍼티를 통해) 그것이 작동 중인 트랜잭션을 관리한다. 설정해야 하는 프로퍼티는 이로써 총 4개가 된다. 다행히도 컴포넌트가 자동으로 프로퍼티를 설정하기도 한다: TSQLConnection은 자동으로 연결되는 첫 번째 TSQLTransaction 을 기본 트랜잭션으로서 이용한다. 유사하게, TSQLQuery 컴포넌트의 Transaction 프로퍼티를 설정되지 않았을 때 값을 설정하면 Database 프로퍼티를 설정할 것이며, 반대의 경우도 적용된다.
SQL 데이터베이스로 연결하기
SQLDB를 이용해 Firebird 데이터베이스로 연결 시에는 아래 단계를 거친다:
- File ▷ New 대화창을 이용해 새 데이터모듈을 생성한다.
- 컴포넌트 팔레트의 SQLDB 탭에서 TIBConnection 을 데이터모듈로 드롭한다. 이는 IBConnection1 으로 명명되고, Firebird 또는 Interbase 데이터베이스로 연결을 구축하도록 허용한다.
- 데이터모듈에 TSQLTransaction 컴포넌트를 드롭한다. Database 프로퍼티는 IBConnection1으로 설정되어야 한다. IBConnection1 컴포넌트의 Transaction 프로퍼티는 자동으로 SQLTransaction1 으로 설정될 것이다.
- 데이터모듈에 TSQLQuery 컴포넌트를 드롭한다. 이는 SQLQuery1으로 명명될 것이다. 그 Database 프로퍼티도 IBConnection1으로 설정되어야 한다. 다시 SQLQuery1 컴포넌트의 Transaction 프로퍼티는 자동으로 SQLTransaction1으로 설정될 것이다.
컴포넌트를 데이터모듈로 (또는 폼으로) 드롭하고 적절하게 연결하고 나면 그 프로퍼티들을 설정할 차례다. TSQLConnection 자손들의 경우에 최소한으로 설정해야 하는 프로퍼티들을 표 12.20에 소개하겠다.
프로퍼티 | 설명 |
Hostname | 데이터베이스가 위치하는 호스트명. 데이터베이스가 현재 머신에 위치한 경우 값을 비워놓을 수 있다. |
DatabaseName | 데이터베이스를 식별할 수 있는 이름. 경로 (SQLite 또는 Firebird에서와 같이) 또는 기호 이름(MySQL 또는 Oracle에서와 같이)이 가능하다. 실제 연결에 따라 좌우된다. |
UserName | 연결해야 할 사용자명. |
Password | 사용자의 비밀번호. |
표 12.20: TSQLConnection 자손들의 프로퍼티 |
각 TSQLConnection 자손은 자손 특정적인 프로퍼티들을 가지는데, 연결을 제어하기 위해 추가로 설정 가능하다. 예를 들어 TIBConnection 은 SQLDialect와 Charset 프로퍼티를 설정하도록 해준다.
이러한 프로퍼티들을 모두 올바르게 설정하고 나면 Connected 프로퍼티를 True로 설정하여 데이터베이스로 연결할 수 있겠다. 무언가가 잘못되면 그림 12.13과 같이 예외 메시지와 함께 대화창이 뜬다.
런타임 시 Open과 Close 메소드는 데이터베이스로 연결을 열거나 닫을 때 사용할 수 있다. 연결이 성공적으로 이루어지면 TSQLQuery 컴포넌트를 구성할 수 있다. 메인 프로퍼티는 SQL 문을 입력할 수 있는 TString 타입의 프로퍼티인 SQL 프로퍼티가 해당한다. 적절한 선택 쿼리를 입력하고 나면 TSQLQuery 컴포넌트의 Active 프로퍼티를 True로 설정하여 쿼리를 실행하고 결과를 가져온다. 결과는 IDE에 표시할 수 있는데, 이런 경우 TDatasource 컴포넌트를 데이터모듈에 추가하고, TDBGrid 를 폼에 드롭 후 그 DataSource 프로퍼티를 설정함으로써 데이터모듈에 데이터소스로 가리키도록 한다. 그 결과는 그림 12.14의 모습과 같다.
SQL 선택 쿼리
SQLDB는 SQL 데이터베이스 지향적으로, SQL 문들만 모든 데이터의 소스로서 사용할 수 있으며, 테이블의 내용을 표시하는 특수 컴포넌트가 없음을 의미한다. 일반적으로는 모든 테이블 데이터를 표시하는 건 불필요하다. 테이블이 많은 데이터를 포함한 경우 모두 로딩 시 상당량의 메모리를 소모할 것이기 때문이다. 데이터를 편집하면서 테이블 구조가 변경될 경우 예상치 못한 오류를 야기할지도 모른다. 더 중요한 것은, 테이블 데이터를 모두 표시하는 방법은 그다지 사용자 친화적이지 않다는 점이다-필터링된 레코드 집합이 훨씬 낫다. 그럼에도 불구하고 때로는 테이블에 모든 레코드를 표시해야 하는 경우가 있다. 이는 SQL 선택문의 결과를 표시하는 것과 근본적으로 다르지 않다:
- 테이블이 위치한 데이터베이스에 대해 TSQLConnection 자손을 구성한다.
- TSQLTransaction 컴포넌트를 연결 컴포넌트로 연결한다.
- 앞의 두 컴포넌트로 연결된 TSQLQuery 컴포넌트에서 아래의 SQL 문을 이용해 테이블의 모든 데이터를 선택할 수 있다:
SELECT * FROM MyTable;
MyTable에 실제 테이블명을 대입해야 하는 것은 당연하다. 이를 실행하고 나면 TSQLQuery 는 TDBF 컴포넌트와 같은 역할을 한다: 데이터를 편집, 정렬 또는 삭제할 수 있게 된다. 유일한 차이는 변경내용을 데이터베이스로 쓰기 위해 ApplyUpdate 를 호출해야 한다는 점이다.
데이터 업데이트하기
TSQLQuery 데이터세트 내 데이터는 편집이 가능하므로, 데이터를 삭제, 삽입 또는 업데이트할 수 있음을 의미한다. 간단한 경우 (예: 단일 테이블에서 필드를 선택 시), TSQLQuery 는 테이블에서 데이터를 업데이트하는 방법을 알고, 필요한 문(statement)들을 스스로 구성하고 실행할 것이다. 이는 Post 호출이 실행될 때 즉시 실행되거나 Update 문을 버퍼하여 한 번에 모두 실행할 수 있다. 두 가지 방법 중 선택은 프로그래머에게 달려 있다. SQL 문이 버퍼되고, ApplyUpdates 메소드가 호출될 때에만 실행된다. Post 연산 직후에 항상 업데이트를 적용하려면 아래 코드를 TSQLQuery 컴포넌트의 AfterPost 핸들러에 삽입한다:
procedure TDMParams.SQLQuery1AfterPost(DataSet: TDataSet);
begin
SQLQuery1.ApplyUpdates;
end;
INSERT 문을 생성하려면 실행해야 무엇을 해야 할지가 분명하므로 TSQLQuery 컴포넌트는 프로그래머의 도움을 필요로 하지 않는다. 하지만 DELETE와 UPDATE 문에서는 변경해야 하는 레코드를 유일하게 식별하도록 WHERE 절을 구성해야 한다. 여기서 TSQLQuery 가 도움을 필요로 하는 경우가 종종 있다. 실행될 SQL 문의 WHERE 절을 UsePrimaryKeyAsKey 프로퍼티와 UpdateMode 프로퍼티를 이용해 제어할 수 있다. UpdateMode 는 표 12.21 에 실린 3가지 값이 가능하다.
값 | 설명 |
upWhereAll | WHERE 문은 ProviderFlags 프로퍼티에서 pfInWhere 가 설정된 필드에 대한 모든 기존(old) 필드 값을 포함할 것이다. 즉, 다른 사용자가 동시에 레코드를 변경 시, 업데이트문이 실패할 것을 의미한다. |
upWhereChanged | WHERE 문은 ProviderFlags 프로퍼티에서 pfInWhere 가 설정된 모든 변경된 필드의 기존(old) 값만 포함할 것이다. upWhereAll 옵션보다 덜 엄격하다. |
upWhereKeyOnly | 제약이 가장 적은 옵션이다. WHERE 절은 데이터세트 내 키 필드에 대한 조건을 포함할 것이다. 키 필드는 ProviderFlags 프로퍼티에서 pfInkey가 설정된 필드를 나타낸다. |
표 12.21: TSQLQuery의 UpdataMode 프로퍼티에 가능한 값 |
기본 값으로, 모든 필드는 ProviderFlags에 pfInWhere를 설정한다. 일부 필드에 대한 플래그를 제거함으로써 문을 조정할 수 있다. UpdateMode 에 대한 기본 값은 upWhereAll 이지만 변경할 것을 권장한다. pfInkey 플래그는 업데이트문에 대한 기본 키를 형성해야 하는 필드에 대해 수동으로 설정하도록 해준다. 하지만 데이터를 인출한 테이블의 주요 색인을 검사함으로써 TSQLQuery 컴포넌트는 어떤 필드의 pfInKey 플래그를 설정해야 하는지 알아볼 수 있다. UsePrimaryKeyAsKey 프로퍼티를 True로 설정 시 (기본 값), 이러한 메커니즘을 활성화할 수 있다. False로 설정할 경우, pfInKey 플래그는 그것을 필요로 하는 모든 필더에 수동으로 설정해야 한다. 분명한 것은 IDE에서 이를 가능하게 하려면 지속성 필드가 필요하다는 사실이다.
어떤 때는 위의 프로퍼티로는 TSQLQuery 가 올바른 업데이트 쿼리를 구성하는 데에 충분하지 않다. 이런 경우 필요한 SQL 문을 수동으로 명시할 수 있는데, 표 12.22에 열거된 프로퍼티를 이용하면 되겠다.
프로퍼티 | 설명 |
DeleteSQL | 데이터베이스에서 현재 레코드를 삭제 시 필요한 SQL문. |
InsertSQL | 데이터베이스에 현재 레코드를 삽입 시 필요한 SQL문. |
UpdateSQL | 현재 레코드를 새 값으로 업데이트 시 필요한 SQL문. |
표 12.22: SQL 업데이트문을 명시하는 프로퍼티 |
세 가지 SQL 문 모두 파라미터를 사용해야 한다. 파라미터는 데이터세트에 동일한 이름으로 된 필드들을 스캔하여 새 값으로 채울 수 있다. SQL문에 필드의 기존(old) 값을 사용하려면 파라미터의 이름 앞에 OLD_를 붙인다: 그러한 파라미터의 값은 필드의 기존 값으로 대체될 것이다.
Master/detail 관계
파라미터는 SQLDB에서 중요한 개념이다: 자주 반복되는 쿼리를 파라미터화하는 데 사용할 수 있고, SQL UPDATE 문을 생성 시에 사용되어야 한다. 또한 SQLDB를 이용해 master-detail 관계를 구성 시에도 중요하다. Master-detail 관계를 구성하기란 매우 쉽다. Detail 쿼리는 소수의 프로퍼티를 포함해야 한다. 이러한 파라미터들의 값은 master 데이터세트에서 인출될 것이다. Master 데이터세트는 Dataousrce 프로퍼티에서 설정 가능하다. 이 프로퍼티를 설정하고 나면, 명시적으로 설정되지 않은 파라미터 값은 해당 데이터소스로 연결된 데이터세트로부터 인출될 것이다. 파라미터와 동일한 이름으로 된 필드가 검색되고, 그 필드의 현재 값이 파라미터의 값으로 사용될 것이다. SELECT 쿼리를 열 때, 혹은 ExecSQL 을 이용해 다른 SQL 문을 실행할 때에도 마찬가지다.
게다가 master 데이터세트 내 현재 레코드가 변경되는 즉시 detail 데이터세트는 닫히고, 채워진 파라미터에 대해 새 값을 이용해 다시 열린다. 이 모든 것은 단 한 줄의 코드도 없이 실행 가능하다.