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 263 by tony, Thu Dec 6 15:55:01 2018 UTC vs.
ibx/branches/udr/client/FBClientAPI.pas (file contents), Revision 379 by tony, Mon Jan 10 10:08:03 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 90 | Line 90 | FIREBIRD_CLIENT = 'fbclient.dll'; {do no
90   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;
109 +
110   type
111    TStatusVector              = array[0..19] of NativeInt;
112    PStatusVector              = ^TStatusVector;
# Line 101 | Line 118 | type
118    TFBStatus = class(TFBInterfacedObject)
119    private
120      FIBDataBaseErrorMessages: TIBDataBaseErrorMessages;
121 +    FPrefix: AnsiString;
122    protected
123      FOwner: TFBClientAPI;
124 +    function GetIBMessage: Ansistring; virtual; abstract;
125 +    function GetSQLMessage: Ansistring;
126    public
127 <    constructor Create(aOwner: TFBClientAPI);
127 >    constructor Create(aOwner: TFBClientAPI; prefix: AnsiString='');
128      function StatusVector: PStatusVector; virtual; abstract;
129  
130      {IStatus}
131 <    function GetIBErrorCode: Long;
132 <    function Getsqlcode: Long;
131 >    function GetIBErrorCode: TStatusCode;
132 >    function Getsqlcode: TStatusCode;
133      function GetMessage: AnsiString;
134      function CheckStatusVector(ErrorCodes: array of TFBStatusCode): Boolean;
135      function GetIBDataBaseErrorMessages: TIBDataBaseErrorMessages;
# Line 122 | Line 142 | type
142    private
143      class var FEnvSetupDone: boolean;
144      class var FLibraryList: array of IFirebirdLibrary;
145 +  private
146      FFirebirdAPI: IFirebirdAPI;
147      FRequestedLibName: string;
148      function LoadIBLibrary: boolean;
# Line 139 | Line 160 | type
160      destructor Destroy; override;
161      class function GetFBLibrary(aLibPathName: string): IFirebirdLibrary;
162      class procedure FreeLibraries;
163 +    function SameLibrary(aLibName: string): boolean;
164  
165 +  public
166      {IFirebirdLibrary}
167      function GetHandle: TLibHandle;
168      function GetLibraryName: string;
# Line 152 | Line 175 | type
175  
176    TFBClientAPI = class(TFBInterfacedObject)
177    private
178 +    FLocalTimeZoneName: AnsiString; {Informal Time Zone Name from tzname e.g. GMT or BST}
179 +    FTZDataTimeZoneID: AnsiString; {TZData DB ID e.g. Europe/London}
180 +    FLocalTimeOffset: integer;
181 +    FIsDaylightSavingsTime: boolean;
182      class var FIBCS: TRTLCriticalSection;
183 +    function FBTimeStampToDateTime(aDate, aTime: longint): TDateTime;
184 +    procedure GetTZDataSettings;
185    protected
186      FFBLibrary: TFBLibrary;
187      function GetProcAddr(ProcName: PAnsiChar): Pointer;
188 +
189 +  protected type
190 +    Tfb_shutdown = function (timeout: uint;
191 +                                 const reason: int): int;
192 +                   {$IFDEF WINDOWS} stdcall; {$ELSE} cdecl; {$ENDIF}
193 +  protected
194 +    {FB Shutdown API}
195 +    fb_shutdown: Tfb_shutdown;
196 +
197    public
198      {Taken from legacy API}
161    isc_sqlcode: Tisc_sqlcode;
199      isc_sql_interprete: Tisc_sql_interprete;
200 <    isc_interprete: Tisc_interprete;
164 <    isc_event_counts: Tisc_event_counts;
165 <    isc_event_block: Tisc_event_block;
166 <    isc_free: Tisc_free;
200 >    isc_sqlcode: Tisc_sqlcode;
201  
202      constructor Create(aFBLibrary: TFBLibrary);
203      procedure IBAlloc(var P; OldSize, NewSize: Integer);
204      procedure IBDataBaseError;
205      function LoadInterface: boolean; virtual;
206 +    procedure FBShutdown; virtual;
207      function GetAPI: IFirebirdAPI; virtual; abstract;
208      {$IFDEF UNIX}
209      function GetFirebirdLibList: string; virtual; abstract;
210      {$ENDIF}
211 +    function HasDecFloatSupport: boolean;
212 +    function HasInt128Support: boolean; virtual;
213 +    function HasLocalTZDB: boolean; virtual;
214 +    function HasExtendedTZSupport: boolean; virtual;
215 +    function HasTimeZoneSupport: boolean; virtual;
216  
217 +  public
218 +    property LocalTimeZoneName: AnsiString read FLocalTimeZoneName;
219 +    property TZDataTimeZoneID: AnsiString read FTZDataTimeZoneID;
220 +    property LocalTimeOffset: integer read FLocalTimeOffset;
221 +  public
222      {Encode/Decode}
223 <    procedure EncodeInteger(aValue: integer; len: integer; buffer: PByte);
224 <    function DecodeInteger(bufptr: PByte; len: short): integer; virtual; abstract;
225 <    procedure SQLEncodeDate(aDate: TDateTime; bufptr: PByte); virtual; abstract;
226 <    function SQLDecodeDate(byfptr: PByte): TDateTime; virtual; abstract;
227 <    procedure SQLEncodeTime(aTime: TDateTime; bufptr: PByte); virtual; abstract;
223 >    procedure EncodeInteger(aValue: int64; len: integer; buffer: PByte);
224 >    function DecodeInteger(bufptr: PByte; len: short): int64;
225 >    procedure SQLEncodeDate(aDate: TDateTime; bufptr: PByte);  virtual; abstract;
226 >    function SQLDecodeDate(byfptr: PByte): TDateTime;  virtual; abstract;
227 >    procedure SQLEncodeTime(aTime: TDateTime; bufptr: PByte);  virtual; abstract;
228      function SQLDecodeTime(bufptr: PByte): TDateTime;  virtual; abstract;
229      procedure SQLEncodeDateTime(aDateTime: TDateTime; bufptr: PByte); virtual; abstract;
230 <    function SQLDecodeDateTime(bufptr: PByte): TDateTime; virtual; abstract;
231 <
230 >    function  SQLDecodeDateTime(bufptr: PByte): TDateTime; virtual; abstract;
231 >    function Int128ToStr(bufptr: PByte; scale: integer): AnsiString; virtual;
232 >    procedure StrToInt128(scale: integer; aValue: AnsiString; bufptr: PByte);
233 >      virtual;
234 >    procedure SQLDecFloatEncode(aValue: tBCD; SQLType: cardinal; bufptr: PByte); virtual;
235 >    function SQLDecFloatDecode(SQLType: cardinal;  bufptr: PByte): tBCD; virtual;
236  
237      {IFirebirdAPI}
238      function GetStatus: IStatus; virtual; abstract;
239      function IsLibraryLoaded: boolean;
240      function IsEmbeddedServer: boolean; virtual; abstract;
241      function GetFBLibrary: IFirebirdLibrary;
242 < end;
242 >    function GetImplementationVersion: AnsiString;
243 >    function GetClientMajor: integer;  virtual; abstract;
244 >    function GetClientMinor: integer;  virtual; abstract;
245 >  end;
246 >
247 >    IJournallingHook = interface
248 >      ['{7d3e45e0-3628-416a-9e22-c20474825031}']
249 >      procedure TransactionStart(Tr: ITransaction);
250 >      function TransactionEnd(TransactionID: integer; Action: TTransactionAction): boolean;
251 >      procedure TransactionRetained(Tr: ITransaction; OldTransactionID: integer; Action: TTransactionAction);
252 >      procedure ExecQuery(Stmt: IStatement);
253 >    end;
254  
255   implementation
256  
257   uses IBUtils, Registry,
258 <  {$IFDEF Unix} initc, dl, {$ENDIF}
258 >  {$IFDEF Unix} unix, initc, dl, {$ENDIF}
259   {$IFDEF FPC}
260   {$IFDEF WINDOWS }
261   WinDirs,
# Line 205 | Line 265 | WinDirs,
265   {$ENDIF}
266   SysUtils;
267  
208 const
209  IBLocalBufferLength = 512;
210  IBBigLocalBufferLength = IBLocalBufferLength * 2;
211  IBHugeLocalBufferLength = IBBigLocalBufferLength * 20;
212
268   {$IFDEF UNIX}
269   {$I 'include/uloadlibrary.inc'}
270   {$ELSE}
# Line 233 | Line 288 | end;
288  
289   procedure TFBLibrary.FreeFBLibrary;
290   begin
291 +  (FFirebirdAPI as TFBClientAPI).FBShutdown;
292    if FIBLibrary <> NilHandle then
293      FreeLibrary(FIBLibrary);
294    FIBLibrary := NilHandle;
295 +  FFBLibraryName := '';
296   end;
297  
298   function TFBLibrary.GetLibraryName: string;
# Line 287 | Line 344 | end;
344  
345   destructor TFBLibrary.Destroy;
346   begin
290  FFirebirdAPI := nil;
347    FreeFBLibrary;
348 +  FFirebirdAPI := nil;
349    inherited Destroy;
350   end;
351  
# Line 299 | Line 356 | begin
356    if aLibPathName <> '' then
357    begin
358      for i := 0 to Length(FLibraryList) - 1 do
359 <      if (FLibraryList[i] as TFBLibrary).FRequestedLibName = aLibPathName then
359 >    begin
360 >      if (FLibraryList[i] as TFBLibrary).SameLibrary(aLibPathName) then
361        begin
362          Result := FLibraryList[i];
363          Exit;
364        end;
365 +    end;
366      Result := Create(aLibPathName);
367    end;
368  
# Line 317 | Line 376 | begin
376    SetLength(FLibraryList,0);
377   end;
378  
379 + function TFBLibrary.SameLibrary(aLibName: string): boolean;
380 + begin
381 +  Result := FRequestedLibName = aLibName;
382 + end;
383 +
384   function TFBLibrary.GetHandle: TLibHandle;
385   begin
386    Result := FIBLibrary;
# Line 328 | Line 392 | constructor TFBClientAPI.Create(aFBLibra
392   begin
393    inherited Create;
394    FFBLibrary := aFBLibrary;
395 +  GetTZDataSettings;
396   end;
397  
398   procedure TFBClientAPI.IBAlloc(var P; OldSize, NewSize: Integer);
# Line 343 | Line 408 | begin
408    raise EIBInterBaseError.Create(GetStatus);
409   end;
410  
411 < {Under Unixes, if using an embedded server then set up local TMP and LOCK Directories}
347 <
348 < procedure TFBClientAPI.EncodeInteger(aValue: integer; len: integer; buffer: PByte);
411 > procedure TFBClientAPI.EncodeInteger(aValue: int64; len: integer; buffer: PByte);
412   begin
413    while len > 0 do
414    begin
# Line 356 | Line 419 | begin
419    end;
420   end;
421  
422 + (*
423 +  DecodeInteger is Translated from
424 +
425 + SINT64 API_ROUTINE isc_portable_integer(const UCHAR* ptr, SSHORT length)
426 + if (!ptr || length <= 0 || length > 8)
427 +        return 0;
428 +
429 + SINT64 value = 0;
430 + int shift = 0;
431 +
432 + while (--length > 0)
433 + {
434 +        value += ((SINT64) *ptr++) << shift;
435 +        shift += 8;
436 + }
437 +
438 + value += ((SINT64)(SCHAR) *ptr) << shift;
439 +
440 + return value;
441 + *)
442 +
443 + function TFBClientAPI.DecodeInteger(bufptr: PByte; len: short): int64;
444 + var shift: integer;
445 + begin
446 +  Result := 0;
447 +  if (BufPtr = nil) or (len <= 0) or (len > 8) then
448 +    Exit;
449 +
450 +  shift := 0;
451 +  dec(len);
452 +  while len > 0 do
453 +  begin
454 +    Result := Result + (int64(bufptr^) shl shift);
455 +    Inc(bufptr);
456 +    shift := shift + 8;
457 +    dec(len);
458 +  end;
459 +  Result := Result + (int64(bufptr^) shl shift);
460 + end;
461 +
462 + function TFBClientAPI.Int128ToStr(bufptr: PByte; scale: integer): AnsiString;
463 + begin
464 +  if not HasInt128Support then
465 +    IBError(ibxeNotSupported,[]);
466 + end;
467 +
468 + procedure TFBClientAPI.StrToInt128(scale: integer; aValue: AnsiString; bufptr: PByte);
469 + begin
470 +  if not HasInt128Support then
471 +    IBError(ibxeNotSupported,[]);
472 + end;
473 +
474 + procedure TFBClientAPI.SQLDecFloatEncode(aValue: tBCD; SQLType: cardinal;
475 +  bufptr: PByte);
476 + begin
477 +  if not HasDecFloatSupport then
478 +    IBError(ibxeNotSupported,[]);
479 + end;
480 +
481 + function TFBClientAPI.SQLDecFloatDecode(SQLType: cardinal; bufptr: PByte): tBCD;
482 + begin
483 +  if not HasDecFloatSupport then
484 +    IBError(ibxeNotSupported,[]);
485 + end;
486 +
487   function TFBClientAPI.IsLibraryLoaded: boolean;
488   begin
489    Result := FFBLibrary.IBLibrary <> NilHandle;
# Line 366 | Line 494 | begin
494    Result := FFBLibrary;
495   end;
496  
497 + function TFBClientAPI.FBTimeStampToDateTime(aDate, aTime: longint): TDateTime;
498 + begin
499 +  {aDate/aTime are in TTimestamp format but aTime is decimilliseconds}
500 +  aDate := aDate - DateDelta;
501 +  if aDate < 0 then
502 +    Result := trunc(aDate) - abs(frac(aTime / (MSecsPerDay*10)))
503 +  else
504 +    Result := trunc(aDate) + abs(frac(aTime / (MSecsPerDay*10)));
505 + end;
506 +
507 + {$IFDEF UNIX}
508 +
509 + procedure TFBClientAPI.GetTZDataSettings;
510 + var S: TStringList;
511 + begin
512 +  FLocalTimeOffset := GetLocalTimeOffset;
513 +  {$if declared(Gettzname)}
514 +  FLocalTimeZoneName := Gettzname(tzdaylight);
515 +  {$else}
516 +  FLocalTimeZoneName := tzname[tzdaylight];
517 +  {$ifend}
518 +  FIsDaylightSavingsTime := tzdaylight;
519 +  if FileExists(DefaultTimeZoneFile) then
520 +  begin
521 +    S := TStringList.Create;
522 +    try
523 +      S.LoadFromFile(DefaultTimeZoneFile);
524 +      if S.Count > 0 then
525 +        FTZDataTimeZoneID := S[0];
526 +    finally
527 +      S.Free;
528 +    end;
529 +  end;
530 + end;
531 + {$ENDIF}
532 +
533 + {$IFDEF WINDOWS}
534 + procedure TFBClientAPI.GetTZDataSettings;
535 + var TZInfo: TTimeZoneInformation;
536 + begin
537 +  FIsDaylightSavingsTime := false;
538 +  {is there any way of working out the default TZData DB time zone ID under Windows?}
539 +  case GetTimeZoneInformation(TZInfo) of
540 +    TIME_ZONE_ID_UNKNOWN:
541 +      begin
542 +        FLocalTimeZoneName := '';
543 +        FLocalTimeOffset := 0;
544 +      end;
545 +    TIME_ZONE_ID_STANDARD:
546 +      begin
547 +        FLocalTimeZoneName := strpas(PWideChar(@TZInfo.StandardName));
548 +        FLocalTimeOffset := TZInfo.Bias;
549 +      end;
550 +    TIME_ZONE_ID_DAYLIGHT:
551 +      begin
552 +        FLocalTimeZoneName := strpas(PWideChar(@TZInfo.DaylightName));
553 +        FLocalTimeOffset := TZInfo.DayLightBias;
554 +        FIsDaylightSavingsTime := true;
555 +      end;
556 +  end;
557 + end;
558 + {$ENDIF}
559 +
560   function TFBClientAPI.GetProcAddr(ProcName: PAnsiChar): Pointer;
561   begin
562 <  Result := GetProcAddress(FFBLibrary.IBLibrary, ProcName);
562 >  Result := nil;
563 >  if assigned(FFBLibrary) and (FFBLibrary.IBLibrary <> NilHandle) then
564 >    Result := GetProcAddress(FFBLibrary.IBLibrary, ProcName);
565    if not Assigned(Result) then
566      raise Exception.CreateFmt(SFirebirdAPIFuncNotFound,[ProcName]);
567   end;
568  
569 + function TFBClientAPI.HasDecFloatSupport: boolean;
570 + begin
571 +  Result := GetClientMajor >= 4;
572 + end;
573 +
574 + function TFBClientAPI.HasInt128Support: boolean;
575 + begin
576 +  Result := false;
577 + end;
578 +
579 + function TFBClientAPI.HasLocalTZDB: boolean;
580 + begin
581 +  Result := false;
582 + end;
583 +
584 + function TFBClientAPI.HasExtendedTZSupport: boolean;
585 + begin
586 +  Result := false;
587 + end;
588 +
589 + function TFBClientAPI.HasTimeZoneSupport: boolean;
590 + begin
591 +  Result := false;
592 + end;
593 +
594 + function TFBClientAPI.GetImplementationVersion: AnsiString;
595 + begin
596 +  Result := Format('%d.%d',[GetClientMajor,GetClientMinor]);
597 + end;
598 +
599   function TFBClientAPI.LoadInterface: boolean;
600   begin
601    isc_sqlcode := GetProcAddr('isc_sqlcode'); {do not localize}
602    isc_sql_interprete := GetProcAddr('isc_sql_interprete'); {do not localize}
603 <  isc_interprete := GetProcAddr('isc_interprete'); {do not localize}
604 <  isc_event_counts := GetProcAddr('isc_event_counts'); {do not localize}
605 <  isc_event_block := GetProcAddr('isc_event_block'); {do not localize}
606 <  isc_free := GetProcAddr('isc_free'); {do not localize}
607 <  Result := assigned(isc_free);
603 >  fb_shutdown := GetProcAddr('fb_shutdown'); {do not localize}
604 >  Result := true; {don't case if these fail to load}
605 > end;
606 >
607 > procedure TFBClientAPI.FBShutdown;
608 > begin
609 >  if assigned(fb_shutdown) then
610 >    fb_shutdown(0,fb_shutrsn_exit_called);
611   end;
612  
613   { TFBStatus }
614  
615 < constructor TFBStatus.Create(aOwner: TFBClientAPI);
615 > function TFBStatus.GetSQLMessage: Ansistring;
616 > var local_buffer: array[0..IBHugeLocalBufferLength - 1] of AnsiChar;
617 > begin
618 >  Result := '';
619 >  if (FOwner <> nil) and assigned(FOwner.isc_sql_interprete) then
620 >  begin
621 >     FOwner.isc_sql_interprete(Getsqlcode, local_buffer, sizeof(local_buffer));
622 >     Result := strpas(local_buffer);
623 >  end;
624 > end;
625 >
626 > constructor TFBStatus.Create(aOwner: TFBClientAPI; prefix: AnsiString);
627   begin
628    inherited Create;
629    FOwner := aOwner;
630 <  FIBDataBaseErrorMessages := [ShowSQLMessage, ShowIBMessage];
630 >  FPrefix := prefix;
631 >  FIBDataBaseErrorMessages := [ShowSQLCode, ShowSQLMessage, ShowIBMessage];
632   end;
633  
634 < function TFBStatus.GetIBErrorCode: Long;
634 > function TFBStatus.GetIBErrorCode: TStatusCode;
635   begin
636    Result := StatusVector^[1];
637   end;
638  
639 < function TFBStatus.Getsqlcode: Long;
639 > function TFBStatus.Getsqlcode: TStatusCode;
640   begin
641 <  with FOwner do
642 <    Result := isc_sqlcode(PISC_STATUS(StatusVector));
641 >  if (FOwner <> nil) and assigned(FOwner.isc_sqlcode) then
642 >    Result := FOwner.isc_sqlcode(PISC_STATUS(StatusVector))
643 >  else
644 >    Result := -999; {generic SQL Code}
645   end;
646  
647   function TFBStatus.GetMessage: AnsiString;
648 < var local_buffer: array[0..IBHugeLocalBufferLength - 1] of AnsiChar;
409 <    IBDataBaseErrorMessages: TIBDataBaseErrorMessages;
410 <    sqlcode: Long;
411 <    psb: PStatusVector;
648 > var IBDataBaseErrorMessages: TIBDataBaseErrorMessages;
649   begin
650 <  Result := '';
650 >  Result := FPrefix;
651    IBDataBaseErrorMessages := FIBDataBaseErrorMessages;
415  sqlcode := Getsqlcode;
652    if (ShowSQLCode in IBDataBaseErrorMessages) then
653 <    Result := Result + 'SQLCODE: ' + IntToStr(sqlcode); {do not localize}
653 >    Result := Result + 'SQLCODE: ' + IntToStr(Getsqlcode); {do not localize}
654  
655 <  Exclude(IBDataBaseErrorMessages, ShowSQLMessage);
420 <  if (ShowSQLMessage in IBDataBaseErrorMessages) then
655 >  if [ShowSQLMessage, ShowIBMessage]*IBDataBaseErrorMessages <> [] then
656    begin
422    with FOwner do
423      isc_sql_interprete(sqlcode, local_buffer, IBLocalBufferLength);
657      if (ShowSQLCode in FIBDataBaseErrorMessages) then
658 <      Result := Result + CRLF;
659 <    Result := Result + strpas(local_buffer);
658 >      Result := Result + LineEnding;
659 >    Result := Result + 'Engine Code: ' + IntToStr(GetIBErrorCode) + ' ';
660    end;
661  
662 +  if (ShowSQLMessage in IBDataBaseErrorMessages) then
663 +    Result := Result + GetSQLMessage;
664 +
665    if (ShowIBMessage in IBDataBaseErrorMessages) then
666    begin
667 <    if (ShowSQLCode in IBDataBaseErrorMessages) or
668 <       (ShowSQLMessage in IBDataBaseErrorMessages) then
669 <      Result := Result + CRLF;
434 <    psb := StatusVector;
435 <    with FOwner do
436 <    while (isc_interprete(@local_buffer, @psb) > 0) do
437 <    begin
438 <      if (Result <> '') and (Result[Length(Result)] <> LF) then
439 <        Result := Result + CRLF;
440 <      Result := Result + strpas(local_buffer);
441 <    end;
667 >    if ShowSQLMessage in IBDataBaseErrorMessages then
668 >      Result := Result + LineEnding;
669 >    Result := Result + GetIBMessage;
670    end;
671    if (Result <> '') and (Result[Length(Result)] = '.') then
672      Delete(Result, Length(Result), 1);

Comparing:
ibx/trunk/fbintf/client/FBClientAPI.pas (property svn:eol-style), Revision 263 by tony, Thu Dec 6 15:55:01 2018 UTC vs.
ibx/branches/udr/client/FBClientAPI.pas (property svn:eol-style), Revision 379 by tony, Mon Jan 10 10:08:03 2022 UTC

# Line 0 | Line 1
1 + native

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines