| history | TOP |
2006/09/10:作成
2006/09/13:NULL許可のフィールドを空文字列許可に強制変更するためのメソッド追加
2006/09/14:同じ条件でデータベースを開きなおすメソッド追加
| download | TOP |
DatabaseIO.zip(5,012bytes)※ソースコードです。
| overview | TOP |
SELECT操作ができないまま、長い間ほったらかしにしていましたが、データベースを使ってみようか、と思い立ったのでついでに続きを書きました。
ひょっとしたら前よりも機能ダウンしているかも知れません。ベースになるクラスを汎用的に見える感じで書いていますが、mdb専用に変わりありません。
できること
言い訳
| code | TOP |
//=============================================================================
// BlueLeaf1336 http://www.geocities.jp/fjtkt/
//-----------------------------------------------------------------------------
// 2006.09.09 作成
// 2006.09.10 TDatabaseJet.Open で mdb がなければ作成するようにした
// TDatabaseJet.ChangePassword 追加(失敗対策適当)
// TDatabaseJet.CompactDatabase 追加
//=============================================================================
{
参考
Collection.Count
Collection.Item[i]
Collection = Tables, Columns, Fields, Properties, etc.
Table = Catalog.Tables[i]
Table.Columns
Table.Name
Table.DateCreated
Table.DateModified
Table.Type
Column = Table.Columns[i]
Column.Name
Column.Type
Column.Attributes
Column.DefinedSize
Column.NumericScale
Column.Precision
Field = RecordSet.Fields[i]
Columnの全部
Field.Value
Field.ActualSize
Object.Properties
Object = Table, Column, Field, etc.
Property = Object.Properties[i]
Property.Name
Property.Value
}
unit DatabaseIO;
interface
uses
Windows, SysUtils, Classes, ComObj, Variants, SyncObjs;
type
// ADO接続によるデータベースクラス
TDatabase = class(TObject)
protected
FCatalog: OleVariant;
FDatabase: OleVariant;
FUser: String;
FPass: String;
FDatabaseName: String;
FOpened: Boolean;
protected
function BuildConnectionString(const AUser, APass, ADatabaseName: String): String; virtual; abstract;
public
constructor Create();
destructor Destroy(); override;
property User: String read FUser;
property Pass: String read FPass;
property DatabaseName: String read FDatabaseName;
property Opened: Boolean read FOpened;
public
function Open(const AUser, APass, ADatabaseName: String; var Err: String): Boolean; virtual;
procedure Close();
function BeginTrans(var Err: String): Boolean; overload;
function CommitTrans(var Err: String): Boolean; overload;
function RollbackTrans(var Err: String): Boolean; overload;
function BeginTrans(): Boolean; overload;
function CommitTrans(): Boolean; overload;
function RollbackTrans(): Boolean; overload;
function Execute(const AQuery: String; var Err: String): Boolean; overload;
function Execute(const AQueries: TStrings; var Err: String): Boolean; overload;
public
procedure GetTableNames(AList: TStrings);
procedure GetTableFields(const ATableName: String; AList: TStrings);
property Catalog: OleVariant read FCatalog;
property Database: OleVariant read FDatabase;
public
function OpenQuery(const AQuery: String; var ASet: OleVariant; var Err: String): Boolean;
procedure CloseQuery(var ASet: OleVariant);
procedure MoveNext(const ASet: OleVariant);
function IsEOF(const ASet: OleVariant): Boolean;
function IsBOF(const ASet: OleVariant): Boolean;
function IsXOF(const ASet: OleVariant): Boolean;
function FieldCount(const ASet: OleVariant): Integer;
function FieldName(const ASet: OleVariant; const AIndex: Integer): String;
function FieldValue(const ASet: OleVariant; const AIndex: Integer): Variant; overload;
function FieldValue(const ASet: OleVariant; const AName: String): Variant; overload;
end;
// JET-DB専用データベースクラス
TDatabaseJet = class(TDatabase)
protected
function BuildConnectionString(const AUser, APass, ADatabaseName: String): String; override;
public
function Open(const AUser, APass, ADatabaseName: String; var Err: String): Boolean; override;
function ChangePassword(const ANewPass: String; var Err: String): Boolean;
function CompactDatabase(var Err: String): Boolean;
end;
function Quote(const S: String): String;
function NVL(const V, ADefault: Variant): Variant;
var
CS: TCriticalSection = nil;
DB: TDatabase = nil;
implementation
//=============================================================================
// Utility
//-----------------------------------------------------------------------------
// 二重引用符で囲む
function Quote(const S: String): String;
begin
Result := AnsiQuotedStr(S, '"');
end;
//-----------------------------------------------------------------------------
// NULL対策
function NVL(const V, ADefault: Variant): Variant;
begin
Result := V;
if (VarIsNull(V)) then Result := ADefault;
end;
//-----------------------------------------------------------------------------
// テンポラリファイル名取得
// http://www.mesuttop.com/delphi/tip_files.htm
function TempFileName(const ADir: String): String;
var
LBuffer: array[0..MAX_PATH] of Char;
begin
FillChar(LBuffer, SizeOf(LBuffer), 0);
GetTempFileName(PChar(ADir), '!mdb', 0, LBuffer);
Result := String(LBuffer);
end;
//=============================================================================
// TDatabase
//-----------------------------------------------------------------------------
// コンストラクタ
constructor TDatabase.Create();
begin
inherited Create();
FCatalog := Unassigned;
FDatabase := Unassigned;
end;
//-----------------------------------------------------------------------------
// デストラクタ
destructor TDatabase.Destroy();
begin
Close();
inherited Destroy();
end;
//-----------------------------------------------------------------------------
// データベースに接続
function TDatabase.Open(const AUser, APass, ADatabaseName: String; var Err: String): Boolean;
begin
Close();
Result := False;
try
FCatalog := CreateOleObject('ADOX.Catalog');
FCatalog.ActiveConnection := BuildConnectionString(AUser, APass, ADatabaseName);
FDatabase := FCatalog.ActiveConnection;
FOpened := True;
FUser := AUser;
FPass := APass;
FDatabaseName := ADatabaseName;
Result := True;
except
on E:Exception do
begin
Err := E.Message;
Close();
end;
end;
end;
//-----------------------------------------------------------------------------
// データベースから切断
procedure TDatabase.Close();
begin
if (FOpened) then
begin
FDatabase.Close;
end;
FUser := '';
FPass := '';
FDatabaseName := '';
FDatabase := Unassigned;
FCatalog := Unassigned;
FOpened := False;
end;
//-----------------------------------------------------------------------------
// トランザクション処理
function TDatabase.BeginTrans(var Err: String): Boolean;
begin
Result := False;
try
FDatabase.BeginTrans;
Result := True;
except
on E:Exception do Err := E.Message;
end;
end;
//-----------------------------------------------------------------------------
function TDatabase.CommitTrans(var Err: String): Boolean;
begin
Result := False;
try
FDatabase.CommitTrans;
Result := True;
except
on E:Exception do Err := E.Message;
end;
end;
//-----------------------------------------------------------------------------
function TDatabase.RollbackTrans(var Err: String): Boolean;
begin
Result := False;
try
FDatabase.RollbackTrans;
Result := True;
except
on E:Exception do Err := E.Message;
end;
end;
//-----------------------------------------------------------------------------
// トランザクション処理(エラー内容不要版)
function TDatabase.BeginTrans(): Boolean;
var
LErr: String;
begin
Result := BeginTrans(LErr);
end;
//-----------------------------------------------------------------------------
function TDatabase.CommitTrans(): Boolean;
var
LErr: String;
begin
Result := CommitTrans(LErr);
end;
//-----------------------------------------------------------------------------
function TDatabase.RollbackTrans(): Boolean;
var
LErr: String;
begin
Result := RollbackTrans(LErr);
end;
//-----------------------------------------------------------------------------
// クエリー発行
function TDatabase.Execute(const AQuery: String; var Err: String): Boolean;
begin
Result := False;
try
FDatabase.Execute(AQuery);
Result := True;
except
on E:Exception do Err := E.Message;
end;
end;
//-----------------------------------------------------------------------------
// クエリー一括発行(空行無視)
function TDatabase.Execute(const AQueries: TStrings; var Err: String): Boolean;
var
i: Integer;
LQuery: String;
begin
Result := False;
for i := 0 to AQueries.Count - 1 do
begin
LQuery := AQueries.Strings[i];
if (LQuery <> '') then
begin
Result := Execute(LQuery, Err);
if (not Result) then Break;
end;
end;
end;
//-----------------------------------------------------------------------------
// テーブル名の一覧取得
procedure TDatabase.GetTableNames(AList: TStrings);
var
LTables: OleVariant;
LTable: OleVariant;
i: Integer;
begin
LTables := FCatalog.Tables;
for i := 0 to LTables.Count - 1 do
begin
LTable := LTables.Item[i];
AList.Add(LTable.Name);
end;
end;
//-----------------------------------------------------------------------------
// 指定テーブルのフィールド名の一覧取得
procedure TDatabase.GetTableFields(const ATableName: String; AList: TStrings);
var
LTables: OleVariant;
LTable: OleVariant;
LColumns: OleVariant;
LColumn: OleVariant;
i: Integer;
begin
LTables := FCatalog.Tables;
LTable := LTables.Item[ATableName];
LColumns := LTable.Columns;
for i := 0 to LColumns.Count - 1 do
begin
LColumn := LColumns.Item[i];
AList.Add(LColumn.Name);
end;
end;
//-----------------------------------------------------------------------------
// レコードセット作成
function TDatabase.OpenQuery(const AQuery: String; var ASet: OleVariant; var Err: String): Boolean;
begin
Result := False;
ASet := Unassigned;
try
ASet := CreateOleObject('ADODB.Recordset');
ASet.Open(AQuery, FDatabase);
Result := True;
except
on E:Exception do Err := E.Message;
end;
end;
//-----------------------------------------------------------------------------
// レコードセット破棄
procedure TDatabase.CloseQuery(var ASet: OleVariant);
begin
ASet.Close;
ASet := Unassigned;
end;
//-----------------------------------------------------------------------------
// 次のレコードへ移動
procedure TDatabase.MoveNext(const ASet: OleVariant);
begin
ASet.MoveNext;
end;
//-----------------------------------------------------------------------------
// レコードセットがEOFかどうか
function TDatabase.IsEOF(const ASet: OleVariant): Boolean;
begin
Result := ASet.EOF;
end;
//-----------------------------------------------------------------------------
// レコードセットがBOFかどうか
function TDatabase.IsBOF(const ASet: OleVariant): Boolean;
begin
Result := ASet.BOF;
end;
//-----------------------------------------------------------------------------
// レコードセットがEOFまたはBOFかどうか
function TDatabase.IsXOF(const ASet: OleVariant): Boolean;
begin
Result := IsEOF(ASet) or IsBOF(ASet);
end;
//-----------------------------------------------------------------------------
// レコードセットのフィールド数
function TDatabase.FieldCount(const ASet: OleVariant): Integer;
begin
Result := ASet.Fields.Count;
end;
//-----------------------------------------------------------------------------
// レコードセットの指定番目のフィールド名
function TDatabase.FieldName(const ASet: OleVariant; const AIndex: Integer): String;
begin
Result := ASet.Fields[AIndex].Name;
end;
//-----------------------------------------------------------------------------
// レコードセットの指定番目の値
function TDatabase.FieldValue(const ASet: OleVariant; const AIndex: Integer): Variant;
begin
Result := ASet.Fields[AIndex].Value;
end;
//-----------------------------------------------------------------------------
// レコードセットの値
function TDatabase.FieldValue(const ASet: OleVariant; const AName: String): Variant;
begin
Result := ASet.Fields[AName].Value;
end;
//=============================================================================
// TDatabaseJet
//-----------------------------------------------------------------------------
// 接続文字列作成
function TDatabaseJet.BuildConnectionString(const AUser, APass, ADatabaseName: String): String;
const
JET_OLEDB = 'Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Database Password=%s;Data Source=%s';
begin
Result := Format(JET_OLEDB, [APass, ADatabaseName]);
end;
//-----------------------------------------------------------------------------
// データベースを開く(なければ作る)
// TDatabase.Open とほとんど同じなのがもったいない
function TDatabaseJet.Open(const AUser, APass, ADatabaseName: String; var Err: String): Boolean;
begin
Close();
Result := False;
try
FCatalog := CreateOleObject('ADOX.Catalog');
// なければ作る
if (not FileExists(ADatabaseName)) then
begin
FCatalog.Create(BuildConnectionString(AUser, APass, ADatabaseName));
end;
// 接続する
FCatalog.ActiveConnection := BuildConnectionString(AUser, APass, ADatabaseName);
FDatabase := FCatalog.ActiveConnection;
FOpened := True;
FUser := AUser;
FPass := APass;
FDatabaseName := ADatabaseName;
Result := True;
except
on E:Exception do
begin
Err := E.Message;
Close();
end;
end;
end;
//-----------------------------------------------------------------------------
// パスワード変更
function TDatabaseJet.ChangePassword(const ANewPass: String; var Err: String): Boolean;
var
LUser, LPass, LDatabaseName: String;
LEngine: OleVariant;
LTemp: String;
LSrc, LDst: String;
begin
Result := False;
LUser := FUser;
LPass := FPass;
LDatabaseName := FDatabaseName;
// 閉じる
Close();
// 一時ファイル名
LTemp := TempFileName(ExtractFilePath(LDatabaseName));
DeleteFile(LTemp);
// 接続文字列作成
LSrc := BuildConnectionString(LUser, LPass, LDatabaseName);
LDst := BuildConnectionString(LUser, ANewPass, LTemp);
try
try
// パスワード変更(正確には最適化)
LEngine := CreateOleObject('JRO.JetEngine');
LEngine.CompactDatabase(LSrc, LDst);
// 置き換える
DeleteFile(LDatabaseName);
RenameFile(LTemp, LDatabaseName);
finally
LEngine := Unassigned;
end;
Result := True;
except
on E:Exception do Err := E.Message;
end;
// 開く
if Result then
begin
Result := Open(LUser, ANewPass, LDatabaseName, Err);
end
else
begin
Result := Open(LUser, LPass, LDatabaseName, Err);
end;
end;
//-----------------------------------------------------------------------------
// データベース最適化
function TDatabaseJet.CompactDatabase(var Err: String): Boolean;
begin
Result := ChangePassword(FPass, Err);
end;
//-----------------------------------------------------------------------------
initialization
CS := TCriticalSection.Create();
finalization
CS.Free();
end.
| sample | TOP |
このユニットのテストに使ったプログラムです。mdb は Microsoft Access のサンプルデータベース NWIND.MDB を使用しました。
クラスにできることを呼び出しているだけなので、面白いことはしていません。「mdb選択」で指定した mdb のテーブル一覧を表示して、選択したテーブルの全レコードをグリッドに表示します。
ちなみにこのサンプルではパスワードつきのデータベースは開けません。パスワード入力欄を設けるのが邪魔くさかったからで、逆に言うとパスワードつきのデータベースを開くテストはしていません。
今回レコードセットのフィールド名を取り出せるようにしているので、任意の SELECT 文の一覧表示が簡単にできる、はずです。ただしサンプルでは、「SELECT *」限定なのとテストのため、レコードセットからフィールドを取り出さず、テーブル自体の列を表示しています。
このとき存在しないファイルを指定すると、そういうファイル名でデータベースファイルを作成します。勝手に。
「最適化」は最適化を実行します。ソースのコメントにも書いてありますが、失敗対策が適当なので(失敗したことありませんが)、大切な mdb ファイルで試すのはやめたほうがよいです。
「SQL」テストは、エディットボックスに入力したクエリーを BeginTrans() と RollbackTrans() ではさんで実行します。ただ、ここに CREATE TABLE 文などを書くと多分ロールバックできないと思います。その辺はだいたいで。
Jet20060910.zip(229,101bytes)※ソースコードと実行ファイルです。
| NULL許可のフィールドを空文字列許可に強制変更するためのメソッド(2006/09/13) | TOP |
探求其之弐 マイクロソフト技術情報目次作成 > 再始動 のところで、このクラスを通して CREATE TABLE 文を発行することで、テーブルを作成できることを確認しました。当然、あるフィールドは NOT NULL だし、逆に NULL 可 のフィールドもあります。
あれ? mdb って、NULL可 だけじゃだめで「空文字列許可」も設定しないとだめですやん。
と言うわけで、指定したテーブルの全てのフィールドについて、それが文字列フィールドで NULL可 なら、空文字列も強制的に許可するメソッドを追加しました。ひょっとすると CREATE TABLE 文の書き方で対処できるのかも知れませんが、今はインターネットにつなげないので強引に押し切ることにします。
以下の処理ですが、ColumnTypeIsText というのは、数値型や他の文字列型でないフィールドまで処理してしまわないように、その列が文字列型かどうかをチェックする関数です。なくてもエラーにならないようなのではしょります。(ダウンロードファイルを覗いてもらえば書いてあります)
//-----------------------------------------------------------------------------
// 指定テーブルの NULL可 のフィールドの 空文字列許可フラグを立てる
function TDatabaseJet.SyncNullableAllowZeroLength(const ATableName: String; var Err: String): Boolean;
var
LTables: OleVariant;
LTable: OleVariant;
LColumns: OleVariant;
LColumn: OleVariant;
i: Integer;
begin
Result := False;
LTables := FCatalog.Tables;
LTable := LTables.Item[ATableName];
LColumns := LTable.Columns;
try
for i := 0 to LColumns.Count - 1 do
begin
LColumn := LColumns.Item[i];
// 文字列型のみ対象にする
if (not ColumnTypeIsText(LColumn)) then Continue;
// NULL許可列のみ対象にする
if (not LColumn.Properties['Nullable']) then Continue;
// 空文字列許可に設定する
LColumn.Properties['Jet OLEDB:Allow Zero Length'] := True;
end;
Result := True;
except
on E:Exception do Err := E.Message;
end;
end;
Windows XP に Office2000 Professional の Access2000 (SR1 + SP3) をインストールした状態では、これで動いています。
| 同じ条件でデータベースを開きなおすメソッド (2006/09/14) | TOP |
mdb に対してクエリーを発行して、テーブルやビューを作成すると、何故かそのままでは参照することができません。多分何かの方法はあるはずですが、いったん閉じて改めて開きなおすとちゃんと参照できるので、ここではそういうメソッドを追加することにしました。
わざわざこんなメソッドを準備するのは、Close() によりデータベースから切断したとき、パスワードやファイル名をクリアしてしまっているからです。そういうわけで、Close() するまえに パスワードやファイル名を覚えておいて、その情報を使って開きなおす、というなにやらおかしな感じのメソッドを用意したというわけです。
//-----------------------------------------------------------------------------
// いったん閉じて再度開く
function TDatabase.ReOpen(var Err: String): Boolean;
var
LUser, LPass, LDatabaseName: String;
begin
LUser := FUser;
LPass := LPass;
LDatabaseName := FDatabaseName;
// 閉じる
Close();
// 開く
Result := Open(LUser, LPass, LDatabaseName, Err);
end;
| EOF | TOP |