
{$IFNDEF CLR}
{$I Dac.inc}

unit DBToolsClient;
{$ENDIF}

interface

uses
{$IFDEF WIN32}
  CLRClasses,
  VarUtils,
  DesignIntf,
  ToolsApi,
{$ELSE}
  System.Runtime.InteropServices,
  Borland.Vcl.Design.DesignIntf,
  Borland.Studio.ToolsAPI,
  System.Reflection,
  System.Diagnostics,
  CoreLab.DbTools,
  System.Text,
{$ENDIF}
  ActiveX,
  DADesignUtils,
  Windows, Classes, Controls, StdCtrls, ExtCtrls, DB, DBAccess,
  DBToolsIntf, ActnMan, ActnList, Graphics, Menus, Forms, Messages;

type
{$IFDEF CLR}
  TString = string;
  IIDEServices = IOTAService;
  IModuleServices = IOTAModuleServices;
{$ELSE}
  TString = PWideChar;
  PConnectionInfo = ^ConnectionInfo;
  IIDEServices = IOTAServices;
  IModuleServices = IOTAModuleServices70;
{$ENDIF}

  TParamTypeMap = record
    DACType: TFieldType;
    DBToolsType: integer;
  end;

  TSqlSource = class;

  TDBToolsVerb = (dbtEditSql, dbtQueryBuilder,
    dbtFindInDatabaseExplorer, dbtEditDatabaseObject, dbtExecuteSql,
    dbtDebugSql, dbtRetrieveData, dbtCompile, dbtCompileDebug);

  TDBToolsVerbs = set of TDBToolsVerb;

  TCompareFlag = (cfNormal, cfCaseSensitive, cfNone);

  TDBToolsService = class(TObject)
  protected
    FUsedConnectionStrList: TStringList;
    FUsedConnectionCompareFlags: array of TCompareFlag;

    FConnectionStrList: TStringList;
    FConnectionsList: TStringList;
    FDefaultConnectionList: TStringList;
    FDADesignUtils: TDADesignUtilsClass;
    FSqlService: IDbToolsService;
    FSqlEditors: array of ISqlEditor;
    FParamTypeMaps: array of TParamTypeMap;

    FCurrentDesigner: IDesigner;
    FCurrentComponent: TComponent;

    function GetSqlEditor: ISqlEditor;
    procedure ReturnSqlEditor(ASqlEditor: ISqlEditor);
    function GetSqlSource(Component: TComponent; Designer: IDesigner; SqlTextPrefix: string = ''): TSqlSource;
  {$IFDEF WIN32}
    class function AccessData(const V: PSafeArray): pointer;
    class procedure UnaccessData(const V: PSafeArray);
    class function DataHigh(const V: PSafeArray): integer;
  {$ENDIF}
    class function GetConnectionParamStr(const ParamName, ParamValue: string): string;
    function GetConnectionValueStr(ConnectionName: string): string;
    procedure ConnStrToList(ConnStr: string; const ConnList: TStrings);
    procedure CheckConnection(const Component: TComponent);

    procedure BeginConnectionStrGetting(const ConnectionStrList: TStringList);
  public
    constructor Create(ADADesignUtils: TDADesignUtilsClass; ASqlService: {$IFDEF WIN32}IUnknown{$ELSE}TObject{$ENDIF};ADefaultConnectionStr: string);
    destructor Destroy; override;
    function DBToolsTypeToDataType(AType: integer; OldType: TFieldType): TFieldType;
    function DataTypeToDBToolsType(AType: TFieldType): integer;

    function GetConnection(const Component: TComponent): TCustomDAConnection;
    function GetNativeConnectionString(const Component: TComponent): string;
    function GetConnectionString(const Component: TComponent): TString;
    function GetConnectionStringObjectTypeAndFullName(const Component: TComponent; out ConnectionString, ObjectType, FullName: TString): boolean;

    procedure GetConnections(NameList: TStrings; Condition: string = '');
    function FindConnectionName(AConnection: TCustomDAConnection): string; //Call GetConnections
    function GetConnectionStrList(ConnectionName: string): TStringList; //before!

    procedure FindInDatabaseExplorer;
    procedure EditDatabaseObject;
    procedure ExecuteSql(Debug: boolean);
    procedure Compile(Debug: boolean);
    procedure RetrieveData(AsDocument: boolean);
    procedure EditSql(AsQuery: boolean);

    procedure AddParamTypeMap(ADACType: TFieldType; ADBToolsType: integer);

    procedure PutConnectionParam(const ConnectionParam: string; const CompareFlag: TCompareFlag = cfNormal);
    procedure SkipConnectionParams(const Count: integer);

    property DADesignUtils: TDADesignUtilsClass read FDADesignUtils;
    property SqlService: IDbToolsService read FSqlService;
  end;

  TDACSqlEditorFrame = class(TPanel)
  private
    FReadOnly: boolean;
    FOnExit: TNotifyEvent;
    FOnChange: TNotifyEvent;
    FDBToolsService: TDBToolsService;
    FSqlEditors: array[TStatementType] of ISqlEditor;
    FStatementType: TStatementType;
    FComponent: TComponent;
    FLastConnectionString: string;
    FInInit: boolean;

    function GetSqlEditor: ISqlEditor;
    procedure SetStatementType(const Value: TStatementType);
    function GetText: string;
    procedure SetText(const Value: string); {$IFDEF CLR} reintroduce; {$ENDIF}
    procedure SetReadOnly(Value: boolean);
    procedure InternalResize;
  protected
    procedure Resize; override;
    function GetSqlEditorHandle: HWND;
    procedure CheckModified;
    procedure WndProc(var Message: TMessage); override;
  public
    constructor Create(AOwner: TComponent; Component: TComponent; DBToolsService: TDBToolsService); reintroduce;
    destructor Destroy; override;
    procedure SetFocus; override;
    procedure CheckConnectionChange;
    procedure EndInit;

    property SqlEditor: ISqlEditor read GetSqlEditor;
    property Text: string read GetText write SetText;
    property OnExit: TNotifyEvent read FOnExit write FOnExit;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property ReadOnly: boolean read FReadOnly write SetReadOnly;
    property StatementType: TStatementType write SetStatementType;
  end;

  TDBToolsDesignNotification = class;

  TDBTools = class(TObject)
  private
    FIDEServices: IIDEServices;
    FModuleServices: IModuleServices;
    FDBToolsServices: array of TDBToolsService;
    FDesignNotification: TDBToolsDesignNotification;
    FCurrentDADesignUtils: TDADesignUtilsClass;
    FCurrentDBToolsService: TDBToolsService;
  {$IFDEF WIN32}
    FNeedUninitialize: boolean;
  {$ENDIF}
    FMenuManager: TActionManager;
    FMenuActions: array [TDBToolsVerb] of TAction;
    FLastDACSqlEditorFrame: TDACSqlEditorFrame;
    FLastMemo: TWinControl;
    FDACSqlEditorFrames: array of TDACSqlEditorFrame;

    function MenuItemsAvailable: boolean;
    procedure DebugSql(Sender: TObject);
    procedure EditDatabaseObject(Sender: TObject);
    procedure EditSql(Sender: TObject);
    procedure ExecuteSql(Sender: TObject);
    procedure Compile(Sender: TObject);
    procedure CompileDebug(Sender: TObject);
    procedure FindInDatabaseExplorer(Sender: TObject);
    procedure QueryBuilder(Sender: TObject);
    procedure RetrieveData(Sender: TObject);

    procedure CreateMenuActions;
    function GetMenuActions(Index: TDBToolsVerb): TAction;
  public
    property DesignNotification: TDBToolsDesignNotification read FDesignNotification;
    property IDEServices: IIDEServices read FIDEServices;
    property ModuleServices: IModuleServices read FModuleServices;

    constructor Create;
    destructor Destroy; override;
    function CreateDBToolsService(const DADesignUtils: TDADesignUtilsClass;
      const ClassID: TGUID;{$IFDEF CLR} const ServiceShortName: string; {$ENDIF}
      const DefaultConnectionStr: string; const ProviderKey: string;
      out ServiceVersion: int64; out NeedToCheck: TNeedToCheckDbTools): TObject;
    function CheckDevTools(const ClassID: TGUID; const ProviderKey: string; const DADesignUtils: TDADesignUtilsClass; out ServiceVersion: int64): TNeedToCheckDbTools;
    procedure CheckDBToolsChanges(Control: TWinControl);
    procedure ReplaceMemo(var Memo: TMemo; DADesignUtils: TDADesignUtilsClass; Component: TComponent);
    procedure DesignerClosing(const FileName: string);
    function GetDesignerName(Designer: IDesigner): string;
    procedure PrepareMenu(Designer: IDesigner; Component: TComponent; DADesignUtils: TDADesignUtilsClass);
    function HasDACSqlEditorFrame(Memo: TWinControl): boolean;
    function GetDACSqlEditorFrame(Memo: TWinControl): TDACSqlEditorFrame;
    procedure CheckConnectionChanges;
    function GetActiveDACSqlEditorFrame: TWinControl;

    property MenuActions[Index: TDBToolsVerb]: TAction read GetMenuActions;
  end;

  TSqlSource = class(TInterfacedObject, ISqlSource)
  protected
    FParameterCount: integer;
    FParameterSetted: array of boolean;
    FDesigner: IDesigner;
    FDesignerName: string;
    FComponent: TComponent;
    FLastName: string;
    FComponentSQL: string;
    FSqlSourceNotifier: ISqlSourceNotifier;
    FStatementType: TStatementType;
    FDBToolsService: TDBToolsService;
    FLastConnection: TCustomDAConnection;
    FLastConnectionString: string;
    FSqlTextPrefix: string;

    function GetParams: TDAParams;
    function GetSqlText: string;
    procedure SetSqlText(Value: string);

    function Get_Name: TString; {$IFDEF WIN32}stdcall;{$ENDIF}
    function Get_ConnectionString: TString; {$IFDEF WIN32}stdcall;{$ENDIF}
    function Get_DesignerName: TString; {$IFDEF WIN32}stdcall;{$ENDIF}
    function Get_Sql: TString; {$IFDEF WIN32}stdcall;{$ENDIF}
    procedure Set_Sql({$IFDEF WIN32}const {$ENDIF}Param1: TString); {$IFDEF WIN32}stdcall;{$ENDIF}
    procedure GetParameter({$IFDEF WIN32}const {$ENDIF}Index: integer; out Info: CommandParameterInfo); {$IFDEF WIN32}stdcall;{$ENDIF}
    procedure SetParameter({$IFDEF WIN32}const {$ENDIF}Index: integer; Info: CommandParameterInfo); {$IFDEF WIN32}stdcall;{$ENDIF}
    procedure Set_ParameterCount({$IFDEF WIN32}const {$ENDIF}Value: integer); {$IFDEF WIN32}stdcall;{$ENDIF}
    function Get_ParameterCount: integer; {$IFDEF WIN32}stdcall;{$ENDIF}

    procedure Close; {$IFDEF WIN32}stdcall;{$ENDIF}

    property Params: TDAParams read GetParams;
  public
    constructor Create(DBToolsService: TDBToolsService; Component: TComponent; Designer: IDesigner);
    destructor Destroy; override;
    procedure CheckRename;
    procedure CheckConnectionChange(InternalCheck: boolean);
    procedure CheckChanges;

    property SqlSourceNotifier: ISqlSourceNotifier read FSqlSourceNotifier;
    property Designer: IDesigner read FDesigner;
    property SqlTextPrefix: string read FSqlTextPrefix write FSqlTextPrefix;
  end;

  TSqlSourceList = class(TList)
  private
    FNotifiersToDelete: array of ISqlSourceNotifier;
  public
    function IndexOf(Component: TComponent): integer; reintroduce;
    function Find(Component: TComponent): TSqlSource; overload;
    function Find(Component: TComponent; SqlTextPrefix: string): TSqlSource; overload;
    function FindDesigner(FileName: string): IDesigner;
    procedure CheckSubordinated(AComponent: TComponent);
    procedure Delete(Index: integer; WithNotification: boolean = False); reintroduce; overload;
    procedure Delete(Component: TComponent; WithNotification: boolean = True); overload;
    procedure Delete(SqlSource: TSqlSource); overload;
    procedure Add(SqlSource: TSqlSource);
    procedure DeleteDesigner(Designer: IDesigner);
    procedure CheckDeletedComponents(Designer: IDesigner);    
  end;

  TModuleNotifier = class({$IFDEF CLR}TObject{$ELSE}TInterfacedObject, IOTANotifier, IOTAModuleNotifier, IOTAModuleNotifier80{$ENDIF})
  private
    FModule : IOTAModule;
  {$IFDEF WIN32}
    AllowSaveSwitch : boolean;
    FIndex : integer;
  {$ELSE}
    procedure BeforeSave(Sender: TObject; e: EventArgs);
  {$ENDIF}
    procedure RemoveNotifier;
  public
    constructor Create(const Module: IOTAModule);
    destructor Destroy; override;
  {$IFDEF WIN32}
  {IOTANotifier}
    procedure AfterSave;
    procedure BeforeSave;
    procedure Destroyed;
    procedure Modified;
  {IOTAModuleNotifier}
    function CheckOverwrite: Boolean;
    procedure ModuleRenamed(const NewName: string);
  {IOTAModuleNotifier80}
    function AllowSave: Boolean;
    function GetOverwriteFileNameCount: Integer;
    function GetOverwriteFileName(Index: Integer): string;
    procedure SetSaveFileName(const FileName: string);
  {$ENDIF}
    property Module: IOTAModule read FModule;
  end;

  TModuleList = class(TList)
  public
    function IndexOf(Module: IOTAModule): integer; reintroduce;
    procedure Add(Module: IOTAModule);
    procedure Delete(Index: integer); reintroduce; overload;
    procedure Delete(Module: IOTAModule); overload;
  end;

  TDBToolsDesignNotification = class(TInterfacedObject, IDesignNotification)
  protected
    FSqlSourceList: TSqlSourceList;
    FModuleList: TModuleList;
    FDebugStr: string;
  public
    constructor Create;
    destructor Destroy; override;
    procedure ItemDeleted(const ADesigner: IDesigner; AItem: TPersistent);
    procedure ItemInserted(const ADesigner: IDesigner; AItem: TPersistent);
    procedure ItemsModified(const ADesigner: IDesigner);
    procedure SelectionChanged(const ADesigner: IDesigner; const ASelection: IDesignerSelections);
    procedure DesignerOpened(const ADesigner: IDesigner; AResurrecting: Boolean);
    procedure DesignerClosed(const ADesigner: IDesigner; AGoingDormant: Boolean);

    property SqlSourceList: TSqlSourceList read FSqlSourceList;
    property ModuleList: TModuleList read FModuleList;
  end;

var
  DBTools: TDBTools;

function GetDBToolsService(DADesignUtilsClass: TDADesignUtilsClass): TDBToolsService;

implementation

{$IFDEF WIN32}
{$R DBToolsClient.res}
{$ENDIF}
uses
  DADesign, Registry, Download, DAConsts,
  Variants, SysUtils, ComObj, TypInfo;

const
  SConnectionName = '_ConnName_';
  SConnStrError = 'ConnectionString error';
  SCompilePrefix = 'compile';
  SEditPrefix = 'edit';  

{$IFDEF WIN32}
  function ToWideChar(s: WideString): PWideChar;
  begin
    if s = '' then    
      Result := nil
    else
      Result := SysAllocString(PWideChar(s));
  end;
{$ENDIF}

{ TSqlSourceList }

procedure TSqlSourceList.Add(SqlSource: TSqlSource);
var
  ModuleFileName, s: string;
  Module: IOTAModule;
begin
  SqlSource.Designer.ModuleFileNames(ModuleFileName, s, s);

  inherited Add(SqlSource);

  Module := DBTools.ModuleServices.FindModule(ModuleFileName);
  DBTools.DesignNotification.ModuleList.Add(Module)
end;

function TSqlSourceList.IndexOf(Component: TComponent): integer;
var
  i: integer;
begin
  Result := -1;
  for i := 0 to Count - 1 do
    if TSqlSource(Items[i]).FComponent = Component then begin
      Result := i;
      Break;
    end;
end;

function TSqlSourceList.Find(Component: TComponent): TSqlSource;
var
  i: integer;
begin
  i := IndexOf(Component);
  if i < 0 then
    Result := nil
  else
    Result := TSqlSource(Items[i]);
end;

function TSqlSourceList.Find(Component: TComponent; SqlTextPrefix: string): TSqlSource;
var
  i: integer;
begin
  for i := 0 to Count - 1 do
    if (TSqlSource(Items[i]).FComponent = Component) and
      (TSqlSource(Items[i]).SqlTextPrefix = SqlTextPrefix) then begin
      Result := TSqlSource(Items[i]);
      Exit;
    end;
  Result := nil;
end;

procedure TSqlSourceList.CheckSubordinated(AComponent: TComponent);
var
  i: integer;
begin
  if AComponent is TCustomDAConnection then begin
    for i := 0 to Count - 1 do
      with TSqlSource(Items[i]) do
        if FDBToolsService.GetConnection(FComponent) = AComponent then
          CheckConnectionChange(False);
  end
  else
    for i := 0 to Count - 1 do
      if TSqlSource(Items[i]).FComponent.Owner = AComponent then
        TSqlSource(Items[i]).CheckRename;
end;

procedure TSqlSourceList.Delete(Index: integer; WithNotification: boolean = False);
var
  n: integer;
  SqlSource: TSqlSource;
begin
  SqlSource := TSqlSource(Items[Index]);
  if WithNotification and (SqlSource.FSqlSourceNotifier <> nil) then begin
    n := Length(FNotifiersToDelete);
    SetLength(FNotifiersToDelete, n + 1);
    FNotifiersToDelete[n] := SqlSource.FSqlSourceNotifier;
    SqlSource.FSqlSourceNotifier := nil;
    inherited Delete(Index);
  end
  else
    inherited Delete(Index);
end;

procedure TSqlSourceList.Delete(Component: TComponent; WithNotification: boolean = True);
var
  Index: integer;
begin
  Index := IndexOf(Component);
  if Index >= 0 then
    Delete(Index, WithNotification);
end;

procedure TSqlSourceList.Delete(SqlSource: TSqlSource);
var
  i: integer;
begin
  for i := 0 to Count - 1 do
    if TSqlSource(Items[i]) = SqlSource then begin
      inherited Delete(i);
      Break;
    end;
end;

procedure TSqlSourceList.DeleteDesigner(Designer: IDesigner);
var
  i: integer;
  SqlSource: TSqlSource;
begin
  i := 0;
  while i < Count do begin
    SqlSource := TSqlSource(Items[i]);
    if SqlSource.Designer = Designer then begin
      if SqlSource.FSqlSourceNotifier <> nil then begin
        SqlSource.FSqlSourceNotifier.OnSqlSourceDeleted;
        SqlSource.FSqlSourceNotifier := nil;
      end;
      inherited Delete(i);
    end
    else
      Inc(i);
  end;
end;

procedure TSqlSourceList.CheckDeletedComponents(Designer: IDesigner);
var
  i, n: integer;
  Notifier: ISqlSourceNotifier;
  Sel: IDesignerSelections;
begin
  n := Length(FNotifiersToDelete);
  if n <= 0 then
    Exit;
  Sel := CreateSelectionList;
  Sel.Add(Designer.Root);
  Designer.SetSelections(Sel);
  Designer.Activate;
  for i := n - 1 downto 0 do begin
    Notifier := FNotifiersToDelete[i];
    SetLength(FNotifiersToDelete, i);
    Notifier.OnSqlSourceDeleted;
  end;
end;

function TSqlSourceList.FindDesigner(FileName: string): IDesigner;
var
  i: integer;
  ModuleFileName, s: string;
begin
  for i := 0 to Count - 1 do begin
    TSqlSource(Items[i]).Designer.ModuleFileNames(ModuleFileName, s, s);
    if ModuleFileName = FileName then begin
      Result := TSqlSource(Items[i]).Designer;
      Exit;
    end;
  end;
  Result := nil;
end;

{ TDBToolsDesignNotification }

constructor TDBToolsDesignNotification.Create;
begin
  inherited;
  FSqlSourceList := TSqlSourceList.Create;
  FModuleList := TModuleList.Create;
end;

procedure TDBToolsDesignNotification.DesignerClosed(const ADesigner: IDesigner;
  AGoingDormant: Boolean);
begin
  if ADesigner <> nil then
    DBTools.DesignNotification.SqlSourceList.DeleteDesigner(ADesigner);
end;

procedure TDBToolsDesignNotification.DesignerOpened(const ADesigner: IDesigner;
  AResurrecting: Boolean);
begin
end;

destructor TDBToolsDesignNotification.Destroy;
var
  i, n: integer;
begin
  n := FSqlSourceList.Count;
  for i := n - 1 downto 0 do
    FSqlSourceList.Delete(i);
  FSqlSourceList.Free;

  n := FModuleList.Count;
  for i := n - 1 downto 0 do begin
  {$IFDEF CLR}
    TModuleNotifier(FModuleList.Items[i]).Free;
  {$ENDIF}
    FModuleList.Delete(i);
  end;
  FModuleList.Free;
end;

procedure TDBToolsDesignNotification.ItemDeleted(const ADesigner: IDesigner;
  AItem: TPersistent);
begin
  if AItem is TComponent then
    FSqlSourceList.Delete(TComponent(AItem));
  FSqlSourceList.CheckDeletedComponents(ADesigner);
end;

procedure TDBToolsDesignNotification.ItemInserted(const ADesigner: IDesigner;
  AItem: TPersistent);
begin
end;

procedure TDBToolsDesignNotification.ItemsModified(const ADesigner: IDesigner);
var
  i: Integer;
  SqlSource: TSqlSource;
  SelectionList: IDesignerSelections;
  Component: TComponent;
begin
  if ADesigner = nil then
    Exit;
  SelectionList := CreateSelectionList;
  ADesigner.GetSelections(SelectionList);
  for i := 0 to SelectionList.Count - 1 do begin
    if SelectionList.Items[i] is TComponent then begin
      Component := TComponent(SelectionList.Items[i]);
      SqlSource := FSqlSourceList.Find(Component);
      if SqlSource <> nil then
        SqlSource.CheckChanges
      else
        FSqlSourceList.CheckSubordinated(Component);
    end;
  end;
end;

procedure TDBToolsDesignNotification.SelectionChanged(const ADesigner: IDesigner;
  const ASelection: IDesignerSelections);
begin
end;

{ TSqlSource }

procedure TSqlSource.Close;
begin
  Assert(DBTools.DesignNotification <> nil);
  DBTools.DesignNotification.SqlSourceList.Delete(FComponent, False);
end;

constructor TSqlSource.Create(DBToolsService: TDBToolsService; Component: TComponent; Designer: IDesigner);
begin
  inherited Create;
  Assert(DBToolsService <> nil);
  FComponent := Component;
  FDBToolsService := DBToolsService;
  FStatementType := stQuery;
  FLastName := Get_Name;
  FComponentSQL := GetSqlText;
  FDesigner := Designer;
  FDesignerName := DBTools.GetDesignerName(FDesigner);
  FLastConnection := FDBToolsService.GetConnection(Component);
  FLastConnectionString := FDBToolsService.GetNativeConnectionString(FLastConnection);
end;

destructor TSqlSource.Destroy;
begin
  inherited;
end;

function TSqlSource.GetParams: TDAParams;
begin
  with FDBToolsService.DADesignUtils do
    if HasParams(FComponent) then
      Result := GetParams(FComponent)
    else
      Result := nil;
end;

function TSqlSource.Get_ConnectionString: TString;
begin
  Result := {$IFDEF WIN32}ToWideChar({$ENDIF}
    FLastConnectionString
  {$IFDEF WIN32}){$ENDIF};
end;

function TSqlSource.Get_DesignerName: TString;
begin
  Result := {$IFDEF WIN32}ToWideChar(FDesignerName){$ELSE}FDesignerName{$ENDIF};
end;

function TSqlSource.Get_Name: TString;
begin
  Result := {$IFDEF WIN32}ToWideChar({$ENDIF}
    FComponent.Owner.Name + '-' + FComponent.Name
  {$IFDEF WIN32}){$ENDIF};
end;

function TSqlSource.GetSqlText: string;
var
  SQL: TStrings;
  Macros: TMacros;
  NewMacros: TDesignMacros;
begin
  with FDBToolsService.DADesignUtils do
    if IsStoredProc(FComponent) then begin
      Result := Trim(GetFullName(FComponent));
      if Result <> '' then
        Result := GetObjectType(FComponent) + ':' + Result;
    end
    else begin
      SQL := GetSQL(FComponent);
      Macros := GetMacros(FComponent);
      NewMacros := TDesignMacros.Create(nil);
      try
        NewMacros.SetParserClass(TDBAccessUtils.GetParserClass(Macros));
        NewMacros.Assign(Macros);
        Result := SQL.Text;
        NewMacros.Expand(Result);        
      finally
        NewMacros.Free;
      end;
    end;
  if (FSqlTextPrefix <> '') and (FSqlTextPrefix <> SEditPrefix) then
    Result := FSqlTextPrefix + ':' + Result;
end;

procedure TSqlSource.SetSqlText(Value: string);
var
  SQL: TStrings;
  Macros: TMacros;
  NewMacros: TDesignMacros;
begin
  with FDBToolsService.DADesignUtils do
    if not IsStoredProc(FComponent) then begin
      SQL := GetSQL(FComponent);
      Macros := GetMacros(FComponent);
      NewMacros := TDesignMacros.Create(nil);
      try
        NewMacros.SetParserClass(TDBAccessUtils.GetParserClass(Macros));
        NewMacros.Scan(Value);
        SQL.Text := Value;
        Macros.Assign(NewMacros);
      finally
        NewMacros.Free;
      end;
    end;
end;

function TSqlSource.Get_Sql: TString;
begin
  Result := {$IFDEF WIN32}ToWideChar({$ENDIF}
    WideString(GetSqlText)
  {$IFDEF WIN32}){$ENDIF};
end;

procedure TSqlSource.Set_Sql({$IFDEF WIN32}const {$ENDIF}Param1: TString);
begin
  SetSqlText(Param1);
end;

procedure TSqlSource.GetParameter({$IFDEF WIN32}const {$ENDIF}Index: integer; out Info: CommandParameterInfo);
begin
  Assert(Index < Get_ParameterCount);
  Info.Name := {$IFDEF WIN32}ToWideChar({$ENDIF}Params[Index].Name
    {$IFDEF WIN32}){$ENDIF};
  Info.DataType := FDBToolsService.DataTypeToDBToolsType(Params[Index].DataType);
  Info.Value := Params[Index].Value;
  case Params[Index].ParamType of
    ptUnknown,
    ptInput:
      Info.ParameterType := ParameterType_Input;
    ptOutput:
      Info.ParameterType := ParameterType_Output;
    ptInputOutput:
      Info.ParameterType := ParameterType_InputOutput;
    ptResult:
      Info.ParameterType := ParameterType_ReturnValue;
  end;
end;

function TSqlSource.Get_ParameterCount: integer;
begin
  if (Params <> nil) and (Params.Count > FParameterCount) then
    Set_ParameterCount(Params.Count);
  Result := FParameterCount;
end;

procedure TSqlSource.Set_ParameterCount({$IFDEF WIN32}const {$ENDIF}Value: integer);
var
  i, n: integer;
begin
  if Params = nil then
    Exit;
  FParameterCount := Value;
  SetLength(FParameterSetted, Value);
  n := Params.Count;
  for i := 0 to Value - 1 do begin
    if i >= n then
      Params.Add;
    FParameterSetted[i] := False;
  end;
end;

procedure TSqlSource.SetParameter({$IFDEF WIN32}const {$ENDIF}Index: integer; Info: CommandParameterInfo);
var
  i, j: integer;
  TempParam: TDAParam;
begin
  Assert(Index < Get_ParameterCount);
  for i := 0 to Params.Count - 1 do
    if Params[i].Name = Info.Name then begin
      if i <> Index then begin
        TempParam := Params[Index];
        Params[Index] := Params[i];
        Params[i] := TempParam;
      end;
      Break;
    end;
  with TDAParam(Params[Index]) do begin
    Name := Info.Name;
    DataType := FDBToolsService.DBToolsTypeToDataType(Info.DataType, DataType);
    Value := Variant(Info.Value);
    case Info.ParameterType of
      ParameterType_Input:
        ParamType := ptInput;
      ParameterType_Output:
        ParamType := ptOutput;
      ParameterType_InputOutput:
        ParamType := ptInputOutput;
      ParameterType_ReturnValue:
        ParamType := ptResult;
    end;
  end;
  for i := FParameterCount - 1 downto 0 do
    if not FParameterSetted[i] then
      Break
    else
      if i = 0 then
        for j := Params.Count - 1 downto FParameterCount do
          Params.Delete(j);
end;

procedure TSqlSource.CheckRename;
begin
  if (FLastName <> Get_Name) then begin
    if FSqlSourceNotifier <> nil then
      FSqlSourceNotifier.OnSqlSourceRenamed(Get_Name);
    FLastName := Get_Name;
  end;
end;

procedure TSqlSource.CheckConnectionChange(InternalCheck: boolean);
var
  NewConnection: TCustomDAConnection;
  NewConnectionString: string;
begin
  NewConnection := FDBToolsService.GetConnection(FComponent);
  if InternalCheck and (FLastConnection = NewConnection) then
    Exit;
  NewConnectionString := FDBToolsService.GetNativeConnectionString(NewConnection);
  if NewConnectionString <> FLastConnectionString then begin
    FLastConnectionString := NewConnectionString;
    if not InternalCheck and (FSqlSourceNotifier <> nil) then
      FSqlSourceNotifier.OnSqlSourceChanged;
  end;
end;

procedure TSqlSource.CheckChanges;
begin
  CheckRename;
  CheckConnectionChange(True);
  if GetSqlText <> FComponentSQL then begin
    if FSqlSourceNotifier <> nil then
      FSqlSourceNotifier.OnSqlSourceChanged;
    FComponentSQL := GetSqlText;
  end;
end;

{ TDACSqlEditorFrame }

procedure TDACSqlEditorFrame.CheckModified;
begin
  if (SqlEditor <> nil) and (SqlEditor.Modified) then begin
    if Assigned(FOnChange) then
      FOnChange(Self);
    if Assigned(FOnExit) then
      FOnExit(Self);
    SqlEditor.Modified := False;
  end;
end;

constructor TDACSqlEditorFrame.Create(AOwner: TComponent; Component: TComponent; DBToolsService: TDBToolsService);
var
  i: integer;
begin
  inherited Create(AOwner);

  FInInit := True;
  FComponent := Component;
  FDBToolsService := DBToolsService;
  BevelOuter := bvNone;
  FStatementType := stQuery;
  TabStop := True;
  i := Length(DBTools.FDACSqlEditorFrames);
  SetLength(DBTools.FDACSqlEditorFrames, i + 1);
  DBTools.FDACSqlEditorFrames[i] := Self;
end;

destructor TDACSqlEditorFrame.Destroy;
var
  st: TStatementType;
  i, j, n: integer;
begin
  for st := Low(TStatementType) to High(TStatementType) do
    if FSqlEditors[st] <> nil then
      FDBToolsService.ReturnSqlEditor(FSqlEditors[st]);
  n := High(DBTools.FDACSqlEditorFrames);
  for i := 0 to n do
    if DBTools.FDACSqlEditorFrames[i] = Self then begin
      for j := i to n - 1 do
        DBTools.FDACSqlEditorFrames[j] := DBTools.FDACSqlEditorFrames[j + 1];
      SetLength(DBTools.FDACSqlEditorFrames, n);
      Break;
    end;

  inherited;
end;

procedure TDACSqlEditorFrame.EndInit;
begin
  FInInit := False;
end;

procedure TDACSqlEditorFrame.InternalResize;
begin
  if SqlEditor <> nil then
    Windows.SetWindowPos(GetSqlEditorHandle, 0, 0, 0, ClientWidth, ClientHeight, SWP_NOZORDER or SWP_SHOWWINDOW);
end;

procedure TDACSqlEditorFrame.Resize;
begin
  InternalResize;
  inherited;
end;

procedure TDACSqlEditorFrame.SetFocus;
begin
  inherited;

  if SqlEditor <> nil then
    Windows.SetFocus(GetSqlEditorHandle);
end;

procedure TDACSqlEditorFrame.WndProc(var Message: TMessage);
begin
  if not FInInit and (Message.Msg = WM_SETFOCUS) and (SqlEditor <> nil) then
    Windows.SetFocus(GetSqlEditorHandle)
  else
    inherited;
end;

procedure TDACSqlEditorFrame.CheckConnectionChange;
var
  NewConnectionString: string;
  st: TStatementType;
begin
  Assert(SqlEditor <> nil);

  NewConnectionString := FDBToolsService.GetNativeConnectionString(FComponent);
  if NewConnectionString <> FLastConnectionString then begin
    FLastConnectionString := NewConnectionString;

    if FLastConnectionString <> '' then
      for st := Low(TStatementType) to High(TStatementType) do
        if FSqlEditors[st] <> nil then
          FSqlEditors[st].SetConnection({$IFDEF WIN32}ToWideChar({$ENDIF}
            FLastConnectionString
          {$IFDEF WIN32}){$ENDIF});
  end;
end;

function TDACSqlEditorFrame.GetSqlEditorHandle: HWND;
begin
  Assert(SqlEditor <> nil);

  Result := SqlEditor.Handle{$IFDEF CLR}.ToInt32{$ENDIF};
end;

function TDACSqlEditorFrame.GetText: string;
begin
  if SqlEditor <> nil then
    Result := SqlEditor.Text
  else
    Result := '';
end;

procedure TDACSqlEditorFrame.SetText(const Value: string);
begin
  if SqlEditor <> nil then
    SqlEditor.Text := {$IFDEF WIN32}ToWideChar(Value){$ELSE}Value{$ENDIF};
end;

procedure TDACSqlEditorFrame.SetReadOnly(Value: boolean);
var
  st: TStatementType;
begin
  if Value <> FReadOnly then begin
    FReadOnly := Value;
    for st := Low(TStatementType) to High(TStatementType) do
       if FSqlEditors[st] <> nil then
          FSqlEditors[st].ReadOnly := Value;
    SetStatementType(FStatementType);
  end;
end;

function TDACSqlEditorFrame.GetSqlEditor: ISqlEditor;
begin
  if (not ReadOnly) or (FSqlEditors[FStatementType] <> nil) then
    Result := FSqlEditors[FStatementType]
  else
    Result := FSqlEditors[stQuery];
end;

procedure TDACSqlEditorFrame.SetStatementType(const Value: TStatementType);
var
  NewStatementType: TStatementType;
  FHide, FShow, FFocused: boolean;
begin
  if FReadOnly and (FSqlEditors[Value] = nil) then
    NewStatementType := stQuery
  else
    NewStatementType := Value;
  FHide := NewStatementType <> FStatementType;
  FShow := FHide;
  if FSqlEditors[NewStatementType] = nil then
    if (FSqlEditors[stQuery] <> nil) and (FSqlEditors[stQuery].Text = '') then begin
      FSqlEditors[NewStatementType] := FSqlEditors[stQuery];
      FSqlEditors[stQuery] := nil;
      FHide := False;
      FShow := False;
    end
    else begin
      FSqlEditors[NewStatementType] := FDBToolsService.GetSqlEditor;
      if FLastConnectionString <> '' then
        FSqlEditors[NewStatementType].SetConnection({$IFDEF WIN32}ToWideChar({$ENDIF}
          FLastConnectionString
        {$IFDEF WIN32}){$ENDIF});
      FSqlEditors[NewStatementType].Text := {$IFDEF WIN32}nil{$ELSE}''{$ENDIF};
      FShow := True;
    end;
  if FHide and (FSqlEditors[NewStatementType] <> nil) then begin
    Windows.SetParent(GetSqlEditorHandle, MAXDWORD - 2{HWND_MESSAGE});
    FFocused := Windows.GetFocus = GetSqlEditorHandle;
  end
  else
    FFocused := False;

  FStatementType := Value;
  if FShow then begin
    Windows.SetParent(GetSqlEditorHandle, Handle);
    FSqlEditors[NewStatementType].ReadOnly := FReadOnly;
    InternalResize;
    if FFocused then
      Windows.SetFocus(GetSqlEditorHandle);
  end;
end;

{ TDBTools }

procedure TDBTools.CheckDBToolsChanges(Control: TWinControl);
var
  i: integer;
begin
  if Control <> nil then
    for i := 0 to Control.ComponentCount - 1 do
      if Control.Components[i] is TDACSqlEditorFrame then
        TDACSqlEditorFrame(Control.Components[i]).CheckModified;
end;

function TDBTools.CreateDBToolsService(const DADesignUtils: TDADesignUtilsClass;
      const ClassID: TGUID;{$IFDEF CLR} const ServiceShortName: string; {$ENDIF}
      const DefaultConnectionStr: string; const ProviderKey: string;
      out ServiceVersion: int64; out NeedToCheck: TNeedToCheckDbTools): TObject;
var
  n: integer;
{$IFDEF CLR}
  ServiceType: System.Type;
  Obj: TObject;
{$ELSE}
  Obj: IUnknown;
{$ENDIF}
  DBToolsService: TDBToolsService;

{$IFDEF CLR}
  function FindType(const TypeFullName, AssemblyNameFilter: string; out AType: System.Type): boolean;
  var
    i, j: integer;
    LoadedAssemblies: array of Assembly;
    Types: array of System.Type;
  begin
    Result := False;
    LoadedAssemblies := System.AppDomain.CurrentDomain.GetAssemblies;
    for i := 0 to Length(LoadedAssemblies) - 1 do
      if (LoadedAssemblies[i].FullName.ToLower.IndexOf(AssemblyNameFilter.ToLower) >= 0) then begin
        Types := LoadedAssemblies[i].GetExportedTypes;
        for j := 0 to Length(Types) - 1 do
          if Types[j].FullName = TypeFullName then begin
            AType := Types[j];
            Result := True;
            Exit;
          end;
      end;
  end;
{$ENDIF}

begin
  Result := nil;
  Obj := nil;
  try
    NeedToCheck := ncNoAddin;
    ServiceVersion := 0;
    DADesignUtils.SetDBToolsDownloadParams(False, False);
    if FIDEServices = nil then begin
    {$IFDEF WIN32}
      Supports(BorlandIDEServices, IIDEServices, FIDEServices);
      Supports(BorlandIDEServices, IModuleServices, FModuleServices);
    {$ELSE}
      FIDEServices := BorlandIDE.OTAService;
      FModuleServices := BorlandIDE.ModuleServices;
    {$ENDIF}
    end;

    NeedToCheck := CheckDevTools(ClassID, ProviderKey, DADesignUtils, ServiceVersion);
    if NeedToCheck <> ncNone then
      Exit;
  {$IFDEF CLR}
    if FindType('CoreLab.DbTools.' + ServiceShortName + '.' + ServiceShortName + 'DbToolsService', ServiceShortName, ServiceType) then
  {$ENDIF}
    begin
    {$IFDEF WIN32}
      if not FNeedUninitialize then
        FNeedUninitialize := Succeeded(CoInitialize(nil));
      Obj := CreateComObject(ClassID);
    {$ELSE}
      Obj := BorlandIDE.GetService(ServiceType);
    {$ENDIF}
    end;
    if Obj = nil then begin
      NeedToCheck := ncExpired;
      Exit;
    end
    else begin
      if FDesignNotification = nil then begin
        FDesignNotification := TDBToolsDesignNotification.Create;
        RegisterDesignNotification(FDesignNotification);
      end;
      DBToolsService := TDBToolsService.Create(DADesignUtils, Obj, DefaultConnectionStr);
      Result := DBToolsService;
      n := Length(FDBToolsServices);
      SetLength(FDBToolsServices, n + 1);
      FDBToolsServices[n] := DBToolsService;
    end;
  except
    NeedToCheck := ncExpired;
  end;
end;

function TDBTools.CheckDevTools(const ClassID: TGUID; const ProviderKey: string; const DADesignUtils: TDADesignUtilsClass; out ServiceVersion: int64): TNeedToCheckDbTools;

  function GetFileVersion(Filename: string): int64;
  var
  {$IFDEF CLR}
    VersionInfo: FileVersionInfo;
  {$ELSE}
    Handle,
    FixedFileInfoLen,
    VersionDataLen: DWORD;
    VersionData: TBytes;
    FixedFileInfo: ^VS_FIXEDFILEINFO;
  {$ENDIF}
  begin
    Result := 0;
    if FileExists(Filename) then begin
    {$IFDEF CLR}
      VersionInfo := FileVersionInfo.GetVersionInfo(Filename);
      Result :=
        VersionInfo.FileMajorPart * $1000000000000 +
        VersionInfo.FileMinorPart * $100000000 +
        VersionInfo.FileBuildPart * $10000 +
        VersionInfo.FilePrivatePart;
    {$ELSE}
      VersionDataLen := GetFileVersionInfoSize(PChar(Filename), Handle);
      SetLength(VersionData, VersionDataLen);
      if GetFileVersionInfo(PChar(Filename), Handle, VersionDataLen, VersionData) and
        VerQueryValue(VersionData, '\', Pointer(FixedFileInfo), FixedFileInfoLen) then
        Result := FixedFileInfo.dwFileVersionMS * $100000000 + FixedFileInfo.dwFileVersionLS;
    {$ENDIF}
    end;
  end;

begin
  Result := ncNoAddin;
  ServiceVersion := 0;
  with TRegistry.Create do
    try
      try
        RootKey := HKEY_CLASSES_ROOT;
        if OpenKey({$IFDEF WIN32}'CLSID\' + GUIDToString(ClassID) + '\InprocServer32'
          {$ELSE}'CLSID\{' + GUIDToString(ClassID) + '}\InprocServer32'{$ENDIF}, False)
          and ValueExists('CodeBase') then begin
          Result := ncIncompatible;
          ServiceVersion := GetFileVersion(ReadString('CodeBase'));
          CloseKey;
          if ServiceVersion <> 0 then
            Result := ncNone;
        end
        else begin
          RootKey := HKEY_LOCAL_MACHINE;
          if OpenKey('SOFTWARE\CoreLab\Database Developer Tools\DatabaseProviders\' + ProviderKey, False)
            and ValueExists('Assembly') then begin
            Result := ncIncompatible;
            ServiceVersion := GetFileVersion(ReadString('Assembly'));
            CloseKey;
          end;
        end;
      except
        //Silent
      end;
    finally
      Free;
      if Result <> ncNone then begin
        DADesignUtils.SetDBToolsDownloadParams(False, Result = ncIncompatible);
        CheckForTools(Result = ncIncompatible);
      end;
    end;
end;

function TDBTools.MenuItemsAvailable: boolean;
var
  Incompatible: boolean;
begin
  Result := FCurrentDADesignUtils.DBToolsAvailable;
  Incompatible := FCurrentDADesignUtils.NeedToCheckDbTools = ncIncompatible;
  if not Result then begin
    FCurrentDADesignUtils.SetDBToolsDownloadParams(True, Incompatible);
    CheckForTools(Incompatible);
  end;
end;

procedure TDBTools.EditSql(Sender: TObject);
begin
  if MenuItemsAvailable then
    FCurrentDBToolsService.EditSql(False);
end;

procedure TDBTools.QueryBuilder(Sender: TObject);
begin
  if MenuItemsAvailable then
    FCurrentDBToolsService.EditSql(True);
end;

procedure TDBTools.FindInDatabaseExplorer(Sender: TObject);
begin
  if MenuItemsAvailable then
    FCurrentDBToolsService.FindInDatabaseExplorer;
end;

procedure TDBTools.EditDatabaseObject(Sender: TObject);
begin
  if MenuItemsAvailable then
    FCurrentDBToolsService.EditDatabaseObject;
end;

procedure TDBTools.ExecuteSql(Sender: TObject);
begin
  if MenuItemsAvailable then
    FCurrentDBToolsService.ExecuteSql(False);
end;

procedure TDBTools.Compile(Sender: TObject);
begin
  if MenuItemsAvailable then
    FCurrentDBToolsService.Compile(False);
end;

procedure TDBTools.CompileDebug(Sender: TObject);
begin
  if MenuItemsAvailable then
    FCurrentDBToolsService.Compile(True);
end;

procedure TDBTools.DebugSql(Sender: TObject);
begin
  if MenuItemsAvailable then
    FCurrentDBToolsService.ExecuteSql(True);
end;

procedure TDBTools.RetrieveData(Sender: TObject);
begin
  if MenuItemsAvailable then
    FCurrentDBToolsService.RetrieveData(True);
end;

procedure TDBTools.PrepareMenu(Designer: IDesigner; Component: TComponent; DADesignUtils: TDADesignUtilsClass);
begin
  FCurrentDADesignUtils := DADesignUtils;
  FCurrentDBToolsService := GetDBToolsService(DADesignUtils);
  if FCurrentDBToolsService <> nil then
    with FCurrentDBToolsService do begin
      FCurrentDesigner := Designer;
      FCurrentComponent := Component;
    end;
end;

function TDBTools.HasDACSqlEditorFrame(Memo: TWinControl): boolean;
begin
  FLastMemo := Memo;
  FLastDACSqlEditorFrame := TDACSqlEditorFrame(Memo.Owner.FindComponent(Memo.Name + '_dbt'));
  Result := FLastDACSqlEditorFrame <> nil;
end;

function TDBTools.GetDACSqlEditorFrame(Memo: TWinControl): TDACSqlEditorFrame;
begin
  if Memo <> FLastMemo then
    HasDACSqlEditorFrame(Memo);
  Result := FLastDACSqlEditorFrame;
end;

procedure TDBTools.CheckConnectionChanges;
var
  i: integer;
begin
  for i := 0 to High(FDACSqlEditorFrames) do
    FDACSqlEditorFrames[i].CheckConnectionChange;
end;

function TDBTools.GetActiveDACSqlEditorFrame: TWinControl;
var
  i: integer;
  Handle: HWND;
  ClassName: {$IFDEF WIN32}PChar{$ELSE}StringBuilder{$ENDIF};
begin
  Result := nil;
{$IFDEF WIN32}
  GetMem(ClassName, 1024);
{$ELSE}
  ClassName := StringBuilder.Create;
  ClassName.Capacity := 1024;
{$ENDIF}
  try
    Handle := GetFocus;
    repeat
      Handle := GetParent(Handle);
      if Handle = 0 then
        Exit;
      GetClassName(Handle, ClassName, 1024);
  {$IFDEF WIN32}
    until ClassName = TDACSqlEditorFrame.ClassName;
  {$ELSE}
    until Pos(TDACSqlEditorFrame.ClassName, ClassName.ToString) <> 0;
  {$ENDIF}

    for i := 0 to High(FDACSqlEditorFrames) do
      if FDACSqlEditorFrames[i].Handle = Handle then begin
        Result := FDACSqlEditorFrames[i];
        Break;
      end;
  finally
  {$IFDEF WIN32}
    FreeMem(ClassName);
  {$ELSE}
    ClassName.Free;
  {$ENDIF}
  end;
end;

procedure TDBTools.CreateMenuActions;
var
  VerbIdx: TDBToolsVerb;
{$IFDEF WIN32}
  Bitmap: TBitmap;
  MenuImages: TImageList;
{$ENDIF}
begin
{$IFDEF WIN32}
  MenuImages := TImageList.Create(nil);
  Bitmap := TBitmap.Create;
  Bitmap.LoadFromResourceName(HInstance, 'MENUICONS');
  MenuImages.AddMasked(BitMap, $ff00ff);
  Bitmap.Free;
  (BorlandIDEServices as INTAServices).AddImages(MenuImages);
  MenuImages.Free;
{$ENDIF}
  FMenuManager := TActionManager.Create(nil);
  for VerbIdx := Low(TDBToolsVerb) to High(TDBToolsVerb) do begin
    FMenuActions[VerbIdx] := TAction.Create(FMenuManager);
    with FMenuActions[VerbIdx] do
      case VerbIdx of
        dbtEditSql: begin
          Caption := 'Edit SQL';
          OnExecute := EditSql;
        end;
        dbtQueryBuilder: begin
          Caption := 'Query Builder';
          OnExecute := QueryBuilder;
        end;
        dbtFindInDatabaseExplorer: begin
          Caption := 'Find in Database Explorer';
          OnExecute := FindInDatabaseExplorer;
        end;
        dbtEditDatabaseObject: begin
          Caption := 'Edit object';
          OnExecute := EditDatabaseObject;
        end;
        dbtExecuteSql: begin
          Caption := 'Execute';
          OnExecute := ExecuteSql;
        end;
        dbtDebugSql: begin
          Caption := 'Step Into';
          OnExecute := DebugSql;
        end;
        dbtRetrieveData: begin
          Caption := 'Retreive data';
          OnExecute := RetrieveData;
        end;
        dbtCompile: begin
          Caption := 'Recompile';
          OnExecute := Compile;
        end;
        dbtCompileDebug: begin
          Caption := 'Recompile with debug info';
          OnExecute := CompileDebug;
        end;
      end;
  {$IFDEF WIN32}
    FMenuActions[VerbIdx].ImageIndex := Ord(VerbIdx);
    (BorlandIDEServices as INTAServices).AddActionMenu('', FMenuActions[VerbIdx], nil);
  {$ENDIF}
  end;
end;

constructor TDBTools.Create;
begin
  inherited;
  CreateMenuActions;
end;

destructor TDBTools.Destroy;
begin
  FMenuManager.Free;
  if FDesignNotification <> nil then
    UnregisterDesignNotification(FDesignNotification);
{$IFDEF WIN32}
  if FNeedUninitialize then
    CoUninitialize;
{$ENDIF}

  inherited;
end;

function TDBTools.GetDesignerName(Designer: IDesigner): string;
{$IFDEF CLR}
var
  Ptr: IntPtr;
{$ENDIF}
begin
{$IFDEF CLR}
  Ptr := Marshal.GetIUnknownForObject(Designer);
{$ENDIF}
  Result := 'Designer_' + IntToHex(Integer({$IFDEF WIN32}Designer{$ELSE}Ptr{$ENDIF}), 8) + FDesignNotification.FDebugStr;
{$IFDEF CLR}
  Marshal.Release(Ptr);
{$ENDIF}
end;

function TDBTools.GetMenuActions(Index: TDBToolsVerb): TAction;
begin
  Result := FMenuActions[Index];
end;

procedure TDBTools.ReplaceMemo(var Memo: TMemo; DADesignUtils: TDADesignUtilsClass; Component: TComponent);
var
  NewMemo: TWinControl;
  OldName: string;
  C: TComponent;
  DBToolsService: TDBToolsService;
begin
  DBToolsService := GetDBToolsService(DADesignUtils);
  if (DBToolsService = nil) or HasDACSqlEditorFrame(Memo) then
    Exit;

  NewMemo := TDACSqlEditorFrame.Create(Memo.Owner, Component, DBToolsService);
  with TDACSqlEditorFrame(NewMemo) do begin
    Parent := Memo.Parent;
    Left := Memo.Left;
    Top := Memo.Top;
    Width := Memo.Width;
    Height := Memo.Height;
    Align := Memo.Align;
    TabOrder := Memo.TabOrder;
    Anchors := Memo.Anchors;
    HelpContext := Memo.HelpContext;
    ReadOnly := Memo.ReadOnly;
    OnChange := Memo.OnChange;
    OnExit := Memo.OnExit;
    //OnKeyDown, OnKeyPress

    StatementType := stQuery;
    CheckConnectionChange;
  end;

  if Memo.Owner <> nil then begin
    Memo.OnExit := nil;
    C := NewMemo.Owner;
    while C <> nil do begin
      if C is TCustomForm then begin
        if TCustomForm(C).ActiveControl = Memo then begin
          TCustomForm(C).ActiveControl := NewMemo;
          NewMemo.SetFocus;
        end;
        Break;
      end;
      C := C.Owner;
    end;
  end;
  OldName := Memo.Name;
  Memo.Hide;

  NewMemo.Name := OldName + '_dbt';
  TDACSqlEditorFrame(NewMemo).EndInit;
end;

procedure TDBTools.DesignerClosing(const FileName: string);
var
  i: integer;
  Designer: IDesigner;
  DesignerName: string;
begin
  Designer := FDesignNotification.SqlSourceList.FindDesigner(FileName);
  if Designer = nil then
    Exit;
  DesignerName := GetDesignerName(Designer);
  for i := 0 to Length(FDBToolsServices) - 1 do
    FDBToolsServices[i].SqlService.DesignerClosing(DesignerName);
end;

{ TDBToolsService }

constructor TDBToolsService.Create(ADADesignUtils: TDADesignUtilsClass; ASqlService: {$IFDEF WIN32}IUnknown{$ELSE}TObject{$ENDIF}; ADefaultConnectionStr: string);
begin
  inherited Create;

  FConnectionStrList := TStringList.Create;
  FConnectionsList := TStringList.Create;
  FDefaultConnectionList := TStringList.Create;
  FDADesignUtils := ADADesignUtils;

{$IFDEF WIN32}
  ASqlService.QueryInterface(IDbToolsService, FSqlService);
{$ELSE}
  FSqlService := ASqlService as IDbToolsService;
{$ENDIF}
  ConnStrToList(ADefaultConnectionStr, FDefaultConnectionList);
end;

destructor TDBToolsService.Destroy;
begin
{$IFDEF WIN32}
  PInteger(@FSqlService)^ := 0; // To prevent _Release calling
{$ENDIF}
  FDefaultConnectionList.Free;
  FConnectionStrList.Free;
  FConnectionsList.Free;

  inherited;
end;

function TDBToolsService.DataTypeToDBToolsType(AType: TFieldType): integer;
var
  i: integer;
begin
  Assert(Length(FParamTypeMaps) > 0);
  Result := FParamTypeMaps[0].DBToolsType;
  for i := 0 to High(FParamTypeMaps) do
    if FParamTypeMaps[i].DACType = AType then begin
      Result := FParamTypeMaps[i].DBToolsType;
      Break;
    end;
end;

function TDBToolsService.DBToolsTypeToDataType(AType: integer; OldType: TFieldType): TFieldType;
var
  i: integer;
begin
  Result := ftUnknown;
  for i := 0 to High(FParamTypeMaps) do
    if FParamTypeMaps[i].DBToolsType = AType then begin
      if (Result = ftUnknown) or (FParamTypeMaps[i].DACType = OldType) then
        Result := FParamTypeMaps[i].DACType;
      if Result = OldType then
        Break;
    end
    else
      if Result <> ftUnknown then
        Break;
end;

procedure TDBToolsService.BeginConnectionStrGetting(const ConnectionStrList: TStringList);
begin
  FUsedConnectionStrList := ConnectionStrList;
  SetLength(FUsedConnectionCompareFlags, 0);
end;

procedure TDBToolsService.PutConnectionParam(const ConnectionParam: string; const CompareFlag: TCompareFlag = cfNormal);
var
  i: integer;
begin
  i := Length(FUsedConnectionCompareFlags);
  Assert (i < FDefaultConnectionList.Count);
  FUsedConnectionStrList.Values[FDefaultConnectionList.Names[i]] := ConnectionParam;
  SetLength(FUsedConnectionCompareFlags, i + 1);
  FUsedConnectionCompareFlags[i] := CompareFlag;
end;

procedure TDBToolsService.SkipConnectionParams(const Count: integer);
var
  i, j: integer;
begin
  i := Length(FUsedConnectionCompareFlags);
  Assert (i + Count <= FDefaultConnectionList.Count);
  SetLength(FUsedConnectionCompareFlags, i + Count);
  for j := i to i + Count - 1 do
    FUsedConnectionCompareFlags[j] := cfNone;
end;

class function TDBToolsService.GetConnectionParamStr(const ParamName, ParamValue: string): string;
var
  i: integer;
  QuoteChar: char;
begin
  Result := ParamName + '=';
  if Pos('''', ParamValue) > 0 then
    QuoteChar := '"'
  else
    if Pos('"', ParamValue) > 0 then
      QuoteChar := ''''
    else
      if (Pos(' ', ParamValue) > 0) or (Pos(';', ParamValue) > 0) then
        QuoteChar := '"'
      else
        QuoteChar := #0;
  if QuoteChar <> #0 then
    Result := Result + QuoteChar;
  for i := 1 to Length(ParamValue) do begin
    if (QuoteChar <> #0) and (ParamValue[i] = QuoteChar) then
      Result := Result + QuoteChar;
    Result := Result + ParamValue[i];
  end;
  if QuoteChar <> #0 then
    Result := Result + QuoteChar;
  Result := Result + ';';
end;

function TDBToolsService.GetConnection(const Component: TComponent): TCustomDAConnection;
begin
  if Component = nil then
    Result := nil
  else
  if Component is TCustomDAConnection then
    Result := TCustomDAConnection(Component)
  else
    Result := DADesignUtils.UsedConnection(Component);
end;

function TDBToolsService.GetNativeConnectionString(const Component: TComponent): string;
var
  i, n: integer;
  DefStr: string;
  Connection: TCustomDAConnection;
  ConnectionList: TStringList;
begin
  Result := '';
  Connection := GetConnection(Component);
  if Connection <> nil then begin
    ConnectionList := TStringList.Create;
    try
      BeginConnectionStrGetting(ConnectionList);
      DADesignUtils.GetDBToolsConnectionList(Connection);
      n := ConnectionList.Count - 1;
      for i := n downto 0 do begin
        DefStr := FDefaultConnectionList.Values[ConnectionList.Names[i]];
        if (DefStr <> '') and (DefStr = ConnectionList.ValueFromIndex[i]) then
          ConnectionList.Delete(i);
      end;
      for i := 0 to ConnectionList.Count - 1 do
         Result := Result + GetConnectionParamStr(ConnectionList.Names[i], ConnectionList.ValueFromIndex[i]);
    finally
      ConnectionList.Free;
    end;
  end;
end;

function TDBToolsService.GetConnectionString(const Component: TComponent): TString;
begin
  Result := {$IFDEF WIN32}ToWideChar({$ENDIF}
    GetNativeConnectionString(Component)
  {$IFDEF WIN32}){$ENDIF};
end;

function TDBToolsService.GetConnectionStringObjectTypeAndFullName(const Component: TComponent; out ConnectionString, ObjectType, FullName: TString): boolean;
begin
  ConnectionString := GetConnectionString(Component);
  Result := ConnectionString <> '';
  if Result then begin
    FullName := {$IFDEF WIN32}ToWideChar({$ENDIF}
      DADesignUtils.GetFullName(Component){$IFDEF WIN32}){$ENDIF};
    ObjectType := {$IFDEF WIN32}ToWideChar({$ENDIF}
      DADesignUtils.GetObjectType(Component){$IFDEF WIN32}){$ENDIF};
    if not(Component is TCustomDAConnection) then
      Result := Length(FullName) > 0;
  end
  else begin
    FullName := {$IFDEF WIN32}nil{$ELSE}''{$ENDIF};
    ObjectType := {$IFDEF WIN32}nil{$ELSE}''{$ENDIF};
  end;
end;

procedure TDBToolsService.GetConnections(NameList: TStrings; Condition: string = '');
var
  ConnectionInfoArray: TConnectionInfoArray;
  i: integer;
  Connection: {$IFDEF WIN32}PConnectionInfo{$ELSE}ConnectionInfo{$ENDIF};
begin
  ConnectionInfoArray := FSqlService.GetConnections;
  NameList.BeginUpdate;
  FConnectionsList.BeginUpdate;
{$IFDEF WIN32}
  Connection := AccessData(ConnectionInfoArray);
{$ENDIF}
  try
    FConnectionsList.Clear;
    NameList.Clear;
    for i := 0 to {$IFDEF WIN32}DataHigh{$ELSE}High{$ENDIF}(ConnectionInfoArray) do begin
    {$IFDEF CLR}
      Connection := ConnectionInfoArray[i];
    {$ENDIF}
      if (Condition = '') or (Pos(Condition, Connection.ConnectionString) > 0) then begin
        NameList.Add(Connection.Name);
        FConnectionsList.Add(GetConnectionValueStr(Connection.Name) + Connection.ConnectionString);
      end;
    {$IFDEF WIN32}
      Inc(Connection);
    {$ENDIF}
    end;
  finally
    NameList.EndUpdate;
    FConnectionsList.EndUpdate;
  {$IFDEF WIN32}
    UnaccessData(ConnectionInfoArray);
  {$ENDIF}
  end;
end;

function TDBToolsService.FindConnectionName(AConnection: TCustomDAConnection): string;
var
  i, j: integer;
  AConnectionStrList: TStringList;
  Str1, Str2, DefStr: string;

  function ToCommonCase(const s: string): string;
  var
    ts: string;
  begin
    Result := s;
    if FUsedConnectionCompareFlags[j] = cfNormal then begin
      ts := Trim(Result);
      if (Length(ts) < 2) or (ts[1] <> '"') or (ts[Length(ts)] <> '"') then
        Result := UpperCase(Result);
    end;
  end;

begin
  AConnectionStrList := TStringList.Create;
  try
    BeginConnectionStrGetting(AConnectionStrList);
    DADesignUtils.GetDBToolsConnectionList(AConnection);
    i := Length(FUsedConnectionCompareFlags);
    SetLength(FUsedConnectionCompareFlags, FDefaultConnectionList.Count);
    for j := i to FDefaultConnectionList.Count - 1 do
      FUsedConnectionCompareFlags[j] := cfNormal;
    for i := 0 to FConnectionsList.Count - 1 do begin
      ConnStrToList(FConnectionsList[i], FConnectionStrList);
      for j := 0 to FDefaultConnectionList.Count - 1 do begin
        if FUsedConnectionCompareFlags[j] = cfNone then
          Continue;

        DefStr := FDefaultConnectionList.ValueFromIndex[j];
        Str1 := ToCommonCase(AConnectionStrList.Values[FDefaultConnectionList.Names[j]]);
        Str2 := ToCommonCase(FConnectionStrList.Values[FDefaultConnectionList.Names[j]]);
        if (Str1 <> Str2) and (DefStr <> '') then begin
          if Str1 = '' then
            Str1 := ToCommonCase(DefStr);
          if Str2 = '' then
            Str2 := ToCommonCase(DefStr);
        end;
        if Str1 <> Str2 then
          Break
        else
          if j = FDefaultConnectionList.Count - 1 then begin
            Result := FConnectionStrList.Values[SConnectionName];
            Exit;
          end;
      end;
    end;
    Result := '';
  finally
    AConnectionStrList.Free;
  end;
end;

function TDBToolsService.GetConnectionStrList(ConnectionName: string): TStringList;
var
  i, j, k: integer;
  s: string;
begin
  Result := FConnectionStrList;
  s := GetConnectionValueStr(ConnectionName);
  for i := 0 to FConnectionsList.Count - 1 do
    if (Length(FConnectionsList[i]) >= Length(s)) and
      (Copy(FConnectionsList[i], 1, Length(s)) = s) then begin
      ConnStrToList(Copy(FConnectionsList[i], Length(s) + 1, Length(FConnectionsList[i]) - Length(s)), Result);

      for j := 0 to FDefaultConnectionList.Count - 1 do begin
        k := Result.IndexOfName(FDefaultConnectionList.Names[j]);
        if k < 0 then
          Result.Add(FDefaultConnectionList[j])
        else
          if Result.ValueFromIndex[k] = '' then
            Result[k] := FDefaultConnectionList[j];
      end;
      Exit;
    end;
  Result.Clear;
end;

procedure TDBToolsService.CheckConnection(const Component: TComponent);
var
  Connection: TCustomDAConnection;
begin
  Connection := GetConnection(Component);
  if Connection = nil then
    DatabaseError(SConnectionNotDefined);
end;

procedure TDBToolsService.FindInDatabaseExplorer;
var
  ConnectionString, ObjectType, FullName: TString;
begin
  CheckConnection(FCurrentComponent);
  if GetConnectionStringObjectTypeAndFullName(FCurrentComponent, ConnectionString, ObjectType, FullName) then
    SqlService.FindInDatabaseExplorer(ConnectionString, ObjectType, FullName);
end;

procedure TDBToolsService.EditDatabaseObject;
var
  ConnectionString, ObjectType, FullName: TString;
begin
  CheckConnection(FCurrentComponent);
  if GetConnectionStringObjectTypeAndFullName(FCurrentComponent, ConnectionString, ObjectType, FullName) then
    SqlService.EditDatabaseObject(ConnectionString, ObjectType, FullName);
end;

procedure TDBToolsService.ExecuteSql(Debug: boolean);
var
  SqlSource: TSqlSource;
begin
  CheckConnection(FCurrentComponent);
  SqlSource := GetSqlSource(FCurrentComponent, FCurrentDesigner);
  SqlService.ExecuteSql(SqlSource, Debug);
end;

procedure TDBToolsService.Compile(Debug: boolean);
var
  SqlSource: TSqlSource;
begin
  CheckConnection(FCurrentComponent);
  SqlSource := GetSqlSource(FCurrentComponent, FCurrentDesigner, SCompilePrefix);
  SqlService.ExecuteSql(SqlSource, Debug);
end;

procedure TDBToolsService.RetrieveData(AsDocument: boolean);
var
  SqlSource: TSqlSource;
begin
  CheckConnection(FCurrentComponent);
  SqlSource := GetSqlSource(FCurrentComponent, FCurrentDesigner);
  SqlService.RetrieveData(SqlSource, AsDocument);
end;

procedure TDBToolsService.EditSql(AsQuery: boolean);
var
  SqlSource: TSqlSource;
begin
  SqlSource := GetSqlSource(FCurrentComponent, FCurrentDesigner, SEditPrefix);
  SqlService.EditSql(SqlSource, AsQuery, SqlSource.FSqlSourceNotifier);
  if SqlSource.FSqlSourceNotifier = nil then
    DBTools.DesignNotification.SqlSourceList.Delete(SqlSource);
end;

function TDBToolsService.GetSqlEditor: ISqlEditor;
var
  n: integer;
begin
  n := Length(FSqlEditors) - 1;
  if n >= 0 then begin
    Result := FSqlEditors[n];
    SetLength(FSqlEditors, n);
  end
  else
    SqlService.CreateSqlEditor(Result);
end;

function TDBToolsService.GetSqlSource(Component: TComponent; Designer: IDesigner; SqlTextPrefix: string = ''): TSqlSource;
begin
  Assert(DBTools.DesignNotification <> nil);
  Result := DBTools.DesignNotification.SqlSourceList.Find(Component, SqlTextPrefix);
  if Result = nil then begin
    Result := TSqlSource.Create(Self, Component, Designer);
    Result.SqlTextPrefix := SqlTextPrefix;
    DBTools.DesignNotification.SqlSourceList.Add(Result);
  end;
end;

procedure TDBToolsService.ReturnSqlEditor(ASqlEditor: ISqlEditor);
var
  n: integer;
begin
  Windows.SetParent(ASqlEditor.Handle{$IFDEF CLR}.ToInt32{$ENDIF}, MAXDWORD - 2{HWND_MESSAGE});
  n := Length(FSqlEditors);
  SetLength(FSqlEditors, n + 1);
  FSqlEditors[n] := ASqlEditor;
end;

procedure TDBToolsService.AddParamTypeMap(ADACType: TFieldType; ADBToolsType: integer);
var
  n: integer;
begin
  n := Length(FParamTypeMaps);
  SetLength(FParamTypeMaps, n + 1);
  with FParamTypeMaps[n] do begin
    DACType := ADACType;
    DBToolsType := ADBToolsType;
  end;
end;

{$IFDEF WIN32}
class function TDBToolsService.AccessData(const V: PSafeArray): pointer;
begin
  if V = nil then
    Result := nil
  else
    SafeArrayCheck(SafeArrayAccessData(V, Result));
end;

class procedure TDBToolsService.UnaccessData(const V: PSafeArray);
begin
  if V <> nil then
    SafeArrayCheck(SafeArrayUnaccessData(V));
end;

class function TDBToolsService.DataHigh(const V: PSafeArray): integer;
begin
  if V = nil then
    Result := -1
  else
    SafeArrayGetUBound(V, 1, Result);
end;
{$ENDIF}

function TDBToolsService.GetConnectionValueStr(ConnectionName: string): string;
begin
  Result := SConnectionName + '=' + ConnectionName + ';';
end;

procedure TDBToolsService.ConnStrToList(ConnStr: string; const ConnList: TStrings);
var
  ParamName, ParamValue: string;
  QuoteChar: char;
  Quoted: boolean;
  i, l: integer;
begin
  ConnStr := Trim(ConnStr);
  ConnList.BeginUpdate;
  ConnList.Clear;
  try
    if ConnStr = '' then
      Exit;
    if ConnStr[Length(ConnStr)] <> ';' then
      ConnStr := ConnStr + ';';
    repeat
      i := Pos('=', ConnStr);
      if i <= 0 then
        Break;
      ParamName := Copy(ConnStr, 1, i); //with '='
      Inc(i);
      QuoteChar := ConnStr[i];
      Quoted := {$IFDEF CLR}AnsiChar{$ENDIF}(QuoteChar) in ['''', '"'];
      if Quoted then
        Inc(i);
      ParamValue := '';
      repeat
        if i + Ord(Quoted) > Length(ConnStr) then
          raise Exception.Create(SConnStrError);

        if Quoted then
          if ConnStr[i] = QuoteChar then
            if (ConnStr[i + 1] = QuoteChar) or (ConnStr[i + 1] = ';') then
              Inc(i)
            else
              raise Exception.Create(SConnStrError);

        if ConnStr[i] = ';' then
          Break
        else
          ParamValue := ParamValue + ConnStr[i];
        Inc(i);
      until False;
      ConnList.Add(ParamName + ParamValue);
      l := Length(ConnStr) - i;
      if l > 0 then
        ConnStr := Copy(ConnStr, i + 1, l)
      else
        Break;
    until False;
  finally
    ConnList.EndUpdate;
  end;
end;

{ TModuleNotifier}

constructor TModuleNotifier.Create(const Module : IOTAModule);
begin
  inherited Create;

  FModule := Module;
{$IFDEF WIN32}
  FIndex := Module.AddNotifier(Self);
{$ELSE}
  Include(Module.BeforeSave, BeforeSave);
{$ENDIF}
end;

procedure TModuleNotifier.RemoveNotifier;
begin
{$IFDEF WIN32}
  if Findex >= 0 then
  begin
    FModule.RemoveNotifier(FIndex);
    FIndex := -1;
  end;
{$ELSE}
  Exclude(FModule.BeforeSave, BeforeSave);
{$ENDIF}
  DBTools.DesignNotification.ModuleList.Delete(FModule);
end;

destructor TModuleNotifier.Destroy;
begin
  RemoveNotifier;

  inherited;
end;

{$IFDEF WIN32}
procedure TModuleNotifier.Destroyed;
begin
  RemoveNotifier;
  FModule := nil;
end;

procedure TModuleNotifier.AfterSave;
begin
end;

procedure TModuleNotifier.BeforeSave;
begin
end;

procedure TModuleNotifier.Modified;
begin
end;

function TModuleNotifier.CheckOverwrite: Boolean;
begin
  Result := True;
end;

procedure TModuleNotifier.ModuleRenamed(const NewName: string);
begin
end;

function TModuleNotifier.AllowSave: Boolean;
begin
  if AllowSaveSwitch then
    DBTools.DesignerClosing(FModule.FileName);
  AllowSaveSwitch := not AllowSaveSwitch;
  Result := True;
end;

function TModuleNotifier.GetOverwriteFileNameCount: Integer;
begin
  Result := 0;
end;

function TModuleNotifier.GetOverwriteFileName(Index: Integer): string;
begin
  Result := '';
end;

procedure TModuleNotifier.SetSaveFileName(const FileName: string);
begin
end;

{$ELSE}
procedure TModuleNotifier.BeforeSave(Sender: TObject; e: EventArgs);
begin
  DBTools.DesignerClosing(FModule.FileName);
end;
{$ENDIF}

{ TModuleList }

function TModuleList.IndexOf(Module: IOTAModule): integer;
var
  i: integer;
begin
  Result := -1;
  for i := 0 to Count - 1 do
    if TModuleNotifier(Items[i]).Module = Module then begin
      Result := i;
      Break;
    end;
end;

procedure TModuleList.Add(Module: IOTAModule);
begin
  if IndexOf(Module) < 0 then
    inherited Add(TModuleNotifier.Create(Module));
end;

procedure TModuleList.Delete(Module: IOTAModule);
var
  Index: integer;
begin
  Index := IndexOf(Module);
  if Index >= 0 then
    Delete(Index);
end;

procedure TModuleList.Delete(Index: integer);
var
  ModuleNotifier: TModuleNotifier;
begin
  ModuleNotifier := TModuleNotifier(Items[Index]);
  inherited Delete(Index);
{$IFDEF WIN32}
  ModuleNotifier.Destroyed;
{$ENDIF}
end;

function GetDBToolsService(DADesignUtilsClass: TDADesignUtilsClass): TDBToolsService;
begin
  Result := TDBToolsService(DADesignUtilsClass.DBToolsService);
end;

initialization
  DBTools := TDBTools.Create;

finalization
  DBTools.Free;

end.