ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/branches/udr/client/FBClientAPI.pas
(Generate patch)

Comparing:
ibx/trunk/fbintf/client/FBClientAPI.pas (file contents), Revision 308 by tony, Sat Jul 18 10:26:30 2020 UTC vs.
ibx/branches/udr/client/FBClientAPI.pas (file contents), Revision 390 by tony, Sat Jan 22 16:15:12 2022 UTC

# Line 76 | Line 76 | uses
76    Classes,
77      {$IFDEF WINDOWS}Windows, {$ENDIF}
78      {$IFDEF FPC} Dynlibs, {$ENDIF}
79 <   IB, IBHeader, FBActivityMonitor, FBMessages, IBExternals;
79 >   IB, IBHeader, FBActivityMonitor, FBMessages, IBExternals, FmtBCD;
80  
81 < {For Linux see result of GetFirebirdLibList method}
81 > {For Linux see result of GetFirebirdLibListruntime/nongui/winipc.inc method}
82   {$IFDEF DARWIN}
83   const
84   FIREBIRD_SO2 = 'libfbclient.dylib';
# Line 91 | Line 91 | FIREBIRD_EMBEDDED = 'fbembed.dll';
91   {$ENDIF}
92  
93   const
94 +  {fb_shutdown reasons}
95 +  fb_shutrsn_svc_stopped          = -1;
96 +  fb_shutrsn_no_connection        = -2;
97 +  fb_shutrsn_app_stopped          = -3;
98 +  fb_shutrsn_signal               = -5;
99 +  fb_shutrsn_services             = -6;
100 +  fb_shutrsn_exit_called          = -7;
101 +
102 + const
103 +    DefaultTimeZoneFile = '/etc/timezone';
104 +
105 + const
106    IBLocalBufferLength = 512;
107    IBBigLocalBufferLength = IBLocalBufferLength * 2;
108    IBHugeLocalBufferLength = IBBigLocalBufferLength * 20;
# Line 103 | Line 115 | type
115  
116    { TFBStatus }
117  
118 <  TFBStatus = class(TFBInterfacedObject)
118 >  TFBStatus = class(TFBInterfacedObject, IStatus)
119    private
120      FIBDataBaseErrorMessages: TIBDataBaseErrorMessages;
121 +    FPrefix: AnsiString;
122 +    function SQLCodeSupported: boolean;
123    protected
124      FOwner: TFBClientAPI;
125 +    function GetIBMessage: Ansistring; virtual; abstract;
126 +    function GetSQLMessage: Ansistring;
127    public
128 <    constructor Create(aOwner: TFBClientAPI);
128 >    constructor Create(aOwner: TFBClientAPI; prefix: AnsiString='');
129 >    constructor Copy(src: TFBStatus);
130      function StatusVector: PStatusVector; virtual; abstract;
131 +    function Clone: IStatus; virtual; abstract;
132  
133      {IStatus}
134 <    function GetIBErrorCode: Long;
135 <    function Getsqlcode: Long;
134 >    function InErrorState: boolean; virtual; abstract;
135 >    function GetIBErrorCode: TStatusCode;
136 >    function Getsqlcode: TStatusCode;
137      function GetMessage: AnsiString;
138      function CheckStatusVector(ErrorCodes: array of TFBStatusCode): Boolean;
139      function GetIBDataBaseErrorMessages: TIBDataBaseErrorMessages;
# Line 127 | Line 146 | type
146    private
147      class var FEnvSetupDone: boolean;
148      class var FLibraryList: array of IFirebirdLibrary;
149 +  private
150      FFirebirdAPI: IFirebirdAPI;
151      FRequestedLibName: string;
152      function LoadIBLibrary: boolean;
# Line 144 | Line 164 | type
164      destructor Destroy; override;
165      class function GetFBLibrary(aLibPathName: string): IFirebirdLibrary;
166      class procedure FreeLibraries;
167 +    function SameLibrary(aLibName: string): boolean;
168  
169 +  public
170      {IFirebirdLibrary}
171      function GetHandle: TLibHandle;
172      function GetLibraryName: string;
# Line 157 | Line 179 | type
179  
180    TFBClientAPI = class(TFBInterfacedObject)
181    private
182 +    FLocalTimeZoneName: AnsiString; {Informal Time Zone Name from tzname e.g. GMT or BST}
183 +    FTZDataTimeZoneID: AnsiString; {TZData DB ID e.g. Europe/London}
184 +    FLocalTimeOffset: integer;
185 +    FIsDaylightSavingsTime: boolean;
186      class var FIBCS: TRTLCriticalSection;
187 +    function FBTimeStampToDateTime(aDate, aTime: longint): TDateTime;
188 +    procedure GetTZDataSettings;
189    protected
190      FFBLibrary: TFBLibrary;
191      function GetProcAddr(ProcName: PAnsiChar): Pointer;
192 +
193 +  protected type
194 +    Tfb_shutdown = function (timeout: uint;
195 +                                 const reason: int): int;
196 +                   {$IFDEF WINDOWS} stdcall; {$ELSE} cdecl; {$ENDIF}
197 +  protected
198 +    {FB Shutdown API}
199 +    fb_shutdown: Tfb_shutdown;
200 +
201    public
202      {Taken from legacy API}
166    isc_sqlcode: Tisc_sqlcode;
203      isc_sql_interprete: Tisc_sql_interprete;
204 <    isc_event_counts: Tisc_event_counts;
169 <    isc_event_block: Tisc_event_block;
170 <    isc_free: Tisc_free;
204 >    isc_sqlcode: Tisc_sqlcode;
205  
206      constructor Create(aFBLibrary: TFBLibrary);
207      procedure IBAlloc(var P; OldSize, NewSize: Integer);
208      procedure IBDataBaseError;
209      function LoadInterface: boolean; virtual;
210 +    procedure FBShutdown; virtual;
211      function GetAPI: IFirebirdAPI; virtual; abstract;
212      {$IFDEF UNIX}
213      function GetFirebirdLibList: string; virtual; abstract;
214      {$ENDIF}
215 +    function HasDecFloatSupport: boolean;
216 +    function HasInt128Support: boolean; virtual;
217 +    function HasLocalTZDB: boolean; virtual;
218 +    function HasExtendedTZSupport: boolean; virtual;
219 +    function HasTimeZoneSupport: boolean; virtual;
220  
221 +  public
222 +    property LocalTimeZoneName: AnsiString read FLocalTimeZoneName;
223 +    property TZDataTimeZoneID: AnsiString read FTZDataTimeZoneID;
224 +    property LocalTimeOffset: integer read FLocalTimeOffset;
225 +  public
226      {Encode/Decode}
227 <    procedure EncodeInteger(aValue: integer; len: integer; buffer: PByte);
228 <    function DecodeInteger(bufptr: PByte; len: short): integer; virtual; abstract;
229 <    procedure SQLEncodeDate(aDate: TDateTime; bufptr: PByte); virtual; abstract;
230 <    function SQLDecodeDate(byfptr: PByte): TDateTime; virtual; abstract;
231 <    procedure SQLEncodeTime(aTime: TDateTime; bufptr: PByte); virtual; abstract;
227 >    procedure EncodeInteger(aValue: int64; len: integer; buffer: PByte);
228 >    function DecodeInteger(bufptr: PByte; len: short): int64;
229 >    procedure SQLEncodeDate(aDate: TDateTime; bufptr: PByte);  virtual; abstract;
230 >    function SQLDecodeDate(byfptr: PByte): TDateTime;  virtual; abstract;
231 >    procedure SQLEncodeTime(aTime: TDateTime; bufptr: PByte);  virtual; abstract;
232      function SQLDecodeTime(bufptr: PByte): TDateTime;  virtual; abstract;
233      procedure SQLEncodeDateTime(aDateTime: TDateTime; bufptr: PByte); virtual; abstract;
234 <    function SQLDecodeDateTime(bufptr: PByte): TDateTime; virtual; abstract;
235 <    function FormatStatus(Status: TFBStatus): AnsiString; virtual; abstract;
234 >    function  SQLDecodeDateTime(bufptr: PByte): TDateTime; virtual; abstract;
235 >    function Int128ToStr(bufptr: PByte; scale: integer): AnsiString; virtual;
236 >    procedure StrToInt128(scale: integer; aValue: AnsiString; bufptr: PByte);
237 >      virtual;
238 >    procedure SQLDecFloatEncode(aValue: tBCD; SQLType: cardinal; bufptr: PByte); virtual;
239 >    function SQLDecFloatDecode(SQLType: cardinal;  bufptr: PByte): tBCD; virtual;
240  
241      {IFirebirdAPI}
242      function GetStatus: IStatus; virtual; abstract;
# Line 197 | Line 246 | type
246      function GetImplementationVersion: AnsiString;
247      function GetClientMajor: integer;  virtual; abstract;
248      function GetClientMinor: integer;  virtual; abstract;
249 < end;
249 >  end;
250 >
251 >    IJournallingHook = interface
252 >      ['{7d3e45e0-3628-416a-9e22-c20474825031}']
253 >      procedure TransactionStart(Tr: ITransaction);
254 >      function TransactionEnd(TransactionID: integer; Completion: TTrCompletionState): boolean;
255 >      procedure TransactionRetained(Tr: ITransaction; OldTransactionID: integer; Action: TTransactionAction);
256 >      procedure ExecQuery(Stmt: IStatement);
257 >      procedure ExecImmediateJnl(sql: AnsiString; tr: ITransaction);
258 >    end;
259  
260   implementation
261  
262   uses IBUtils, Registry,
263 <  {$IFDEF Unix} initc, dl, {$ENDIF}
263 >  {$IFDEF Unix} unix, initc, dl, {$ENDIF}
264   {$IFDEF FPC}
265   {$IFDEF WINDOWS }
266   WinDirs,
# Line 235 | Line 293 | end;
293  
294   procedure TFBLibrary.FreeFBLibrary;
295   begin
296 +  (FFirebirdAPI as TFBClientAPI).FBShutdown;
297    if FIBLibrary <> NilHandle then
298      FreeLibrary(FIBLibrary);
299    FIBLibrary := NilHandle;
300 +  FFBLibraryName := '';
301   end;
302  
303   function TFBLibrary.GetLibraryName: string;
# Line 289 | Line 349 | end;
349  
350   destructor TFBLibrary.Destroy;
351   begin
292  FFirebirdAPI := nil;
352    FreeFBLibrary;
353 +  FFirebirdAPI := nil;
354    inherited Destroy;
355   end;
356  
# Line 301 | Line 361 | begin
361    if aLibPathName <> '' then
362    begin
363      for i := 0 to Length(FLibraryList) - 1 do
364 <      if (FLibraryList[i] as TFBLibrary).FRequestedLibName = aLibPathName then
364 >    begin
365 >      if (FLibraryList[i] as TFBLibrary).SameLibrary(aLibPathName) then
366        begin
367          Result := FLibraryList[i];
368          Exit;
369        end;
370 +    end;
371      Result := Create(aLibPathName);
372    end;
373  
# Line 319 | Line 381 | begin
381    SetLength(FLibraryList,0);
382   end;
383  
384 + function TFBLibrary.SameLibrary(aLibName: string): boolean;
385 + begin
386 +  Result := FRequestedLibName = aLibName;
387 + end;
388 +
389   function TFBLibrary.GetHandle: TLibHandle;
390   begin
391    Result := FIBLibrary;
# Line 330 | Line 397 | constructor TFBClientAPI.Create(aFBLibra
397   begin
398    inherited Create;
399    FFBLibrary := aFBLibrary;
400 +  GetTZDataSettings;
401   end;
402  
403   procedure TFBClientAPI.IBAlloc(var P; OldSize, NewSize: Integer);
# Line 345 | Line 413 | begin
413    raise EIBInterBaseError.Create(GetStatus);
414   end;
415  
416 < {Under Unixes, if using an embedded server then set up local TMP and LOCK Directories}
349 <
350 < procedure TFBClientAPI.EncodeInteger(aValue: integer; len: integer; buffer: PByte);
416 > procedure TFBClientAPI.EncodeInteger(aValue: int64; len: integer; buffer: PByte);
417   begin
418    while len > 0 do
419    begin
# Line 358 | Line 424 | begin
424    end;
425   end;
426  
427 + (*
428 +  DecodeInteger is Translated from
429 +
430 + SINT64 API_ROUTINE isc_portable_integer(const UCHAR* ptr, SSHORT length)
431 + if (!ptr || length <= 0 || length > 8)
432 +        return 0;
433 +
434 + SINT64 value = 0;
435 + int shift = 0;
436 +
437 + while (--length > 0)
438 + {
439 +        value += ((SINT64) *ptr++) << shift;
440 +        shift += 8;
441 + }
442 +
443 + value += ((SINT64)(SCHAR) *ptr) << shift;
444 +
445 + return value;
446 + *)
447 +
448 + function TFBClientAPI.DecodeInteger(bufptr: PByte; len: short): int64;
449 + var shift: integer;
450 + begin
451 +  Result := 0;
452 +  if (BufPtr = nil) or (len <= 0) or (len > 8) then
453 +    Exit;
454 +
455 +  shift := 0;
456 +  dec(len);
457 +  while len > 0 do
458 +  begin
459 +    Result := Result + (int64(bufptr^) shl shift);
460 +    Inc(bufptr);
461 +    shift := shift + 8;
462 +    dec(len);
463 +  end;
464 +  Result := Result + (int64(bufptr^) shl shift);
465 + end;
466 +
467 + function TFBClientAPI.Int128ToStr(bufptr: PByte; scale: integer): AnsiString;
468 + begin
469 +  if not HasInt128Support then
470 +    IBError(ibxeNotSupported,[]);
471 + end;
472 +
473 + procedure TFBClientAPI.StrToInt128(scale: integer; aValue: AnsiString; bufptr: PByte);
474 + begin
475 +  if not HasInt128Support then
476 +    IBError(ibxeNotSupported,[]);
477 + end;
478 +
479 + procedure TFBClientAPI.SQLDecFloatEncode(aValue: tBCD; SQLType: cardinal;
480 +  bufptr: PByte);
481 + begin
482 +  if not HasDecFloatSupport then
483 +    IBError(ibxeNotSupported,[]);
484 + end;
485 +
486 + function TFBClientAPI.SQLDecFloatDecode(SQLType: cardinal; bufptr: PByte): tBCD;
487 + begin
488 +  if not HasDecFloatSupport then
489 +    IBError(ibxeNotSupported,[]);
490 + end;
491 +
492   function TFBClientAPI.IsLibraryLoaded: boolean;
493   begin
494    Result := FFBLibrary.IBLibrary <> NilHandle;
# Line 368 | Line 499 | begin
499    Result := FFBLibrary;
500   end;
501  
502 < function TFBClientAPI.GetImplementationVersion: AnsiString;
502 > function TFBClientAPI.FBTimeStampToDateTime(aDate, aTime: longint): TDateTime;
503   begin
504 <  Result := Format('%d.%d',[GetClientMajor,GetClientMinor]);
504 >  {aDate/aTime are in TTimestamp format but aTime is decimilliseconds}
505 >  aDate := aDate - DateDelta;
506 >  if aDate < 0 then
507 >    Result := trunc(aDate) - abs(frac(aTime / (MSecsPerDay*10)))
508 >  else
509 >    Result := trunc(aDate) + abs(frac(aTime / (MSecsPerDay*10)));
510   end;
511  
512 + {$IFDEF UNIX}
513 +
514 + procedure TFBClientAPI.GetTZDataSettings;
515 + var S: TStringList;
516 + begin
517 +  FLocalTimeOffset := GetLocalTimeOffset;
518 +  {$if declared(Gettzname)}
519 +  FLocalTimeZoneName := Gettzname(tzdaylight);
520 +  {$else}
521 +  FLocalTimeZoneName := tzname[tzdaylight];
522 +  {$ifend}
523 +  FIsDaylightSavingsTime := tzdaylight;
524 +  if FileExists(DefaultTimeZoneFile) then
525 +  begin
526 +    S := TStringList.Create;
527 +    try
528 +      S.LoadFromFile(DefaultTimeZoneFile);
529 +      if S.Count > 0 then
530 +        FTZDataTimeZoneID := S[0];
531 +    finally
532 +      S.Free;
533 +    end;
534 +  end;
535 + end;
536 + {$ENDIF}
537 +
538 + {$IFDEF WINDOWS}
539 + procedure TFBClientAPI.GetTZDataSettings;
540 + var TZInfo: TTimeZoneInformation;
541 + begin
542 +  FIsDaylightSavingsTime := false;
543 +  {is there any way of working out the default TZData DB time zone ID under Windows?}
544 +  case GetTimeZoneInformation(TZInfo) of
545 +    TIME_ZONE_ID_UNKNOWN:
546 +      begin
547 +        FLocalTimeZoneName := '';
548 +        FLocalTimeOffset := 0;
549 +      end;
550 +    TIME_ZONE_ID_STANDARD:
551 +      begin
552 +        FLocalTimeZoneName := strpas(PWideChar(@TZInfo.StandardName));
553 +        FLocalTimeOffset := TZInfo.Bias;
554 +      end;
555 +    TIME_ZONE_ID_DAYLIGHT:
556 +      begin
557 +        FLocalTimeZoneName := strpas(PWideChar(@TZInfo.DaylightName));
558 +        FLocalTimeOffset := TZInfo.DayLightBias;
559 +        FIsDaylightSavingsTime := true;
560 +      end;
561 +  end;
562 + end;
563 + {$ENDIF}
564 +
565   function TFBClientAPI.GetProcAddr(ProcName: PAnsiChar): Pointer;
566   begin
567 <  Result := GetProcAddress(FFBLibrary.IBLibrary, ProcName);
567 >  Result := nil;
568 >  if assigned(FFBLibrary) and (FFBLibrary.IBLibrary <> NilHandle) then
569 >    Result := GetProcAddress(FFBLibrary.IBLibrary, ProcName);
570    if not Assigned(Result) then
571      raise Exception.CreateFmt(SFirebirdAPIFuncNotFound,[ProcName]);
572   end;
573  
574 + function TFBClientAPI.HasDecFloatSupport: boolean;
575 + begin
576 +  Result := GetClientMajor >= 4;
577 + end;
578 +
579 + function TFBClientAPI.HasInt128Support: boolean;
580 + begin
581 +  Result := false;
582 + end;
583 +
584 + function TFBClientAPI.HasLocalTZDB: boolean;
585 + begin
586 +  Result := false;
587 + end;
588 +
589 + function TFBClientAPI.HasExtendedTZSupport: boolean;
590 + begin
591 +  Result := false;
592 + end;
593 +
594 + function TFBClientAPI.HasTimeZoneSupport: boolean;
595 + begin
596 +  Result := false;
597 + end;
598 +
599 + function TFBClientAPI.GetImplementationVersion: AnsiString;
600 + begin
601 +  Result := Format('%d.%d',[GetClientMajor,GetClientMinor]);
602 + end;
603 +
604   function TFBClientAPI.LoadInterface: boolean;
605   begin
606    isc_sqlcode := GetProcAddr('isc_sqlcode'); {do not localize}
607    isc_sql_interprete := GetProcAddr('isc_sql_interprete'); {do not localize}
608 <  isc_event_counts := GetProcAddr('isc_event_counts'); {do not localize}
609 <  isc_event_block := GetProcAddr('isc_event_block'); {do not localize}
610 <  isc_free := GetProcAddr('isc_free'); {do not localize}
611 <  Result := assigned(isc_free);
608 >  fb_shutdown := GetProcAddr('fb_shutdown'); {do not localize}
609 >  Result := true; {don't case if these fail to load}
610 > end;
611 >
612 > procedure TFBClientAPI.FBShutdown;
613 > begin
614 >  if assigned(fb_shutdown) then
615 >    fb_shutdown(0,fb_shutrsn_exit_called);
616   end;
617  
618   { TFBStatus }
619  
620 < constructor TFBStatus.Create(aOwner: TFBClientAPI);
620 > function TFBStatus.SQLCodeSupported: boolean;
621 > begin
622 >  Result:= (FOwner <> nil) and assigned(FOwner.isc_sqlcode) and  assigned(FOwner.isc_sql_interprete);
623 > end;
624 >
625 > function TFBStatus.GetSQLMessage: Ansistring;
626 > var local_buffer: array[0..IBHugeLocalBufferLength - 1] of AnsiChar;
627 > begin
628 >  Result := '';
629 >  if (FOwner <> nil) and assigned(FOwner.isc_sql_interprete) then
630 >  begin
631 >     FOwner.isc_sql_interprete(Getsqlcode, local_buffer, sizeof(local_buffer));
632 >     Result := strpas(local_buffer);
633 >  end;
634 > end;
635 >
636 > constructor TFBStatus.Create(aOwner: TFBClientAPI; prefix: AnsiString);
637   begin
638    inherited Create;
639    FOwner := aOwner;
640 <  FIBDataBaseErrorMessages := [ShowSQLMessage, ShowIBMessage];
640 >  FPrefix := prefix;
641 >  FIBDataBaseErrorMessages := [ShowIBMessage];
642 > end;
643 >
644 > constructor TFBStatus.Copy(src: TFBStatus);
645 > begin
646 >  inherited Create;
647 >  FOwner := src.FOwner;
648 >  FPrefix := src.FPrefix;
649 >  SetIBDataBaseErrorMessages(src.GetIBDataBaseErrorMessages);
650   end;
651  
652 < function TFBStatus.GetIBErrorCode: Long;
652 > function TFBStatus.GetIBErrorCode: TStatusCode;
653   begin
654    Result := StatusVector^[1];
655   end;
656  
657 < function TFBStatus.Getsqlcode: Long;
657 > function TFBStatus.Getsqlcode: TStatusCode;
658   begin
659 <  with FOwner do
660 <    Result := isc_sqlcode(PISC_STATUS(StatusVector));
659 >  if (FOwner <> nil) and assigned(FOwner.isc_sqlcode) then
660 >    Result := FOwner.isc_sqlcode(PISC_STATUS(StatusVector))
661 >  else
662 >    Result := -999; {generic SQL Code}
663   end;
664  
665   function TFBStatus.GetMessage: AnsiString;
666 < var local_buffer: array[0..IBHugeLocalBufferLength - 1] of AnsiChar;
415 <    IBDataBaseErrorMessages: TIBDataBaseErrorMessages;
416 <    sqlcode: Long;
666 > var IBDataBaseErrorMessages: TIBDataBaseErrorMessages;
667   begin
668 <  Result := '';
668 >  Result := FPrefix;
669    IBDataBaseErrorMessages := FIBDataBaseErrorMessages;
670 <  sqlcode := Getsqlcode;
671 <  if (ShowSQLCode in IBDataBaseErrorMessages) then
672 <    Result := Result + 'SQLCODE: ' + IntToStr(sqlcode); {do not localize}
673 <
674 <  Exclude(IBDataBaseErrorMessages, ShowSQLMessage);
675 <  if (ShowSQLMessage in IBDataBaseErrorMessages) then
676 <  begin
677 <    with FOwner do
678 <      isc_sql_interprete(sqlcode, local_buffer, sizeof(local_buffer));
679 <    if (ShowSQLCode in FIBDataBaseErrorMessages) then
680 <      Result := Result + CRLF;
431 <    Result := Result + strpas(local_buffer);
670 >  if SQLCodeSupported then
671 >  begin
672 >    if (ShowSQLCode in IBDataBaseErrorMessages) then
673 >      Result := Result + 'SQLCODE: ' + IntToStr(Getsqlcode); {do not localize}
674 >
675 >    if (ShowSQLMessage in IBDataBaseErrorMessages) then
676 >    begin
677 >      if ShowSQLCode in IBDataBaseErrorMessages then
678 >        Result := Result + LineEnding;
679 >      Result := Result + GetSQLMessage;
680 >    end;
681    end;
682  
683    if (ShowIBMessage in IBDataBaseErrorMessages) then
684    begin
685 <    if (ShowSQLCode in IBDataBaseErrorMessages) or
437 <       (ShowSQLMessage in IBDataBaseErrorMessages) then
685 >    if Result <> FPrefix then
686        Result := Result + LineEnding;
687 <    Result := Result + LineEnding + FOwner.FormatStatus(self);
687 >    Result := Result + 'Engine Code: ' + IntToStr(GetIBErrorCode) + LineEnding + GetIBMessage;
688    end;
689    if (Result <> '') and (Result[Length(Result)] = '.') then
690      Delete(Result, Length(Result), 1);

Comparing:
ibx/trunk/fbintf/client/FBClientAPI.pas (property svn:eol-style), Revision 308 by tony, Sat Jul 18 10:26:30 2020 UTC vs.
ibx/branches/udr/client/FBClientAPI.pas (property svn:eol-style), Revision 390 by tony, Sat Jan 22 16:15:12 2022 UTC

# Line 0 | Line 1
1 + native

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines