//////////////////////////////////////////////////
//  SQL Server Data Access Components
//  Copyright © 1998-2007 Core Lab. All right reserved.
//  MSAccess
//////////////////////////////////////////////////

{$IFNDEF CLR}

{$I Sdac.inc}

unit MSLoader;
{$ENDIF}
interface

uses
{$IFDEF CLR}
  System.Runtime.InteropServices, Variants,
{$ELSE}
  CLRClasses,
{$ENDIF}
  Windows, Classes, DB, MemDS, CRAccess, MemData,
  DALoader, OLEDBAccess, MSAccess, OLEDBC, OLEDBIntf;

type
  TMSColumn = class(TDAColumn)
  private
    FSize: integer;
    FPrecision: integer;
    FScale: integer;
    FIsWide: boolean;

    function GetSize: integer;
    procedure SetSize(Value: integer);

  protected
    procedure SetFieldType(Value: TFieldType); override;

  published
    property Size: integer read GetSize write SetSize;
    property Precision: integer read FPrecision write FPrecision default 0;
    property Scale: integer read FScale write FScale default 0;
  end;

  TMSLoader = class;

  TMSPutDataEvent = procedure (Sender: TMSLoader) of object;
  TMSGetColumnDataEvent = procedure (Sender: TObject; Column: TMSColumn;
    Row: integer; var Value: variant; var IsEOF: boolean) of object;

  TMSLoaderOptions = class(TPersistent)
  private
    FRowsPerBatch: integer;
    FKilobytesPerBatch: integer;
    FLockTable: boolean;
    FCheckConstraints: boolean;
    FOwner: TMSLoader;
  protected
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create(Owner: TMSLoader);
  published
    property RowsPerBatch: integer read FRowsPerBatch write FRowsPerBatch default 0;
    property KilobytesPerBatch: integer read FKilobytesPerBatch write FKilobytesPerBatch default 0;
    property LockTable: boolean read FLockTable write FLockTable default False;
    property CheckConstraints: boolean read FCheckConstraints write FCheckConstraints default False;
  end;

  TDBIDAccessor = class
  protected
    FPDBID: PDBID;
    function GeteKind: DBKIND;
    procedure SeteKind(Value: DBKIND);
    function GetpwszName: IntPtr;
    procedure SetpwszName(Value: IntPtr);
    procedure SetPDBID(Value: PDBID);
  public
    constructor Create(APDBID: PDBID);
    property eKind: DBKIND read GeteKind write SeteKind;
    property pwszName: IntPtr read GetpwszName write SetpwszName;
  end;

  TMSLoader = class(TDALoader)
  private
    FIOpenRowset: IOpenRowset;
    FIRowsetFastLoad: IRowsetFastLoad;
    FParamsAccessorData: TParamsAccessorData;
    FOnPutData: TMSPutDataEvent;
    FOnGetColumnData: TMSGetColumnDataEvent;
    FKeepIdentity: boolean;
    FKeepNulls: boolean;
    FOptions: TMSLoaderOptions;
    FParamDescs: TParamDescs;

    procedure DAPutDataEvent(Sender: TDALoader);
    procedure DAGetColumnDataEvent(Sender: TObject; Column: TDAColumn;
      Row: integer; var Value: variant; var IsEOF: boolean);
  protected
    class function DAColumnClass: TDAColumnClass; override;
    function GetConnection: TMSConnection;
    procedure SetConnection(Value: TMSConnection);
    procedure SetOnPutData(const Value: TMSPutDataEvent);
    procedure SetOnGetColumnData(const Value: TMSGetColumnDataEvent);
    procedure SetKeepIdentity(Value: boolean);
    procedure SetKeepNulls(Value: boolean);
    procedure SetOptions(Value: TMSLoaderOptions);

    procedure Prepare; override;
    procedure Commit(Done: boolean = True);
    procedure Finish; override;

    procedure FillColumn(Column: TDAColumn; FieldDesc: TFieldDesc); override;
  public
    constructor Create(Owner: TComponent); override;
    destructor Destroy; override;
    procedure PutColumnData(Col: integer; Row: integer; const Value: variant); override;
  published
    property Connection: TMSConnection read GetConnection write SetConnection;
    property TableName;
    property Columns;
    property KeepIdentity: boolean read FKeepIdentity write SetKeepIdentity default False;
    property KeepNulls: boolean read FKeepNulls write SetKeepNulls default False;
    property Options: TMSLoaderOptions read FOptions write SetOptions;
    property OnPutData: TMSPutDataEvent read FOnPutData write SetOnPutData;
    property OnGetColumnData: TMSGetColumnDataEvent read FOnGetColumnData write SetOnGetColumnData;
  end;

implementation

uses
  SysUtils,
{$IFDEF CLR}
  System.Text,
{$ENDIF}
{$IFDEF VER6P}
{$IFNDEF CLR}
  Variants,
{$ENDIF}
  FmtBcd,
{$ENDIF}
  ActiveX, MemUtils, DBAccess;

{ TMSColumn }

function TMSColumn.GetSize: integer;
begin
  if (FieldType in [ftString, ftFixedChar, ftWideString, ftBytes, ftVarBytes,
    ftBlob, ftMemo{$IFDEF VER10P}, ftWideMemo{$ENDIF}, ftVariant, ftUnknown]) then
    Result := FSize
  else
    Result := 0;
end;

procedure TMSColumn.SetFieldType(Value: TFieldType);
var
  NewValue: boolean;
begin
  NewValue := Value <> FieldType;

  inherited;

  if NewValue then
    case FieldType of
      ftGuid:
        FSize := SizeOf(TGuid);
      ftSmallint:
        FSize := SizeOf(SmallInt);
      ftInteger:
        FSize := SizeOf(Integer);
      ftWord:
        FSize := SizeOf(Word);
      ftLargeint:
        FSize := SizeOf(LargeInt);
      ftBoolean:
        FSize := SizeOf(Boolean);
      ftFloat, ftCurrency:
        FSize := SizeOf(Double);
      ftBCD:
        FSize := SizeOf(Currency);
    {$IFDEF VER6P}
      ftFMTBcd:
        FSize := SizeOf(TBcd);
    {$ENDIF}
      ftDate, ftTime, ftDateTime{$IFDEF VER6P}, ftTimeStamp{$ENDIF}:
        FSize := SizeOf(Double);
      else
        FSize := 0;
    end;
end;

procedure TMSColumn.SetSize(Value: integer);
begin
  if (FieldType in [ftString, ftFixedChar, ftWideString, ftBytes, ftVarBytes,
    ftBlob, ftMemo{$IFDEF VER10P}, ftWideMemo{$ENDIF}, ftVariant, ftUnknown]) and (Value > 0) then
    FSize := Value;
end;

{ TDBIDAccessor }

constructor TDBIDAccessor.Create(APDBID: PDBID);
begin
  inherited Create;

  FPDBID := APDBID;
end;

procedure TDBIDAccessor.SetPDBID(Value: PDBID);
begin
  FPDBID := Value;
end;

function TDBIDAccessor.GeteKind: DBKIND;
var
  Offset: integer;
begin
  Offset := 16;
  Result := Marshal.ReadInt32(FPDBID, Offset);
end;

procedure TDBIDAccessor.SeteKind(Value: DBKIND);
var
  Offset: integer;
begin
  Offset := 16;
  Marshal.WriteInt32(FPDBID, Offset, Value);
end;

function TDBIDAccessor.GetpwszName: IntPtr;
var
  Offset: integer;
begin
  Offset := 20;
  Result := Marshal.ReadIntPtr(FPDBID, Offset);
end;

procedure TDBIDAccessor.SetpwszName(Value: IntPtr);
var
  Offset: integer;
begin
  Offset := 20;
  Marshal.WriteIntPtr(FPDBID, Offset, Value);
end;

{ TMSLoader }

class function TMSLoader.DAColumnClass: TDAColumnClass;
begin
  Result := TMSColumn;
end;

function TMSLoader.GetConnection: TMSConnection;
begin
  Result := TMSConnection(inherited Connection);
end;

procedure TMSLoader.SetConnection(Value: TMSConnection);
begin
  inherited Connection := Value;
end;

procedure TMSLoader.DAPutDataEvent(Sender: TDALoader);
begin
  if Assigned(FOnPutData) then
    FOnPutData(TMSLoader(Sender));
end;

procedure TMSLoader.DAGetColumnDataEvent(Sender: TObject; Column: TDAColumn;
  Row: integer; var Value: variant; var IsEOF: boolean);
begin
  if Assigned(FOnGetColumnData) then
    FOnGetColumnData(Sender, TMSColumn(Column), Row, Value, IsEOF);
end;

procedure TMSLoader.SetOnPutData(const Value: TMSPutDataEvent);
begin
  FOnPutData := Value;
  if Assigned(FOnPutData) then
    inherited OnPutData := DAPutDataEvent
  else
    inherited OnPutData := nil;
end;

procedure TMSLoader.SetOnGetColumnData(const Value: TMSGetColumnDataEvent);
begin
  FOnGetColumnData := Value;
  if Assigned(Value) then
    inherited OnGetColumnData := DAGetColumnDataEvent
  else
    inherited OnGetColumnData := nil;
end;

procedure TMSLoader.SetKeepIdentity(Value: boolean);
begin
  FKeepIdentity := Value;
end;

procedure TMSLoader.SetKeepNulls(Value: boolean);
begin
  FKeepNulls := Value;
end;

procedure TMSLoader.SetOptions(Value: TMSLoaderOptions);
begin
  FOptions.Assign(Value);
end;

constructor TMSLoader.Create(Owner: TComponent);
begin
  inherited Create(Owner);

  FOptions := TMSLoaderOptions.Create(Self);
  FKeepIdentity := False;
  FKeepNulls := False;
  FSkipReadOnlyFieldDescs := False;
end;

destructor TMSLoader.Destroy;
begin
  Assert(FIOpenRowset = nil);
  Assert(FIRowsetFastLoad = nil);
  FOptions.Free;
  
  inherited;
end;

procedure TMSLoader.FillColumn(Column: TDAColumn; FieldDesc: TFieldDesc);
begin
  with TMSColumn(Column) do begin
    Name := FieldDesc.Name;
    FIsWide := (FieldDesc.SubDataType and dtWide) <> 0;
    FieldType := GetFieldType(FieldDesc.DataType);
    case FieldType of
      ftString, ftFixedChar, ftWideString, ftBytes, ftVarBytes, ftBlob, ftMemo{$IFDEF VER10P}, ftWideMemo{$ENDIF}, ftVariant, ftUnknown:
        Size := FieldDesc.Length;
      ftFloat, ftCurrency, ftBCD{$IFDEF VER6P}, ftFMTBcd{$ENDIF}: begin
        Precision := FieldDesc.Length;
        Scale := FieldDesc.Scale;
      end;
    end;
  end;
end;

procedure TMSLoader.Prepare;
const
  MaxPropCount = 3;
var
  OLEDBConnection: TOLEDBConnection;
  TableID: PDBID;
  TableIDAccessor: TDBIDAccessor;
  BindMemorySize: UINT;
{$IFDEF CLR}
  rgBindingsGC: GCHandle;
{$ENDIF}
  rgBindings: PDBBinding;
  rgStatus: PUINT;
  i: integer;
  OLEDBPropertiesSet: TOLEDBPropertiesSet;
  OptionsStr: string;
  IUnk: IUnknown;
  ParamDesc: TOLEDBParamDesc;

  procedure AddOption(var OptionStr: string; const Value: string);
  begin
    if OptionStr <> '' then
      OptionStr := OptionStr + ', ';
    OptionStr := OptionStr + Value;
  end;

begin
  inherited;

  OLEDBConnection := TMSAccessUtils.FIConnection(Connection);
  OLEDBConnection.Check(TMSAccessUtils.FIDBCreateSession(OLEDBConnection).CreateSession(nil, IID_IOpenRowset, IUnk), Self);
  FIOpenRowset := IOpenRowset(IUnk);

  TableID := Marshal.AllocHGlobal(SizeOf(TDBID));
  TableIDAccessor := TDBIDAccessor.Create(TableID);
  OLEDBPropertiesSet := TOLEDBPropertiesSet.Create(OLEDBConnection, DBPROPSET_SQLSERVERROWSET);
  try
    TableIDAccessor.eKind := DBKIND_NAME;
    TableIDAccessor.pwszName := Marshal.AllocHGlobal(Length(TableName) * SizeOf(WideChar) + SizeOf(WideChar));
    CopyBuffer(Marshal.StringToHGlobalUni(Encoding.Default.GetString(Encoding.Default.GetBytes(TableName), 0, Length(TableName))),
      TableIDAccessor.pwszName, Length(TableName) * SizeOf(WideChar) + SizeOf(WideChar));

    OLEDBPropertiesSet.AddPropBool(SSPROP_FASTLOADKEEPIDENTITY, FKeepIdentity);
    OLEDBPropertiesSet.AddPropBool(SSPROP_FASTLOADKEEPNULLS, FKeepNulls);
    if (FOptions.FRowsPerBatch > 0) or (FOptions.FKilobytesPerBatch > 0) or FOptions.FLockTable or FOptions.FCheckConstraints then begin
       OptionsStr := '';
       if FOptions.FRowsPerBatch > 0 then
         AddOption(OptionsStr, 'ROWS_PER_BATCH = ' + IntToStr(FOptions.FRowsPerBatch));
       if FOptions.FKilobytesPerBatch > 0 then
         AddOption(OptionsStr, 'KILOBYTES_PER_BATCH = ' + IntToStr(FOptions.FKilobytesPerBatch));
       if FOptions.FLockTable then
         AddOption(OptionsStr, 'TABLOCK');
       if FOptions.FCheckConstraints then
         AddOption(OptionsStr, 'CHECK_CONSTRAINTS');
      OLEDBPropertiesSet.AddPropStr(SSPROP_FASTLOADOPTIONS, OptionsStr);
    end;

    OLEDBConnection.Check(FIOpenRowset.OpenRowset(nil, TableID, nil, IID_IRowsetFastLoad, 1,
      PDBPropIDSetArray(OLEDBPropertiesSet.InitPropSet), IUnk), Self);
    FIRowsetFastLoad := IRowsetFastLoad(IUnk);
  finally
    OLEDBPropertiesSet.Free;
    Marshal.FreeHGlobal(TableIDAccessor.pwszName);
    Marshal.FreeHGlobal(TableID);
    TableIDAccessor.Free;
  end;
  
  rgStatus := Marshal.AllocHGlobal(Columns.Count * SizeOf(UINT));
{$IFNDEF CLR}
  IntPtr(FParamsAccessorData.Accessor) := nil;
{$ENDIF}
  try
    try
      FParamsAccessorData.ExecuteParams.HACCESSOR := 0;
      FParamsAccessorData.ExecuteParams.pData := nil;
      FParamsAccessorData.ExecuteParams.cParamSets := 1;

      SetLength(FParamsAccessorData.rgBindings, Columns.Count);

      for i := 0 to Columns.Count - 1 do
        FParamsAccessorData.rgBindings[i].pObject := nil;

      BindMemorySize := 0;

      FParamDescs := TParamDescs.Create;
      for i := 0 to Columns.Count - 1 do begin
        ParamDesc := TOLEDBParamDesc.Create;
        try
          ParamDesc.SetName(Columns[i].Name); // +++
          ParamDesc.SetDataType(GetDataType(TMSColumn(Columns[i]).FieldType));
          ParamDesc.SetParamType(pdInput);
          FillBindingForParam(i + 1, ParamDesc, OLEDBConnection, FParamsAccessorData.rgBindings[i], BindMemorySize, False, TMSColumn(Columns[i]).FIsWide);
          FParamsAccessorData.rgBindings[i].eParamIO := DBPARAMIO_NOTPARAM;
          ParamDesc.SetSize(FParamsAccessorData.rgBindings[i].cbMaxLen);
        finally
          FParamDescs.Add(ParamDesc);
        end;
      end;

      FParamsAccessorData.ExecuteParams.pData := Marshal.AllocHGlobal(BindMemorySize);
      FillChar(FParamsAccessorData.ExecuteParams.pData, BindMemorySize, 0);

      QueryIntf(FIRowsetFastLoad, {$IFDEF CLR}IAccessor{$ELSE}IID_IAccessor{$ENDIF}, FParamsAccessorData.Accessor);

    {$IFDEF CLR}
      rgBindingsGC := GCHandle.Alloc(FParamsAccessorData.rgBindings, GCHandleType.Pinned);
      rgBindings := Marshal.UnsafeAddrOfPinnedArrayElement(FParamsAccessorData.rgBindings, 0);
    {$ELSE}
      rgBindings := @FParamsAccessorData.rgBindings[0];
    {$ENDIF}

      OLEDBConnection.Check(FParamsAccessorData.Accessor.CreateAccessor(DBACCESSOR_ROWDATA, Columns.Count,
        rgBindings, BindMemorySize, FParamsAccessorData.ExecuteParams.HACCESSOR, rgStatus), Self);
    except
      if FParamsAccessorData.ExecuteParams.pData <> nil then
        Marshal.FreeHGlobal(FParamsAccessorData.ExecuteParams.pData);
      FParamsAccessorData.ExecuteParams.pData := nil;

      if Length(FParamsAccessorData.rgBindings) <> 0 then begin
        for i := 0 to Columns.Count - 1 do begin
          if FParamsAccessorData.rgBindings[i].pObject <> nil then
            Marshal.FreeHGlobal(FParamsAccessorData.rgBindings[i].pObject);
        end;
        SetLength(FParamsAccessorData.rgBindings, 0);
      end;

      raise;
    end;
  finally
    Marshal.FreeHGlobal(rgStatus);
  {$IFDEF CLR}
    if IntPtr(rgBindingsGC) <> nil then
      rgBindingsGC.Free;
  {$ENDIF}
  end;
end;

procedure TMSLoader.PutColumnData(Col: integer; Row: integer; const Value: variant);
var
  ParamDesc: CRAccess.TParamDesc;
  pLength: PUINT;
{$IFDEF CLR}
  ParamGCHandle: TIntPtrDynArray;
{$ENDIF}  
begin
  inherited;

  ParamDesc := FParamDescs.Items[Col];
  ParamDesc.SetValue(Unassigned);
  ParamDesc.SetValue(Value);
  FParamsAccessorData.rgBindings[Col].eParamIO := DBPARAMIO_INPUT;


  SaveParamValue(ParamDesc, FParamsAccessorData.rgBindings[Col], FParamsAccessorData{$IFDEF HAVE_COMPRESS}, cbNone{$ENDIF},
    {$IFDEF CLR}ParamGCHandle, {$ENDIF}TMSAccessUtils.FIConnection(Connection).DBMSPrimaryVer, TMSAccessUtils.FIConnection(Connection).ProviderPrimaryVer);
  FParamsAccessorData.rgBindings[Col].eParamIO := DBPARAMIO_NOTPARAM;

  if not (ParamDesc.GetDataType in CharsByRef + BytesByRef) and not (ParamDesc.GetDataType in [dtUnknown]) then begin
    pLength := PUINT(UINT(Integer(FParamsAccessorData.ExecuteParams.pData)) + FParamsAccessorData.rgBindings[Col].obLength);
    Marshal.WriteInt32(pLength, FParamsAccessorData.rgBindings[Col].cbMaxLen);
  end;

  if Col = Columns.Count - 1 then
    TMSAccessUtils.FIConnection(Connection).Check(FIRowsetFastLoad.InsertRow(FParamsAccessorData.ExecuteParams.HACCESSOR,
      FParamsAccessorData.ExecuteParams.pData), Self);
end;

procedure TMSLoader.Commit(Done: boolean = True);
begin
  if FIRowsetFastLoad <> nil then
    TMSAccessUtils.FIConnection(Connection).Check(FIRowsetFastLoad.Commit(Done), Self);
end;

procedure TMSLoader.Finish;
begin
  inherited;

  try
    Commit;

    FParamDescs.Free;

    if FParamsAccessorData.ExecuteParams.pData <> nil then
      TMSAccessUtils.FIConnection(Connection).Check(FParamsAccessorData.Accessor.ReleaseAccessor(FParamsAccessorData.ExecuteParams.HACCESSOR, nil), Self);
  finally
    FParamsAccessorData.ExecuteParams.HACCESSOR := 0;
    FParamsAccessorData.Accessor := nil;
    if FParamsAccessorData.ExecuteParams.pData <> nil then
      Marshal.FreeHGlobal(FParamsAccessorData.ExecuteParams.pData);
    FParamsAccessorData.ExecuteParams.pData := nil;

    FIOpenRowset := nil;
    FIRowsetFastLoad := nil;
    Reset;
  end;
end;

{ TMSLoaderOptions }

constructor TMSLoaderOptions.Create(Owner: TMSLoader);
begin
  inherited Create;

  FOwner := Owner;
  FRowsPerBatch := 0;
  FKilobytesPerBatch := 0;
  FLockTable := False;
  FCheckConstraints := False;
end;

procedure TMSLoaderOptions.AssignTo(Dest: TPersistent);
begin
  if Dest is TMSLoaderOptions then begin
    TMSLoaderOptions(Dest).RowsPerBatch := RowsPerBatch;
    TMSLoaderOptions(Dest).KilobytesPerBatch := KilobytesPerBatch; 
    TMSLoaderOptions(Dest).LockTable := LockTable;
    TMSLoaderOptions(Dest).CheckConstraints := CheckConstraints;
  end
  else
    inherited;
end;

end.
