
//////////////////////////////////////////////////
//  SQLite Data Access Components
//  Copyright © 2008-2014 Devart. All right reserved.
//////////////////////////////////////////////////

{$IFNDEF CLR}

{$I LiteDac.inc}
unit LiteServicesUni;
{$ENDIF}

interface

uses
  SysUtils, Classes, Variants, DB,
{$IFNDEF CLR}
  CLRClasses,
{$ELSE}
  System.Text,
{$ENDIF}
  CRTypes, MemData, {$IFNDEF FPC}MemDS{$ELSE}MemDataSet{$ENDIF},
  CRParser, CRAccess, DBAccess, DAScript, DADump, DASQLGenerator,
{$IFNDEF UNIDACPRO}
  LiteCall, LiteClasses, LiteParser, LiteSQLGenerator;
{$ELSE}
  LiteCallUni, LiteClassesUni, LiteParserUni, LiteSQLGeneratorUni;
{$ENDIF}

type
{ TCustomLiteFieldTypeMap }

  TCustomLiteFieldTypeMap = class(TDAFieldTypeMap)
  end;

{ TCustomLiteDataSetUpdater }

  TCustomLiteDataSetUpdater = class(TDADataSetUpdater)
  protected
    // CLR cross-assembly
    procedure CheckUpdateQuery(const StatementType: TStatementType); override;

    function BatchUpdate: boolean; override;
    procedure SetUpdateQueryOptions(const StatementType: TStatementType); override;
    function GetIdentityFieldValue(out Value: variant): boolean; override;
  end;

{ TCustomLiteDataSetService }

  TCustomLiteDataSetService = class(TDADataSetService)
  protected
    procedure CreateDataSetUpdater; override;
    procedure CreateSQLGenerator; override;

    function DetectCanModify: boolean; override;
    function ExtFieldsInfoIsInternal: boolean; override;
    procedure RequestFieldsInfo(Tables: TSQLObjectsInfo; Columns: TColumnsInfo); override;
    procedure InitFieldsOptions; override;
    function GetRecCount: integer; override;

  public
    constructor Create(AOwner: TMemDataSet); override;
  end;

  TLiteScriptProcessor = class (TDAScriptProcessor)
  protected
    function GetParserClass: TSQLParserClass; override;
  end;

  TCustomLiteDumpProcessor = class(TDADumpProcessor)
  protected
    function CreateQuery: TCustomDADataSet; override;
    function CreateDataQuery: TCustomDADataSet; override;
    function GetFieldValueForDump(Field: TField): string; override;
  end;

  TLiteServerEnumerator = class (TCRServerEnumerator)
  end;

implementation

uses
  CRFunctions, DAConsts, DASQLMonitor;

{ TCustomLiteDataSetUpdater }

procedure TCustomLiteDataSetUpdater.CheckUpdateQuery(const StatementType: TStatementType);
begin
  inherited;
end;

function TCustomLiteDataSetUpdater.BatchUpdate: boolean;
begin
  Result := False;
end;

procedure TCustomLiteDataSetUpdater.SetUpdateQueryOptions(const StatementType: TStatementType);
var
  DestRecordSet, SourceRecordSet: TCRRecordSet;

  procedure CopyPropR(Prop: integer);
  var
    v: variant;
  begin
    SourceRecordSet.GetProp(Prop, v);
    DestRecordSet.SetProp(Prop, v);
  end;

begin
  CheckIRecordSet; // can be inactive
  SourceRecordSet := GetIRecordSet;
  DestRecordSet := TDBAccessUtils.GetIRecordSet(FUpdateQuery as TCustomDADataSet);

  //CopyPropR(prEnableLargeint);

  DestRecordSet.SetProp(prExtendedFieldsInfo, False);
end;

function TCustomLiteDataSetUpdater.GetIdentityFieldValue(out Value: variant): boolean;
begin
  Value := TDBAccessUtils.GetLastInsertId(FUpdateQuery as TCustomDADataSet);
  Result := True;
end;

{ TCustomLiteDataSetService }

constructor TCustomLiteDataSetService.Create(AOwner: TMemDataSet);
begin
  inherited;
end;

procedure TCustomLiteDataSetService.CreateDataSetUpdater;
begin
  SetDataSetUpdater(TCustomLiteDataSetUpdater.Create(Self));
end;

procedure TCustomLiteDataSetService.CreateSQLGenerator; 
begin
  SetSQLGenerator(TCustomLiteSQLGenerator.Create(TDASQLGeneratorService));
end;

function TCustomLiteDataSetService.DetectCanModify: boolean;
begin
  Result := inherited DetectCanModify or
    not FDataSet.ReadOnly and FIsAnyFieldCanBeModified;
end;

function TCustomLiteDataSetService.ExtFieldsInfoIsInternal: boolean;
var
  IConnection: TCRConnection;
begin
  Result := False;
  IConnection := GetIConnection;
  if (IConnection is TSQLiteConnection) then
    Result := TSQLiteConnection(IConnection).API.IsMetaDataAPIAvailable;
end;

procedure TCustomLiteDataSetService.RequestFieldsInfo(Tables: TSQLObjectsInfo; Columns: TColumnsInfo);

  function Locate1(MetaData: TData; RecBuf: IntPtr; Field: TFieldDesc; Value: string): boolean;
  var
    v: variant;
  begin
    MetaData.SetToBegin;
    while True do begin
      MetaData.GetNextRecord(RecBuf);
      if MetaData.Eof then
        break;
      MetaData.GetFieldAsVariant(Field, RecBuf, v);
      if SameText(VarToStr(v), Value) then begin
        Result := True;
        exit;
      end;
    end;
    Result := False;
  end;

var
  Restr: TStringList;
  SQLiteMetaData: TSQLiteMetaData;
  MetaData: TData;
  i, p: integer;
  ColumnInfo, NewColumnInfo: CRAccess.TColumnInfo;
  Located: boolean;
  RecBuf: IntPtr;
  v: variant;
  NameField: TFieldDesc;
  ExprField: TFieldDesc;
begin
  for i := 0 to High(Tables) do
    with Tables[i] do begin
      if FDataSet.Options.FieldsOrigin or (i = GetIRecordSet.UpdatingTableInfoIdx) then
        Flag := 1
      else
        Flag := 0; // don't query fields for this table
    end;

  SQLiteMetaData := TSQLiteMetaData.Create;
  for i := 0 to High(Tables) do begin
    if Tables[i].Flag = 0 then
      continue;

    Restr := TStringList.Create;
    try
      Restr.Add('TABLE_NAME=' + Tables[i].Name);
      MetaData := SQLiteMetaData.GetMetaData(GetIConnection, GetIConnection.GetInternalTransaction, 'columns', Restr);
    finally
      Restr.Free;
    end;

    MetaData.AllocRecBuf(RecBuf);
    try
      p := 0;
      while p < Columns.Count do begin
        ColumnInfo := Columns[p];
        if (ColumnInfo.TableIndex <> -1) and (ColumnInfo.TableIndex <> i) then begin
          Inc(p);
          continue;
        end;

        NameField := MetaData.Fields[3];
        ExprField := MetaData.Fields[10];

        if ColumnInfo.Name = '*' then begin
          MetaData.SetToBegin;
          repeat
            MetaData.GetNextRecord(RecBuf);
            if MetaData.Eof then
              break;

            Inc(p);
            NewColumnInfo := CRAccess.TColumnInfo.Create;
            Columns.Insert(p, NewColumnInfo);
            NewColumnInfo.Table := ColumnInfo.Table;
            NewColumnInfo.TableIndex := i;

            MetaData.GetFieldAsVariant(NameField, RecBuf, v);
            NewColumnInfo.Name := VarToStr(v);
            NewColumnInfo.Alias := NewColumnInfo.Name;

            if FDataSet.Options.DefaultValues and (i = GetIRecordSet.UpdatingTableInfoIdx) then begin
              MetaData.GetFieldAsVariant(ExprField, RecBuf, v);
              NewColumnInfo.Expr := VarToStr(v);
            end;
          until False;
        end
        else
        if (ColumnInfo.Name <> '') then begin
          if ColumnInfo.TableIndex <> -1 then begin
            if FDataSet.Options.DefaultValues and (i = GetIRecordSet.UpdatingTableInfoIdx) then
              Located := Locate1(MetaData, RecBuf, NameField, ColumnInfo.Name)
            else
              Located := False;
          end
          else
            Located := Locate1(MetaData, RecBuf, NameField, ColumnInfo.Name);

          if Located then begin
            if FDataSet.Options.DefaultValues then begin
              MetaData.GetFieldAsVariant(ExprField, RecBuf, v);
              ColumnInfo.Expr := VarToStr(v);
            end;

            ColumnInfo.TableIndex := i;
          end;
        end;

        inc(p);
      end;
      MetaData.Close;
    finally
      MetaData.FreeRecBuf(RecBuf);
      MetaData.Free;
    end;
  end;  
end;

procedure TCustomLiteDataSetService.InitFieldsOptions;
var
  TableName: string;
  MetaData: TDAMetadata;
  Field: TField;
  FieldDesc: TFieldDesc;
  FieldName: string;
  DefValue: string;
  ExtFieldsInfo: boolean;
  Info: TSQLObjectInfo;
  v: variant;
begin
  inherited;

  GetIRecordSet.GetProp(prExtendedFieldsInfo, v);
  ExtFieldsInfo := v;

  if not (FDataSet.Options.DefaultValues or ExtFieldsInfo)
    or (GetIRecordSet.UpdatingTableInfo = nil)
  then
    Exit;

  SQLiteInfo.SplitObjectName(GetIRecordSet.UpdatingTableInfo.TableName, Info);
  TableName := Info.Name;
  MetaData := TDAMetadata.Create(nil);
  try
    MetaData.Connection := UsedConnection;
    MetaData.MetaDataKind := 'Columns';
    MetaData.Restrictions.Text := 'table_name=' + TableName;
    MetaData.Open;
    while not MetaData.EOF do begin
      FieldName := MetaData.FieldByName('COLUMN_NAME').AsString;
      FieldDesc := GetIRecordSet.FindField(FieldName);
      if FieldDesc = nil then
        FieldDesc := GetIRecordSet.FindField(FieldName);
      if FieldDesc <> nil then
        Field := FDataSet.GetField(FieldDesc)
      else
        Field := nil;
      if (Field <> nil) then begin
        if MetaData.FieldByName('NULLABLE').AsInteger = 0 then begin
          FieldDesc.IsAutoIncrement := True;
        {$IFNDEF FPC}
          Field.AutoGenerateValue := arAutoInc;
        {$ENDIF}
          GetIRecordSet.SetIdentityField(TCRFieldDesc(FieldDesc));
        end;
        if FDataSet.Options.DefaultValues and
          not MetaData.FieldByName('DEFAULT_VALUE').IsNull
        then begin
          DefValue := MetaData.FieldByName('DEFAULT_VALUE').AsString;
          Field.DefaultExpression := DefValue;
        end;
      end;
      MetaData.Next;
    end;
  finally
    MetaData.Free;
  end;
end;

function TCustomLiteDataSetService.GetRecCount: integer;
var
  St: string;
  UpdateQuery: TCustomDADataSet;
  MonitorClass: TDASQLMonitorClass;
  MessageID: cardinal;
begin
  Result := 0;
  St := Trim(FDataSet.SQLRecCount.Text);
  if St = '' then
    St := FSQLGenerator.GenerateRecCountSQL;

  TCustomLiteDataSetUpdater(FUpdater).CheckUpdateQuery(stCustom);
  UpdateQuery := TCustomDADataSet(FUpdater.UpdateQuery);
  UpdateQuery.SQL.Text := St;
  UpdateQuery.Params.Assign(FDataSet.Params);

  MonitorClass := TDASQLMonitorClass(TDBAccessUtils.SQLMonitorClass(UsedConnection));
  if MonitorClass.HasMonitor or FDataSet.Debug then
    MonitorClass.SQLExecute(FDataSet, St, UpdateQuery.Params, 'Get RecordCount', MessageID, True);

  UpdateQuery.Open;
  if not UpdateQuery.EOF then
    Result := UpdateQuery.Fields[0].AsInteger;

  if MonitorClass.HasMonitor or FDataSet.Debug then
    MonitorClass.SQLExecute(FDataSet, St, UpdateQuery.Params, 'Get RecordCount', MessageID, False);
end;

{ TLiteScriptProcessor }

function TLiteScriptProcessor.GetParserClass: TSQLParserClass;
begin
  Result := TLiteParser;
end;

{ TCustomLiteDumpProcessor }

function TCustomLiteDumpProcessor.CreateQuery: TCustomDADataSet;
begin
  Result := GetConnection.CreateDataSet;
  Result.ReadOnly := True;
  Result.UniDirectional := True;
  TDBAccessUtils.CheckConnection(Result);
  TDBAccessUtils.GetIRecordSet(Result).SetProp(prExtendedFieldsInfo, False);
end;

function TCustomLiteDumpProcessor.CreateDataQuery: TCustomDADataSet;
begin
  Result := CreateQuery;
  TDBAccessUtils.GetIRecordSet(Result).SetProp(prDumpData, True);
end;

function TCustomLiteDumpProcessor.GetFieldValueForDump(Field: TField): string;
begin
  Result := Field.AsString;
end;

end.
