{----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: JvDBUtils.PAS, released on 2002-07-04. The Initial Developers of the Original Code are: Fedor Koshevnikov, Igor Pavluk and Serge Korolev Copyright (c) 1997, 1998 Fedor Koshevnikov, Igor Pavluk and Serge Korolev Copyright (c) 2001,2002 SGB Software All Rights Reserved. You may retrieve the latest version of this file at the Project JEDI's JVCL home page, located at http://jvcl.delphi-jedi.org Contributors: tia Lazarus port: MichaƂ Gawrycki Known Issues: -----------------------------------------------------------------------------} // $Id$ unit JvDBUtils; {.$I jvcl.inc} interface uses Variants, Classes, SysUtils, DB; type TDropDownAlign = (daLeft, daRight, daCenter); IJvDataControl = interface ['{8B6910C8-D5FD-40BA-A427-FC54FE7B85E5}'] function GetDataLink: TDataLink; end; TJvDataLink = class(TDataLink) protected procedure FocusControl(Field: TFieldRef); overload; override; procedure FocusControl(const {%H-}Field: TField); reintroduce; overload; virtual; end; TCommit = (ctNone, ctStep, ctAll); TJvDBProgressEvent = procedure(UserData: Integer; var Cancel: Boolean; Line: Integer) of object; EJvScriptError = class(Exception) private FErrPos: Integer; public constructor Create(const AMessage: string; AErrPos: Integer); overload; property ErrPos: Integer read FErrPos; end; TJvLocateObject = class(TObject) private FDataSet: TDataSet; FLookupField: TField; FLookupValue: string; FLookupExact: Boolean; FCaseSensitive: Boolean; FBookmark: TBookmark; FIndexSwitch: Boolean; procedure SetDataSet(Value: TDataSet); protected function MatchesLookup(Field: TField): Boolean; procedure CheckFieldType({%H-}Field: TField); virtual; procedure ActiveChanged; virtual; function LocateFilter: Boolean; virtual; function LocateKey: Boolean; virtual; function LocateFull: Boolean; virtual; function UseKey: Boolean; virtual; function FilterApplicable: Boolean; virtual; property LookupField: TField read FLookupField; property LookupValue: string read FLookupValue; property LookupExact: Boolean read FLookupExact; property CaseSensitive: Boolean read FCaseSensitive; property Bookmark: TBookmark read FBookmark write FBookmark; public function Locate(const KeyField, KeyValue: string; Exact, ACaseSensitive: Boolean; DisableControls: Boolean = True; RightTrimmedLookup: Boolean = False): Boolean; property DataSet: TDataSet read FDataSet write SetDataSet; property IndexSwitch: Boolean read FIndexSwitch write FIndexSwitch; end; TCreateLocateObject = function: TJvLocateObject; var CreateLocateObject: TCreateLocateObject = nil; function CreateLocate(DataSet: TDataSet): TJvLocateObject; { Utility routines } function ExtractFieldNameEx(const Fields: String; var Pos: Integer): string; function IsDataSetEmpty(DataSet: TDataSet): Boolean; procedure RefreshQuery(Query: TDataSet); function DataSetSortedSearch(DataSet: TDataSet; const Value, FieldName: string; CaseInsensitive: Boolean): Boolean; //function DataSetSectionName(DataSet: TDataSet): string; //procedure InternalSaveFields(DataSet: TDataSet; AppStorage: TJvCustomAppStorage; const Path: string); //procedure InternalRestoreFields(DataSet: TDataSet; AppStorage: TJvCustomAppStorage; // const Path: string; RestoreVisible: Boolean); function DataSetLocateThrough(DataSet: TDataSet; const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): Boolean; (* procedure SaveFieldsReg(DataSet: TDataSet; IniFile: TRegIniFile); procedure RestoreFieldsReg(DataSet: TDataSet; IniFile: TRegIniFile; RestoreVisible: Boolean); *) //procedure SaveFields(DataSet: TDataSet; AppStorage: TJvCustomAppStorage; const Path: string = ''); //procedure RestoreFields(DataSet: TDataSet; AppStorage: TJvCustomAppStorage; const Path: string = ''; // RestoreVisible: Boolean = True); procedure AssignRecord(Source, Dest: TDataSet; ByName: Boolean); function ConfirmDelete: Boolean; procedure ConfirmDataSetCancel(DataSet: TDataSet); procedure CheckRequiredField(Field: TField); procedure CheckRequiredFields(const Fields: array of TField); procedure GotoBookmarkEx(DataSet: TDataSet; const Bookmark: TBookmark; Mode: TResyncMode = [rmExact, rmCenter]; ForceScrollEvents: Boolean = False); function GetFieldProperty(ADataSet: TDataSet; AControl: TComponent; const AFieldName: string): TField; { SQL expressions } function DateToSQL(Value: TDateTime): string; function FormatSQLDateRange(Date1, Date2: TDateTime; const FieldName: string): string; function FormatSQLDateRangeEx(Date1, Date2: TDateTime; const FieldName: string): string; function FormatSQLNumericRange(const FieldName: string; LowValue, HighValue, LowEmpty, HighEmpty: Double; Inclusive: Boolean): string; function StrMaskSQL(const Value: string): string; function FormatSQLCondition(const FieldName, AOperator, Value: string; FieldType: TFieldType; Exact: Boolean): string; function FormatAnsiSQLCondition(const FieldName, AOperator, Value: string; FieldType: TFieldType; Exact: Boolean): string; const TrueExpr = '0=0'; {$NODEFINE TrueExpr} const { Server Date formats} sdfStandard16 = '''"''mm''/''dd''/''yyyy''"'''; {"mm/dd/yyyy"} sdfStandard32 = '''''''dd/mm/yyyy'''''''; {'dd/mm/yyyy'} sdfOracle = '"TO_DATE(''"dd/mm/yyyy"'', ''DD/MM/YYYY'')"'; sdfInterbase = '"CAST(''"mm"/"dd"/"yyyy"'' AS DATE)"'; sdfMSSQL = '"CONVERT(datetime, ''"mm"/"dd"/"yyyy"'', 103)"'; const ServerDateFmt: string = sdfStandard16; {.$NODEFINE ftNonTextTypes} (*$HPPEMIT 'namespace JvDBUtils'*) (*$HPPEMIT '{'*) (*$HPPEMIT '#define ftNonTextTypes (System::Set () \'*) (*$HPPEMIT ' << ftBytes << ftVarBytes << ftBlob << ftMemo << ftGraphic \'*) (*$HPPEMIT ' << ftFmtMemo << ftParadoxOle << ftDBaseOle << ftTypedBinary << ftCursor )'*) (*$HPPEMIT '}'*) type Largeint = Longint; {$NODEFINE Largeint} function NameDelimiter(C: Char): Boolean; function IsLiteral(C: Char): Boolean; procedure _DBError(const Msg: string); implementation uses DBConst, Math, Controls, Forms, Dialogs, DateUtils, JvJCLUtils, JvTypes, JvConsts, JvResources; resourcestring SDeleteRecordQuestion = 'Delete record?'; SFieldTypeMismatch = 'Field type mismatch: %s'; { TJvDataLink } procedure TJvDataLink.FocusControl(Field: TFieldRef); begin FocusControl(Field^); end; procedure TJvDataLink.FocusControl(const Field: TField); begin end; { Utility routines } function NameDelimiter(C: Char): Boolean; begin Result := CharInSet(C, [' ', ',', ';', ')', '.', Cr, Lf]); end; function IsLiteral(C: Char): Boolean; begin Result := CharInSet(C, ['''', '"']); end; procedure _DBError(const Msg: string); begin DatabaseError(Msg); end; constructor EJvScriptError.Create(const AMessage: string; AErrPos: Integer); begin inherited Create(AMessage); FErrPos := AErrPos; end; // (rom) better use Windows dialogs which are localized function ConfirmDelete: Boolean; begin Screen.Cursor := crDefault; Result := MessageDlg(SDeleteRecordQuestion, mtConfirmation, [mbYes, mbNo], 0) = mrYes; end; procedure ConfirmDataSetCancel(DataSet: TDataSet); begin if DataSet.State in [dsEdit, dsInsert] then begin DataSet.UpdateRecord; if DataSet.Modified then begin case MessageDlg(RsConfirmSave, mtConfirmation, mbYesNoCancel, 0) of mrYes: DataSet.Post; mrNo: DataSet.Cancel; else SysUtils.Abort; end; end else DataSet.Cancel; end; end; function SetToBookmark(ADataSet: TDataSet; ABookmark: TBookmark): Boolean; begin Result := False; if ADataSet.Active and (ABookmark <> nil) and not (ADataSet.Bof and ADataSet.Eof) and ADataSet.BookmarkValid(ABookmark) then try ADataSet.GotoBookmark(ABookmark); Result := True; except end; end; function GetFieldProperty(ADataSet: TDataSet; AControl: TComponent; const AFieldName: string): TField; begin Result := ADataSet.FindField(AFieldName); if Result = nil then DatabaseErrorFmt(SFieldNotFound, [AFieldName], AControl); end; { Refresh Query procedure } procedure RefreshQuery(Query: TDataSet); var BookMk: TBookmark; begin Query.DisableControls; try if Query.Active then BookMk := Query.GetBookmark else BookMk := nil; try Query.Close; Query.Open; SetToBookmark(Query, BookMk); finally if BookMk <> nil then Query.FreeBookmark(BookMk); end; finally Query.EnableControls; end; end; procedure TJvLocateObject.SetDataSet(Value: TDataSet); begin ActiveChanged; FDataSet := Value; end; function TJvLocateObject.LocateFull: Boolean; begin Result := False; DataSet.First; while not DataSet.Eof do begin if MatchesLookup(FLookupField) then begin Result := True; Break; end; DataSet.Next; end; end; function TJvLocateObject.LocateKey: Boolean; begin Result := False; end; function TJvLocateObject.FilterApplicable: Boolean; begin Result := FLookupField.FieldKind in [fkData, fkInternalCalc]; end; function TJvLocateObject.LocateFilter: Boolean; var SaveCursor: TCursor; Options: TLocateOptions; Value: Variant; begin SaveCursor := Screen.Cursor; Screen.Cursor := crHourGlass; try Options := []; if not FCaseSensitive then Include(Options, loCaseInsensitive); if not FLookupExact then Include(Options, loPartialKey); if FLookupValue = '' then VarClear(Value{%H-}) else Value := FLookupValue; Result := DataSet.Locate(FLookupField.FieldName, Value, Options); finally Screen.Cursor := SaveCursor; end; end; procedure TJvLocateObject.CheckFieldType(Field: TField); begin end; function TJvLocateObject.Locate(const KeyField, KeyValue: string; Exact, ACaseSensitive: Boolean; DisableControls: Boolean; RightTrimmedLookup: Boolean): Boolean; var LookupKey: TField; function IsStringType(FieldType: TFieldType): Boolean; const cStringTypes = [ftString, ftWideString]; begin Result := FieldType in cStringTypes; end; begin if DataSet = nil then begin Result := False; Exit; end; DataSet.CheckBrowseMode; LookupKey := DataSet.FieldByName(KeyField); DataSet.CursorPosChanged; FLookupField := LookupKey; if RightTrimmedLookup then FLookupValue := TrimRight(KeyValue) else FLookupValue := KeyValue; FLookupExact := Exact; FCaseSensitive := ACaseSensitive; if not IsStringType(FLookupField.DataType) then begin FCaseSensitive := True; try CheckFieldType(FLookupField); except Result := False; Exit; end; end else FCaseSensitive := ACaseSensitive; if DisableControls then DataSet.DisableControls; try FBookmark := DataSet.GetBookmark; try Result := MatchesLookup(FLookupField); if not Result then begin if UseKey then Result := LocateKey else begin if FilterApplicable then Result := LocateFilter else Result := LocateFull; end; if not Result then SetToBookmark(DataSet, FBookmark); end; finally FLookupValue := ''; FLookupField := nil; DataSet.FreeBookmark(FBookmark); FBookmark := nil; end; finally if DisableControls then DataSet.EnableControls; end; end; function TJvLocateObject.UseKey: Boolean; begin Result := False; end; procedure TJvLocateObject.ActiveChanged; begin end; function TJvLocateObject.MatchesLookup(Field: TField): Boolean; var Temp: string; begin Temp := Field.AsString; if not LookupExact then SetLength(Temp, Min(Length(FLookupValue), Length(Temp))); if CaseSensitive then Result := AnsiSameStr(Temp, LookupValue) else Result := AnsiSameText(Temp, LookupValue); end; function CreateLocate(DataSet: TDataSet): TJvLocateObject; begin if Assigned(CreateLocateObject) then Result := CreateLocateObject() else Result := TJvLocateObject.Create; if (Result <> nil) and (DataSet <> nil) then Result.DataSet := DataSet; end; { DataSet locate routines } function DataSetLocateThrough(DataSet: TDataSet; const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): Boolean; var FieldCount: Integer; Fields: TList; Bookmark: TBookmark; function CompareField(Field: TField; const Value: Variant): Boolean; var S: string; begin if Field.DataType in [ftString{$IFDEF UNICODE}, ftWideString{$ENDIF UNICODE}] then begin if Value = Null then Result := Field.IsNull else begin S := Field.AsString; if loPartialKey in Options then Delete(S, Length(Value) + 1, MaxInt); if loCaseInsensitive in Options then Result := AnsiSameText(S, Value) else Result := AnsiSameStr(S, Value); end; end else Result := (Field.Value = Value); end; function CompareRecord: Boolean; var I: Integer; begin // Works with the KeyValues variant like TCustomClientDataSet.LocateRecord if (FieldCount = 1) and not VarIsArray(KeyValues) then Result := CompareField(TField(Fields[0]), KeyValues) else begin Result := True; for I := 0 to FieldCount - 1 do Result := Result and CompareField(TField(Fields[I]), KeyValues[I]); end; end; begin Result := False; DataSet.CheckBrowseMode; if DataSet.IsEmpty then Exit; Fields := TList.Create; try DataSet.GetFieldList(Fields, KeyFields); FieldCount := Fields.Count; Result := CompareRecord; if Result then Exit; DataSet.DisableControls; try Bookmark := DataSet.Bookmark; try DataSet.First; while not DataSet.Eof do begin Result := CompareRecord; if Result then Break; DataSet.Next; end; finally if not Result and DataSet.BookmarkValid(TBookmark(Bookmark)) then DataSet.Bookmark := Bookmark; end; finally DataSet.EnableControls; end; finally Fields.Free; end; end; { DataSetSortedSearch. Navigate on sorted DataSet routine. } function DataSetSortedSearch(DataSet: TDataSet; const Value, FieldName: string; CaseInsensitive: Boolean): Boolean; var L, H, I: Longint; CurrentPos: Longint; CurrentValue: string; BookMk: TBookmark; Field: TField; function UpStr(const Value: string): string; begin if CaseInsensitive then Result := AnsiUpperCase(Value) else Result := Value; end; function GetCurrentStr: string; begin Result := Field.AsString; if Length(Result) > Length(Value) then SetLength(Result, Length(Value)); Result := UpStr(Result); end; begin Result := False; if DataSet = nil then Exit; Field := DataSet.FindField(FieldName); if Field = nil then Exit; if Field.DataType in [ftString{$IFDEF UNICODE}, ftWideString{$ENDIF UNICODE}] then begin DataSet.DisableControls; BookMk := DataSet.GetBookmark; try L := 0; DataSet.First; CurrentPos := 0; H := DataSet.RecordCount - 1; if Value <> '' then begin while L <= H do begin I := (L + H) shr 1; if I <> CurrentPos then DataSet.MoveBy(I - CurrentPos); CurrentPos := I; CurrentValue := GetCurrentStr; if UpStr(Value) > CurrentValue then L := I + 1 else begin H := I - 1; if UpStr(Value) = CurrentValue then Result := True; end; end; if Result then begin if L <> CurrentPos then DataSet.MoveBy(L - CurrentPos); while (L < DataSet.RecordCount) and (UpStr(Value) <> GetCurrentStr) do begin Inc(L); DataSet.MoveBy(1); end; end; end else Result := True; if not Result then SetToBookmark(DataSet, BookMk); finally DataSet.FreeBookmark(BookMk); DataSet.EnableControls; end; end else DatabaseErrorFmt(SFieldTypeMismatch, [Field.DisplayName]); end; { Save and restore DataSet Fields layout } //function DataSetSectionName(DataSet: TDataSet): string; //begin // if (DataSet.Owner <> nil) and (DataSet.Owner is TCustomForm) then // Result := GetDefaultSection(DataSet.Owner as TCustomForm) // else // Result := DataSet.Name; //end; // //function CheckSection(DataSet: TDataSet; const Section: string): string; //begin // Result := Section; // if Result = '' then // Result := DataSetSectionName(DataSet); //end; // //procedure InternalSaveFields(DataSet: TDataSet; AppStorage: TJvCustomAppStorage; const Path: string); //var // I: Integer; // Field: TField; //begin // AppStorage.BeginUpdate; // try // for I := 0 to DataSet.FieldCount - 1 do // begin // Field := DataSet.Fields[i]; // AppStorage.WriteString(AppStorage.ConcatPaths([CheckSection(DataSet, Path), // DataSet.Name + Field.FieldName]), // Format('%d,%d,%d', [Field.Index, Field.DisplayWidth, Integer(Field.Visible)])); // end; // finally // AppStorage.EndUpdate; // end; //end; // //procedure InternalRestoreFields(DataSet: TDataSet; AppStorage: TJvCustomAppStorage; // const Path: string; RestoreVisible: Boolean); //type // TFieldInfo = record // Field: TField; // EndIndex: Integer; // end; // TFieldArray = array of TFieldInfo; //const // Delims = [' ', ',']; //var // I, J: Integer; // S: string; // FieldArray: TFieldArray; //begin // SetLength(FieldArray, DataSet.FieldCount); // AppStorage.BeginUpdate; // try // for I := 0 to DataSet.FieldCount - 1 do // begin // S := AppStorage.ReadString(AppStorage.ConcatPaths([CheckSection(DataSet, Path), // DataSet.Name + DataSet.Fields[I].FieldName]), ''); // FieldArray[I].Field := DataSet.Fields[I]; // FieldArray[I].EndIndex := DataSet.Fields[I].Index; // if S <> '' then // begin // FieldArray[I].EndIndex := StrToIntDef(ExtractWord(1, S, Delims), // FieldArray[I].EndIndex); // DataSet.Fields[I].DisplayWidth := StrToIntDef(ExtractWord(2, S, Delims), // DataSet.Fields[I].DisplayWidth); // if RestoreVisible then // DataSet.Fields[I].Visible := Boolean(StrToIntDef(ExtractWord(3, S, Delims), // Integer(DataSet.Fields[I].Visible))); // end; // end; // for I := 0 to DataSet.FieldCount - 1 do // begin // for J := 0 to DataSet.FieldCount - 1 do // begin // if FieldArray[J].EndIndex = I then // begin // FieldArray[J].Field.Index := FieldArray[J].EndIndex; // Break; // end; // end; // end; // finally // AppStorage.EndUpdate; // FieldArray := nil; // end; //end; // //procedure SaveFields(DataSet: TDataSet; AppStorage: TJvCustomAppStorage; const Path: string); //begin // InternalSaveFields(DataSet, AppStorage, AppStorage.ConcatPaths([Path, DataSetSectionName(DataSet)])); //end; // //procedure RestoreFields(DataSet: TDataSet; AppStorage: TJvCustomAppStorage; const Path: string; // RestoreVisible: Boolean); //begin // InternalRestoreFields(DataSet, AppStorage, AppStorage.ConcatPaths([DataSetSectionName(DataSet)]), // RestoreVisible); //end; function ExtractFieldNameEx(const Fields: String; var Pos: Integer): string; begin Result := ExtractFieldName(Fields, Pos); end; function IsDataSetEmpty(DataSet: TDataSet): Boolean; begin Result := (not DataSet.Active) or (DataSet.Eof and DataSet.Bof); end; { SQL expressions } function DateToSQL(Value: TDateTime): string; begin Result := IntToStr(Trunc(Value)); end; function FormatSQLDateRange(Date1, Date2: TDateTime; const FieldName: string): string; begin Result := TrueExpr; if (Date1 = Date2) and (Date1 <> NullDate) then begin Result := Format('%s = %s', [FieldName, FormatDateTime(ServerDateFmt, Date1)]); end else if (Date1 <> NullDate) or (Date2 <> NullDate) then begin if Date1 = NullDate then Result := Format('%s < %s', [FieldName, FormatDateTime(ServerDateFmt, IncDay(Date2, 1))]) else if Date2 = NullDate then Result := Format('%s > %s', [FieldName, FormatDateTime(ServerDateFmt, IncDay(Date1, -1))]) else Result := Format('(%s < %s) AND (%s > %s)', [FieldName, FormatDateTime(ServerDateFmt, IncDay(Date2, 1)), FieldName, FormatDateTime(ServerDateFmt, IncDay(Date1, -1))]); end; end; function FormatSQLDateRangeEx(Date1, Date2: TDateTime; const FieldName: string): string; begin Result := TrueExpr; if (Date1 <> NullDate) or (Date2 <> NullDate) then begin if Date1 = NullDate then Result := Format('%s < %s', [FieldName, FormatDateTime(ServerDateFmt, IncDay(Date2, 1))]) else if Date2 = NullDate then Result := Format('%s >= %s', [FieldName, FormatDateTime(ServerDateFmt, Date1)]) else Result := Format('(%s < %s) AND (%s >= %s)', [FieldName, FormatDateTime(ServerDateFmt, IncDay(Date2, 1)), FieldName, FormatDateTime(ServerDateFmt, Date1)]); end; end; function FormatSQLNumericRange(const FieldName: string; LowValue, HighValue, LowEmpty, HighEmpty: Double; Inclusive: Boolean): string; const Operators: array[Boolean, 1..2] of string[2] = (('>', '<'), ('>=', '<=')); begin Result := TrueExpr; if (LowValue = HighValue) and (LowValue <> LowEmpty) then Result := Format('%s = %g', [FieldName, LowValue]) else if (LowValue <> LowEmpty) or (HighValue <> HighEmpty) then begin if LowValue = LowEmpty then Result := Format('%s %s %g', [FieldName, Operators[Inclusive, 2], HighValue]) else if HighValue = HighEmpty then Result := Format('%s %s %g', [FieldName, Operators[Inclusive, 1], LowValue]) else Result := Format('(%s %s %g) AND (%s %s %g)', [FieldName, Operators[Inclusive, 2], HighValue, FieldName, Operators[Inclusive, 1], LowValue]); end; end; function StrMaskSQL(const Value: string): string; begin if (Pos('*', Value) = 0) and (Pos('?', Value) = 0) and (Value <> '') then Result := '*' + Value + '*' else Result := Value; end; function FormatSQLCondition(const FieldName, AOperator, Value: string; FieldType: TFieldType; Exact: Boolean): string; var EmptyValue: Boolean; FieldValue: string; DateValue: TDateTime; LogicOperator: string; begin FieldValue := ''; DateValue := NullDate; Exact := Exact or not (FieldType in [ftString{$IFDEF UNICODE}, ftWideString{$ENDIF UNICODE}, ftDate, ftTime, ftDateTime]); if FieldType in [ftDate, ftTime, ftDateTime] then begin DateValue := StrToDateDef(Value, NullDate); EmptyValue := (DateValue = NullDate); FieldValue := FormatDateTime(ServerDateFmt, DateValue); end else begin FieldValue := Value; EmptyValue := FieldValue = ''; if not (Exact or EmptyValue) then FieldValue := ReplaceStr(ReplaceStr(StrMaskSQL(FieldValue), '*', '%'), '?', '_'); if FieldType in [ftString{$IFDEF UNICODE}, ftWideString{$ENDIF UNICODE}] then FieldValue := '''' + FieldValue + ''''; end; LogicOperator := AOperator; if LogicOperator = '' then begin if Exact then LogicOperator := '=' else begin if FieldType in [ftString{$IFDEF UNICODE}, ftWideString{$ENDIF UNICODE}] then LogicOperator := 'LIKE' else LogicOperator := '>='; end; end; if EmptyValue then Result := TrueExpr else if (FieldType = ftDateTime) and Exact then begin DateValue := IncDay(DateValue, 1); Result := Format('(%s >= %s) and (%s < %s)', [FieldName, FieldValue, FieldName, FormatDateTime(ServerDateFmt, DateValue)]); end else Result := Format('%s %s %s', [FieldName, LogicOperator, FieldValue]); end; function FormatAnsiSQLCondition(const FieldName, AOperator, Value: string; FieldType: TFieldType; Exact: Boolean): string; var S, Esc: string; begin Esc := ''; if not Exact and (FieldType in [ftString{$IFDEF UNICODE}, ftWideString{$ENDIF UNICODE}]) then begin S := ReplaceStr(ReplaceStr(ReplaceStr(Value, '/', '//'), '_', '/_'), '%', '/%'); if S <> Value then Esc := ' ESCAPE''/'''; end else S := Value; Result := FormatSQLCondition(FieldName, AOperator, S, FieldType, Exact) + Esc; end; procedure CheckRequiredField(Field: TField); begin if not Field.ReadOnly and not Field.Calculated and Field.IsNull then begin Field.FocusControl; DatabaseErrorFmt(SNeedField, [Field.DisplayName]); end; end; procedure CheckRequiredFields(const Fields: array of TField); var I: Integer; begin for I := Low(Fields) to High(Fields) do CheckRequiredField(Fields[I]); end; type TDataSetAccess = class(TDataSet); procedure GotoBookmarkEx(DataSet: TDataSet; const Bookmark: TBookmark; Mode: TResyncMode; ForceScrollEvents: Boolean); var DS: TDataSetAccess; begin if (DataSet <> nil) and (Bookmark <> nil) then begin DS := TDataSetAccess(DataSet); DS.CheckBrowseMode; if ForceScrollEvents or (rmCenter in Mode) then DS.DoBeforeScroll; DS.InternalGotoBookmark(Pointer(Bookmark)); DS.Resync(Mode); if ForceScrollEvents or (rmCenter in Mode) then DS.DoAfterScroll; end; end; procedure AssignRecord(Source, Dest: TDataSet; ByName: Boolean); var I: Integer; F, FSrc: TField; begin if not (Dest.State in dsEditModes) then _DBError(SNotEditing); if ByName then begin for I := 0 to Source.FieldCount - 1 do begin F := Dest.FindField(Source.Fields[I].FieldName); FSrc := Source.Fields[i]; if (F <> nil) and (F.DataType <> ftAutoInc) then begin if FSrc.IsNull then F.Value := FSrc.Value else case F.DataType of ftString: F.AsString := FSrc.AsString; ftInteger: F.AsInteger := FSrc.AsInteger; ftBoolean: F.AsBoolean := FSrc.AsBoolean; ftFloat: F.AsFloat := FSrc.AsFloat; ftCurrency: F.AsCurrency := FSrc.AsCurrency; ftDate: F.AsDateTime := FSrc.AsDateTime; ftDateTime: F.AsDateTime := FSrc.AsDateTime; else F.Value := FSrc.Value; end; end; end; end else begin for I := 0 to Min(Source.FieldDefs.Count - 1, Dest.FieldDefs.Count - 1) do begin F := Dest.FindField(Dest.FieldDefs[I].Name); FSrc := Source.FindField(Source.FieldDefs[I].Name); if (F <> nil) and (FSrc <> nil) and (F.DataType <> ftAutoInc) then begin if FSrc.IsNull then F.Value := FSrc.Value else case F.DataType of ftString: F.AsString := FSrc.AsString; ftInteger: F.AsInteger := FSrc.AsInteger; ftBoolean: F.AsBoolean := FSrc.AsBoolean; ftFloat: F.AsFloat := FSrc.AsFloat; ftCurrency: F.AsCurrency := FSrc.AsCurrency; ftDate: F.AsDateTime := FSrc.AsDateTime; ftDateTime: F.AsDateTime := FSrc.AsDateTime; else F.Value := FSrc.Value; end; end; end; end; end; end.