fpc/fcl/db/mysqldb.pp
2000-01-06 01:20:30 +00:00

792 lines
20 KiB
ObjectPascal

unit MySQLDB;
{$H+}
interface
uses
SysUtils, Classes, db, mysql,mysql_com;
type
PMySQLDatasetBookmark = ^TMySQLDatasetBookmark;
TMySQLDatasetBookmark = record
BookmarkData: Integer;
BookmarkFlag: TBookmarkFlag;
end;
Pinteger = ^Integer;
TMySQLDataset = class(TDataSet)
private
FSQL: TStrings;
FDatabase: string;
FHost: string;
FPort: Integer;
FUser: string;
FPassword: string;
FRecordSize: Integer;
FBufferSize: Integer;
// MySQL data
FMYSQL: PMYSQL;
FMYSQLRES: PMYSQL_RES;
FCurrentRecord: Integer; { Record pointer }
FServerInfo: string;
FHostInfo: string;
FAffectedRows: Integer;
FLastInsertID: Integer;
FLoadingFieldDefs: Boolean;
procedure DoOpen;
procedure DoClose;
procedure DoQuery;
procedure DoGetResult;
procedure CalculateSizes;
procedure LoadBufferFromData(Buffer: PChar);
function GetServerStatus: string;
protected
procedure SetDatabase(const Value: string);
procedure SetSQL(const Value: TStrings);
function GetClientInfo: string;
function InternalStrToFloat(S: string): Extended;
function InternalStrToDate(S: string): TDateTime;
function InternalStrToTime(S: string): TDateTime;
function InternalStrToDateTime(S: string): TDateTime;
function InternalStrToTimeStamp(S: string): TDateTime;
function MySQLFieldToFieldType(AType: enum_field_types; ASize: Integer;
var NewType: TFieldType; var NewSize: Integer): Boolean;
function MySQLDataSize(AType: enum_field_types; ASize: Integer): Integer;
function MySQLWriteFieldData(AType: enum_field_types; ASize: Integer; Source: PChar;
Dest: PChar): Integer;
function GetCanModify: Boolean; override;
{ Mandatory overrides }
// Record buffer methods:
function AllocRecordBuffer: PChar; override;
procedure FreeRecordBuffer(var Buffer: PChar); override;
procedure InternalInitRecord(Buffer: PChar); override;
function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
function GetRecordSize: Word; override;
procedure SetFieldData(Field: TField; Buffer: Pointer); override;
// Bookmark methods:
procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
procedure InternalGotoBookmark(ABookmark: Pointer); override;
procedure InternalSetToRecord(Buffer: PChar); override;
procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
// Navigational methods:
procedure InternalFirst; override;
procedure InternalLast; override;
// Editing methods:
procedure InternalAddRecord(Buffer: Pointer; DoAppend: Boolean); override;
procedure InternalDelete; override;
procedure InternalPost; override;
// Misc methods:
procedure InternalClose; override;
procedure InternalHandleException; override;
procedure InternalInitFieldDefs; override;
procedure InternalOpen; override;
function IsCursorOpen: Boolean; override;
{ Optional overrides }
function GetRecordCount: Integer; override;
function GetRecNo: Integer; override;
procedure SetRecNo(Value: Integer); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure ExecSQL;
// TDataset method
function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
property ServerInfo: string read FServerInfo;
property ClientInfo: string read GetClientInfo;
property HostInfo: string read FHostInfo;
property AffectedRows: Integer read FAffectedRows;
property LastInsertID: Integer read FLastInsertID;
property ServerStatus: string read GetServerStatus;
published
property Active;
property SQL: TStrings read FSQL write SetSQL;
property Database: string read FDatabase write SetDatabase;
property Host: string read FHost write FHost;
property Port: Integer read FPort write FPort;
property User: string read FUser write FUser;
property Password: string read FPassword write FPassword;
property BeforeOpen;
property AfterOpen;
property BeforeClose;
property AfterClose;
property BeforeInsert;
property AfterInsert;
property BeforeEdit;
property AfterEdit;
property BeforePost;
property AfterPost;
property BeforeCancel;
property AfterCancel;
property BeforeDelete;
property AfterDelete;
property BeforeScroll;
property AfterScroll;
property OnDeleteError;
property OnEditError;
end;
implementation
{ TMySQLDataset }
constructor TMySQLDataset.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FSQL := TStringList.Create;
FHost := '';
FPort := 0;
FUser := '';
FPassword := '';
FBufferSize := 0;
FRecordSize := 0;
FCurrentRecord := -1;
FLoadingFieldDefs := False;
FAffectedRows := 0;
FLastInsertID := -1;
FMYSQL := nil;
FMYSQLRES := nil;
end;
destructor TMySQLDataset.Destroy;
begin
FSQL.Free;
inherited destroy;
end;
function TMySQLDataset.AllocRecordBuffer: PChar;
begin
Result := AllocMem(FBufferSize);
end;
procedure TMySQLDataset.FreeRecordBuffer(var Buffer: PChar);
begin
FreeMem(Buffer);
end;
procedure TMySQLDataset.GetBookmarkData(Buffer: PChar; Data: Pointer);
begin
PInteger(Data)^ := PMySQLDatasetBookmark(Buffer + FRecordSize)^.BookmarkData;
end;
function TMySQLDataset.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
begin
Result := PMySQLDatasetBookmark(Buffer + FRecordSize)^.BookmarkFlag;
end;
function TMySQLDataset.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
var
I, FC: Integer;
fld: TMYSQL_FIELD;
CurBuf: PChar;
begin
Result := False;
CurBuf := ActiveBuffer;
FC := mysql_num_fields(FMYSQLRES);
for I := 0 to FC-1 do
begin
fld := mysql_fetch_field_direct(FMYSQLRES, I);
//if Field.FieldNo = I+1 then
if Field.FieldName = fld.name then
begin
Move(CurBuf^, PChar(Buffer)^, MySQLDataSize(fld.ftype, fld.length));
if Field.DataType in [ftString{, ftWideString}] then
Result := PChar(buffer)^ <> #0
else
Result := True;
break;
end
else
Inc(CurBuf, MySQLDataSize(fld.ftype, fld.length));
end;
end;
function TMySQLDataset.GetRecNo: Integer;
begin
UpdateCursorPos;
if (FCurrentRecord = -1) and (RecordCount > 0) then
Result := 1
else
Result := FCurrentRecord + 1;
end;
function TMySQLDataset.GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
begin
if RecordCount < 1 then
Result := grEOF
else
begin
Result := grOk;
case GetMode of
gmPrior:
if FCurrentRecord <= 0 then
begin
Result := grBOF;
FCurrentRecord := -1;
end
else
Dec(FCurrentRecord);
gmCurrent:
if (FCurrentRecord < 0) or (FCurrentRecord >= RecordCount) then
Result := grError;
gmNext:
if FCurrentRecord >= RecordCount-1 then
Result := grEOF
else
Inc(FCurrentRecord);
end;
if Result = grOK then
begin
LoadBufferFromData(Buffer);
with PMySQLDatasetBookmark(Buffer + FRecordSize)^ do
begin
BookmarkData := FCurrentRecord;
BookmarkFlag := bfCurrent;
end;
end
else
if (Result = grError) and (DoCheck) then
DatabaseError('No record');
end;
end;
function TMySQLDataset.GetRecordCount: Integer;
begin
Result := mysql_num_rows(FMYSQLRES);
end;
function TMySQLDataset.GetRecordSize: Word;
begin
Result := FRecordSize;
end;
procedure TMySQLDataset.InternalAddRecord(Buffer: Pointer; DoAppend: Boolean);
begin
end;
procedure TMySQLDataset.InternalClose;
begin
FCurrentRecord := -1;
DoClose;
if DefaultFields then
DestroyFields;
end;
procedure TMySQLDataset.InternalDelete;
begin
end;
procedure TMySQLDataset.InternalFirst;
begin
FCurrentRecord := -1;
end;
procedure TMySQLDataset.InternalGotoBookmark(ABookmark: Pointer);
begin
FCurrentRecord := PInteger(ABookmark)^;
end;
procedure TMySQLDataset.InternalHandleException;
begin
// Application.HandleException(self);
end;
procedure TMySQLDataset.InternalInitFieldDefs;
var
I, FC: Integer;
field: TMYSQL_FIELD;
DFT: TFieldType;
DFS: Integer;
WasClosed: Boolean;
begin
if FLoadingFieldDefs then Exit;
FLoadingFieldDefs := True;
try
WasClosed := not IsCursorOpen;
if WasClosed then
begin
DoOpen;
DoQuery;
DoGetResult;
end;
try
FieldDefs.Clear;
FC := mysql_num_fields(FMYSQLRES);
for I := 0 to FC-1 do
begin
field := mysql_fetch_field_direct(FMYSQLRES, I);
if MySQLFieldToFieldType(field.ftype, field.length, DFT, DFS) then
TFieldDef.Create(FieldDefs, field.name, DFT, DFS, False, I+1);
end;
finally
if WasClosed then
begin
DoClose;
end;
end;
finally
FLoadingFieldDefs := False;
end;
end;
procedure TMySQLDataset.InternalInitRecord(Buffer: PChar);
begin
FillChar(Buffer^, FBufferSize, 0);
end;
procedure TMySQLDataset.InternalLast;
begin
FCurrentRecord := RecordCount;
end;
procedure TMySQLDataset.InternalOpen;
begin
FMYSQL := nil;
FMYSQLRES := nil;
try
DoOpen;
DoQuery;
DoGetResult;
FCurrentRecord := -1;
InternalInitFieldDefs;
if DefaultFields then
CreateFields;
CalculateSizes;
BindFields(True);
except
DoClose;
FMYSQL := nil;
FMYSQLRES := nil;
raise;
end;
FServerInfo := mysql_get_server_info(FMYSQL);
FHostInfo := mysql_get_host_info(FMYSQL);
BookMarkSize:=SizeOf(Longint);
end;
procedure TMySQLDataset.InternalSetToRecord(Buffer: PChar);
begin
FCurrentRecord := PMySQLDatasetBookmark(Buffer + FRecordSize)^.BookmarkData;
end;
function TMySQLDataset.IsCursorOpen: Boolean;
begin
Result := FMYSQL <> nil;
end;
procedure TMySQLDataset.SetBookmarkData(Buffer: PChar; Data: Pointer);
begin
PMySQLDatasetBookmark(Buffer + FRecordSize)^.BookmarkData := PInteger(Data)^;
end;
procedure TMySQLDataset.SetBookmarkFlag(Buffer: PChar;
Value: TBookmarkFlag);
begin
PMySQLDatasetBookmark(Buffer + FRecordSize)^.BookmarkFlag := Value;
end;
procedure TMySQLDataset.SetFieldData(Field: TField; Buffer: Pointer);
begin
end;
procedure TMySQLDataset.SetRecNo(Value: Integer);
begin
if (Value >= 0) and (Value <= RecordCount-1) then
begin
FCurrentRecord := Value-1;
Resync([]);
end;
end;
procedure TMySQLDataset.SetSQL(const Value: TStrings);
begin
FSQL.Assign(Value);
FieldDefs.Clear;
end;
procedure TMySQLDataset.ExecSQL;
begin
try
DoOpen;
try
DoQuery;
finally
DoClose;
end;
finally
FMYSQLRES := nil;
FMYSQL := nil;
end;
end;
procedure TMySQLDataset.SetDatabase(const Value: string);
begin
FDatabase := Value;
end;
procedure TMySQLDataset.InternalPost;
begin
end;
function TMySQLDataset.GetClientInfo: string;
begin
Result := mysql_get_client_info;
end;
function TMySQLDataset.MySQLFieldToFieldType(AType: enum_field_types; ASize: Integer;
var NewType: TFieldType; var NewSize: Integer): Boolean;
begin
Result := True;
case AType of
FIELD_TYPE_TINY, FIELD_TYPE_SHORT, FIELD_TYPE_LONG, FIELD_TYPE_LONGLONG,
FIELD_TYPE_INT24:
begin
NewType := ftInteger;
NewSize := 0;
end;
FIELD_TYPE_DECIMAL, FIELD_TYPE_FLOAT, FIELD_TYPE_DOUBLE:
begin
NewType := ftFloat;
NewSize := 0;
end;
FIELD_TYPE_TIMESTAMP, FIELD_TYPE_DATETIME:
begin
NewType := ftDateTime;
NewSize := 0;
end;
FIELD_TYPE_DATE:
begin
NewType := ftDate;
NewSize := 0;
end;
FIELD_TYPE_TIME:
begin
NewType := ftTime;
NewSize := 0;
end;
FIELD_TYPE_VAR_STRING, FIELD_TYPE_STRING, FIELD_TYPE_ENUM, FIELD_TYPE_SET:
begin
NewType := ftString;
NewSize := ASize;
end;
else
Result := False;
end;
end;
procedure TMySQLDataset.CalculateSizes;
var
I, FC: Integer;
field: TMYSQL_FIELD;
begin
FRecordSize := 0;
FC := mysql_num_fields(FMYSQLRES);
for I := 0 to FC-1 do
begin
field := mysql_fetch_field_direct(FMYSQLRES, I);
FRecordSize := FRecordSize + MySQLDataSize(field.ftype, field.length);
end;
FBufferSize := FRecordSize + SizeOf(TMySQLDatasetBookmark);
end;
procedure TMySQLDataset.LoadBufferFromData(Buffer: PChar);
var
I, FC, CT: Integer;
field: TMYSQL_FIELD;
row: TMYSQL_ROW;
begin
mysql_data_seek(FMYSQLRES, FCurrentRecord);
row := mysql_fetch_row(FMYSQLRES);
if row = nil then
DatabaseError(mysql_error(FMYSQL));
FC := mysql_num_fields(FMYSQLRES);
for I := 0 to FC-1 do
begin
field := mysql_fetch_field_direct(FMYSQLRES, I);
CT := MySQLWriteFieldData(field.ftype, field.length, row^, Buffer);
Inc(Buffer, CT);
Inc(row);
end;
end;
function TMySQLDataset.MySQLDataSize(AType: enum_field_types;
ASize: Integer): Integer;
begin
Result := 0;
case AType of
FIELD_TYPE_TINY, FIELD_TYPE_SHORT, FIELD_TYPE_LONG, FIELD_TYPE_LONGLONG,
FIELD_TYPE_INT24:
begin
Result := SizeOf(Integer);
end;
FIELD_TYPE_DECIMAL, FIELD_TYPE_FLOAT, FIELD_TYPE_DOUBLE:
begin
Result := SizeOf(Double);
end;
FIELD_TYPE_TIMESTAMP, FIELD_TYPE_DATE, FIELD_TYPE_TIME, FIELD_TYPE_DATETIME:
begin
Result := SizeOf(TDateTime);
end;
FIELD_TYPE_VAR_STRING, FIELD_TYPE_STRING, FIELD_TYPE_ENUM, FIELD_TYPE_SET:
begin
Result := ASize;
end;
end;
end;
function TMySQLDataset.MySQLWriteFieldData(AType: enum_field_types;
ASize: Integer; Source, Dest: PChar): Integer;
var
VI: Integer;
VF: Double;
VD: TDateTime;
begin
Result := 0;
case AType of
FIELD_TYPE_TINY, FIELD_TYPE_SHORT, FIELD_TYPE_LONG, FIELD_TYPE_LONGLONG,
FIELD_TYPE_INT24:
begin
Result := SizeOf(Integer);
if Source <> '' then
VI := StrToInt(Source)
else
VI := 0;
Move(VI, Dest^, Result);
end;
FIELD_TYPE_DECIMAL, FIELD_TYPE_FLOAT, FIELD_TYPE_DOUBLE:
begin
Result := SizeOf(Double);
if Source <> '' then
VF := InternalStrToFloat(Source)
else
VF := 0;
Move(VF, Dest^, Result);
end;
FIELD_TYPE_TIMESTAMP:
begin
Result := SizeOf(TDateTime);
if Source <> '' then
VD := InternalStrToTimeStamp(Source)
else
VD := 0;
Move(VD, Dest^, Result);
end;
FIELD_TYPE_DATETIME:
begin
Result := SizeOf(TDateTime);
if Source <> '' then
VD := InternalStrToDateTime(Source)
else
VD := 0;
Move(VD, Dest^, Result);
end;
FIELD_TYPE_DATE:
begin
Result := SizeOf(TDateTime);
if Source <> '' then
VD := InternalStrToDate(Source)
else
VD := 0;
Move(VD, Dest^, Result);
end;
FIELD_TYPE_TIME:
begin
Result := SizeOf(TDateTime);
if Source <> '' then
VD := InternalStrToTime(Source)
else
VD := 0;
Move(VD, Dest^, Result);
end;
FIELD_TYPE_VAR_STRING, FIELD_TYPE_STRING, FIELD_TYPE_ENUM, FIELD_TYPE_SET:
begin
Result := ASize;
if Source <> '' then
Move(Source^, Dest^, Result)
else
Dest^ := #0;
end;
end;
end;
function TMySQLDataset.InternalStrToFloat(S: string): Extended;
var
I: Integer;
Tmp: string;
begin
Tmp := '';
for I := 1 to Length(S) do
begin
if not (S[I] in ['0'..'9', '+', '-', 'E', 'e']) then
Tmp := Tmp + DecimalSeparator
else
Tmp := Tmp + S[I];
end;
Result := StrToFloat(Tmp);
end;
function TMySQLDataset.InternalStrToDate(S: string): TDateTime;
var
EY, EM, ED: Word;
begin
EY := StrToInt(Copy(S, 1, 4));
EM := StrToInt(Copy(S, 6, 2));
ED := StrToInt(Copy(S, 9, 2));
if (EY = 0) or (EM = 0) or (ED = 0) then
Result := 0
else
Result := EncodeDate(EY, EM, ED);
end;
function TMySQLDataset.InternalStrToDateTime(S: string): TDateTime;
var
EY, EM, ED: Word;
EH, EN, ES: Word;
begin
EY := StrToInt(Copy(S, 1, 4));
EM := StrToInt(Copy(S, 6, 2));
ED := StrToInt(Copy(S, 9, 2));
EH := StrToInt(Copy(S, 11, 2));
EN := StrToInt(Copy(S, 14, 2));
ES := StrToInt(Copy(S, 17, 2));
if (EY = 0) or (EM = 0) or (ED = 0) then
Result := 0
else
Result := EncodeDate(EY, EM, ED);
Result := Result + EncodeTime(EH, EN, ES, 0);
end;
function TMySQLDataset.InternalStrToTime(S: string): TDateTime;
var
EH, EM, ES: Word;
begin
EH := StrToInt(Copy(S, 1, 2));
EM := StrToInt(Copy(S, 4, 2));
ES := StrToInt(Copy(S, 7, 2));
Result := EncodeTime(EH, EM, ES, 0);
end;
function TMySQLDataset.InternalStrToTimeStamp(S: string): TDateTime;
var
EY, EM, ED: Word;
EH, EN, ES: Word;
begin
EY := StrToInt(Copy(S, 1, 4));
EM := StrToInt(Copy(S, 5, 2));
ED := StrToInt(Copy(S, 7, 2));
EH := StrToInt(Copy(S, 9, 2));
EN := StrToInt(Copy(S, 11, 2));
ES := StrToInt(Copy(S, 13, 2));
if (EY = 0) or (EM = 0) or (ED = 0) then
Result := 0
else
Result := EncodeDate(EY, EM, ED);
Result := Result + EncodeTime(EH, EN, ES, 0);;
end;
procedure TMySQLDataset.DoClose;
begin
try
if FMYSQLRES <> nil then
mysql_free_result(FMYSQLRES);
if FMYSQL <> nil then
mysql_close(FMYSQL);
finally
FMYSQLRES := nil;
FMYSQL := nil;
end;
end;
procedure TMySQLDataset.DoOpen;
begin
FMYSQL := mysql_connect(nil, PChar(FHost), PChar(FUser), PChar(FPassword));
if FMYSQL = nil then
DatabaseError('Error connecting to MySQL server');
if FDatabase <> '' then
if mysql_select_db(FMYSQL, PChar(FDatabase)) <> 0 then
DatabaseError(mysql_error(FMYSQL));
end;
procedure TMySQLDataset.DoQuery;
var
Query: string;
begin
Query := FSQL.GetText;
if mysql_query(FMYSQL, PChar(Query)) <> 0 then
DatabaseError(mysql_error(FMYSQL));
FAffectedRows := mysql_affected_rows(FMYSQL);
FLastInsertID := mysql_insert_id(FMYSQL);
end;
function TMySQLDataset.GetCanModify: Boolean;
begin
Result := False;
end;
procedure TMySQLDataset.DoGetResult;
begin
FMYSQLRES := mysql_store_result(FMYSQL);
if FMYSQLRES = nil then
DatabaseError(mysql_error(FMYSQL));
FAffectedRows := mysql_affected_rows(FMYSQL);
end;
function TMySQLDataset.GetServerStatus: string;
begin
CheckActive;
Result := mysql_stat(FMYSQL);
end;
end.