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.
  $Log: mysqldb.pp,v $
  Revision 1.1  2001/01/24 22:19:04  peter
    * mysql units moved to mysql/ dir

  Revision 1.2  2000/07/13 11:32:56  michael
  + removed logs
 
}
