The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 4,262 other subscribers

Delphi, IBX and the Turkish I problem

Posted by jpluimers on 2018/06/13

Last year, it took a while to reproduce and it is likely not fixed anytime soon: if you use Delphi and IBX (InterBase Express) to connect to either InterBase or FireBird database, then in the Turkish locale do not use lowercase SQL in your Delphi code as it will break on the

I’ve decided to put this in a separate post than the one I discovered the issue in as that one focused on the Unicode and language background of the various i/I/ characters and this post on the Delphi part.

So for the non-Delphi part, readĀ Source: Field ā€œidā€ not found and the The Turkish-Ä°/I/i/ı and case conversion ā€“ Update on the dasBlog Turkish-I bug and a reminder to me on Globalization ā€“ Scott Hanselman.

More recently, I learned that the same problem also happens in the Azeri language or Azerbaijani language – Wikipedia via [WayBack] SQL Instance Collation – Language Neutral Required:

uppercase / lowercase mappings (though this only impacts 2 characters ā€” dotted and dotless “i”/”I” ā€” and for only 2 cultures ā€” Azeri and Turkish)

In general, this problem is called [WayBack] Case Folding and many environments do not have good and ready to use solutions for this.

Basically when working with case-insensitive language identifiers, you should always use culture invariant text comparison operations. In most languages, people use either lowercase or uppercase converted operations which for Delphi >= come down to using :

The reason is simple:

  • without any parameter, ToLower and ToUpper will use [Archive.is]Ā SysLocale.DefaultLCID.
  • without any parameter, LowerCase and UpperCase will implicitly behave as if called withĀ TLocaleOptions.loInvariantLocale, but lots of people forgetĀ they do.

The functionsĀ ToLowerInvariantĀ andĀ Ā ToUpperInvariantĀ  were added in Delphi XE3, but ToLower(LocaleID) and ToUpper(LocaleID) in Delphi XE4.

Instead of doing uppercase and lowercase comparisons you could also use the [Archive.is]Ā System.SysUtils.CompareTextĀ function.

IBX however uses case conversion, and by now you will probably guessed it: IBX got it all wrong. One reproduction is atĀ https://gist.github.com/jpluimers/643b382944ff991d07ec96abbf85548c and a thread with background is at [WayBack] What’s the Delphi equivalent of doing UpperCase with an InvariantCulture in Unicode Delphi versions? (XE and up) – Jeroen Wiert Pluimers – Google+

IBX isn’t alone: just search for other uppercase issues like Turkish iĀ to see tons of other issues.

For IBX, I did the replacements in the diff below to fix it in Delphi XE8. I only replaced where identifiers were compared, not were actual database content was compared.

Unluckily, in the past IBX sources were hosted on CodeCentral atĀ http://cc.embarcadero.com/Author/102Ā but no new bundles have been released since 2012.

Of all the locations I think IBX should not have used any case conversion here:

IBX.IBDatabaseINI.pasfunction LocalServerPath(sFile: string): string

This function uses LowerCase()Ā but I think an NTFS specific comparison should have been used, but I’ve not investigated into a solution for that yet.

–jeroen

PS:Ā Potentially related is [WayBack/Archive.is] FireDAC problem with MSSQL: We have an issue using FireDAC on Microsoft SQL Server 2016, in that we have a case sensitive ( not the insensitive default) … – Tony Danby – Google+

PS:Ā To make it easier finding back the archived URLs of the links above, here is a list:

Reproduction: IBDataSetTestsUnit


unit IBDataSetTestsUnit;
interface
uses
DUnitX.TestFramework;
type
[TestFixture]
TIBDataSetTests = class(TObject)
strict private
procedure Employee_Gdbs_Succeeds(const aLocaleID, aSQLDialect: Integer);
public
[Test]
procedure LocaleId_English_US_Employee_Gdbs_Dialect1_Succeeds;
[Test]
procedure LocaleId_English_US_Employee_Gdbs_Dialect3_Succeeds;
[Test]
procedure LocaleId_Turkish_Turkey_Employee_Gdbs_Dialect1_Succeeds;
[Test]
procedure LocaleId_Turkish_Turkey_Employee_Gdbs_Dialect3_Succeeds;
end;
implementation
uses
IBX.IB,
IBX.IBBatchUpdate,
IBX.IBBlob,
IBX.IBConnectionBroker,
IBX.IBCSMonitor,
// IBX.IBCustomDataSet,
// IBX.IBDatabase,
IBX.IBDatabaseInfo,
IBX.IBDatabaseINI,
IBX.IBErrorCodes,
IBX.IBEvents,
IBX.IBExternals,
IBX.IBExtract,
IBX.IBFilterDialog,
IBX.IBFilterSummary,
IBX.IBHeader,
IBX.IBInstall,
IBX.IBInstallHeader,
IBX.IBIntf,
IBX.IBQuery,
IBX.IBScript,
IBX.IBServices,
IBX.IBSQL,
IBX.IBSQLMonitor,
IBX.IBStoredProc,
IBX.IBSubscription,
IBX.IBTable,
IBX.IBUpdateSQL,
IBX.IBUtils,
IBX.IBVisualConst,
IBX.IBXConst,
IBX.IBXMLHeader,
System.SysUtils,
IBX.IBCustomDataSet,
IBX.IBDatabase,
Winapi.Windows;
procedure TIBDataSetTests.Employee_Gdbs_Succeeds(const aLocaleID, aSQLDialect: Integer);
var
lDatabase: TIBDatabase;
lDataSet: TIBDataSet;
lOriginalLocaleID: Integer;
lTransaction: TIBTransaction;
begin
lOriginalLocaleID := GetThreadLocale;
try
SetThreadLocale(aLocaleID);
GetFormatSettings();
lDatabase := TIBDatabase.Create(nil);
try
lDatabase.ServerType := 'IBServer'; // force to use gds32.dll
lDatabase.DatabaseName := '127.0.0.1:C:\ProgramData\Embarcadero\InterBase\gds_db\examples\database\employee.gdb';
// lDatabase.ServerType := 'IBEmbedded'; // force to use ibtogo.dll
// lDatabase.DatabaseName := 'C:\ProgramData\Embarcadero\InterBase\gds_db\examples\database\employee.gdb';
lDatabase.Params.Clear;
lDatabase.Params.Add('user_name=SYSDBA');
lDatabase.Params.Add('password=masterkey');
lDatabase.LoginPrompt := False;
lDatabase.IdleTimer := 0;
lDatabase.SQLDialect := aSQLDialect;
lDatabase.TraceFlags := [];
lTransaction := TIBTransaction.Create(nil);
try
lTransaction.DefaultDatabase := lDatabase;
lDatabase.Connected := True;
lDataSet := TIBDataSet.Create(nil);
try
lDataSet.Database := lDatabase;
lDataSet.Transaction := lTransaction;
lDataSet.SelectSQL.Text := 'select rdb$relation_id from rdb$database where rdb$relation_id <> :ID';
lDataSet.ParamByName('id').AsInteger := -aSQLDialect;
// when using the tr-TR locale (Turkish in Turkey),
// the above `ParamByName` throws `EIBClientError with message 'Field "id" not found'`
// because it uses `ToUpper` inside `FormatIdentifierValue instead of `ToUpperInvariant`
// so it cannot match 'id' against ':ID' in `lDataSet.SelectSQL.Text`
// Similar issues could potentially happen in other non-invariant locales as well.
finally
lDataSet.Free();
end;
finally
lTransaction.Free();
end;
finally
lDatabase.Free();
end;
finally
SetThreadLocale(lOriginalLocaleID);
GetFormatSettings();
end;
Assert.Pass();
end;
procedure TIBDataSetTests.LocaleId_English_US_Employee_Gdbs_Dialect1_Succeeds;
begin
Employee_Gdbs_Succeeds($0409, 1);
end;
procedure TIBDataSetTests.LocaleId_English_US_Employee_Gdbs_Dialect3_Succeeds;
begin
Employee_Gdbs_Succeeds($0409, 3);
end;
procedure TIBDataSetTests.LocaleId_Turkish_Turkey_Employee_Gdbs_Dialect1_Succeeds;
begin
Employee_Gdbs_Succeeds($041F, 1);
end;
procedure TIBDataSetTests.LocaleId_Turkish_Turkey_Employee_Gdbs_Dialect3_Succeeds;
begin
Employee_Gdbs_Succeeds($041F, 3);
end;
initialization
TDUnitX.RegisterTestFixture(TIBDataSetTests);
end.

Diff of the Delphi XE8 IBX sources against the fixed sources


diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBCustomDataSet.pas" IBX/IBX.IBCustomDataSet.pas
3943c3943
< Old := PName.ToUpper.StartsWith('OLD_'); {do not localize}
> Old := PName.ToUpperInvariant.StartsWith('OLD_'); {do not localize}
diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBDatabase.pas" IBX/IBX.IBDatabase.pas
723c723
< not Params.Text.ToUpper.Contains('FILE ') then {do not localize}
> not Params.Text.ToUpperInvariant.Contains('FILE ') then {do not localize}
734c734
< if TempDBParams[i].ToUpper.Contains('FILE ') then {do not localize}
> if TempDBParams[i].ToUpperInvariant.Contains('FILE ') then {do not localize}
754c754
< lc_ctype := Params.Values['lc_ctype'].ToUpper; {do not localize}
> lc_ctype := Params.Values['lc_ctype'].ToUpperInvariant; {do not localize}
756c756
< lc_ctype := Params.Values['isc_dpb_lc_ctype'].ToUpper; {do not localize}
> lc_ctype := Params.Values['isc_dpb_lc_ctype'].ToUpperInvariant; {do not localize}
760c760
< b := TEncoding.Convert(TEncoding.Default, TEncoding.ANSI, BytesOf(Format(sDefaultCharset, [lc_ctype.ToUpper])));
> b := TEncoding.Convert(TEncoding.Default, TEncoding.ANSI, BytesOf(Format(sDefaultCharset, [lc_ctype.ToUpperInvariant])));
774c774
< if Params.Text.ToUpper.Contains('FILE ') then {do not localize}
> if Params.Text.ToUpperInvariant.Contains('FILE ') then {do not localize}
778c778
< if Params[i].ToUpper.Contains('FILE ') then {do not localize }
> if Params[i].ToUpperInvariant.Contains('FILE ') then {do not localize }
930c930
< pos_of_str := Params[i].ToLower.IndexOf(st);
> pos_of_str := Params[i].ToLowerInvariant.IndexOf(st);
1107c1107
< if Params.Names[i].Trim.ToLower.StartsWith('password') then {do not localize}
> if Params.Names[i].Trim.ToLowerInvariant.StartsWith('password') then {do not localize}
2566c2566
< ParamName := sl[i].ToLower;
> ParamName := sl[i].ToLowerInvariant;
2571c2571
< ParamName := sl.Names[i].ToLower;
> ParamName := sl.Names[i].ToLowerInvariant;
2744c2744
< ParamName := sl[i].ToLower
> ParamName := sl[i].ToLowerInvariant
2747c2747
< ParamName := sl.Names[i].ToLower;
> ParamName := sl.Names[i].ToLowerInvariant;
diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBDatabaseINI.pas" IBX/IBX.IBDatabaseINI.pas
127c127
< if FDatabase.Params.Names[i].ToLower.StartsWith(Name.ToLower) then
> if FDatabase.Params.Names[i].ToLowerInvariant.StartsWith(Name.ToLowerInvariant) then
166c166
< if FDatabase.Params.Names[i].ToLower.StartsWith(Name.ToLower) then
> if FDatabase.Params.Names[i].ToLowerInvariant.StartsWith(Name.ToLowerInvariant) then
183c183
< if FDatabase.Params.Names[i].ToLower.StartsWith(Name.ToLower) then
> if FDatabase.Params.Names[i].ToLowerInvariant.StartsWith(Name.ToLowerInvariant) then
diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBExtract.pas" IBX/IBX.IBExtract.pas
449c449
< i := default.ToUpper.IndexOf('DEFAULT ') + 8;
> i := default.ToUpperInvariant.IndexOf('DEFAULT ') + 8;
1696c1696
< if qryDomains.FieldByName('RDB$VALIDATION_SOURCE').AsTrimString.ToUpper.StartsWith('CHECK') then {do not localize}
> if qryDomains.FieldByName('RDB$VALIDATION_SOURCE').AsTrimString.ToUpperInvariant.StartsWith('CHECK') then {do not localize}
2520c2520
< if 'check'.StartsWith(ToValidate.ToLower) then {do not localize}
> if 'check'.StartsWith(ToValidate.ToLowerInvariant) then {do not localize}
diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBFilterDialog.pas" IBX/IBX.IBFilterDialog.pas
171c171
< s := ' ' + AnsiUpperCase(AString) + ' '; {do not localize}
> s := ' ' + UpperCase(AString, TLocaleOptions.loInvariantLocale) + ' '; {do not localize}
175c175
< p := Pos(' ' + AnsiUpperCase(AWord) + ' ', s); {do not localize}
> p := Pos(' ' + UpperCase(AWord, TLocaleOptions.loInvariantLocale) + ' ', s); {do not localize}
896c896
< if AnsiUpperCase(FilterValue) = 'NULL' then {do not localize}
> if UpperCase(FilterValue, TLocaleOptions.loInvariantLocale) = 'NULL' then {do not localize}
976c976
< if AnsiUpperCase(FilterValue) = 'NULL' then {do not localize}
> if UpperCase(FilterValue, TLocaleOptions.loInvariantLocale) = 'NULL' then {do not localize}
diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBIntf.pas" IBX/IBX.IBIntf.pas
1700c1700
< if aLibrary.ToLower = IBClientInterface.ToArray[i].Key.ToLower then
> if aLibrary.ToLowerInvariant = IBClientInterface.ToArray[i].Key.ToLowerInvariant then
diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBScript.pas" IBX/IBX.IBScript.pas
345c345
< Token := UpperCase(FTokens[0]);
> Token := UpperCase(FTokens[0], TLocaleOptions.loInvariantLocale);
413c413
< Token1 := UpperCase(FTokens[1]);
> Token1 := UpperCase(FTokens[1], TLocaleOptions.loInvariantLocale);
454c454
< (FTokens.Count > 2) and (UpperCase(FTokens[2]) = 'INDEX')) then {do not localize}
> (FTokens.Count > 2) and (UpperCase(FTokens[2], TLocaleOptions.loInvariantLocale) = 'INDEX')) then {do not localize}
467c467
< (UpperCase(FTokens[2]) = 'DIALECT') then {do not localize}
> (UpperCase(FTokens[2], TLocaleOptions.loInvariantLocale) = 'DIALECT') then {do not localize}
589c589
< (UpperCase(FTokens[FTokens.Count – 2]) = 'END') and {do not localize}
> (UpperCase(FTokens[FTokens.Count – 2], TLocaleOptions.loInvariantLocale) = 'END') and {do not localize}
593,596c593,596
< (UpperCase(FTokens[0]) = 'ALTER') and {do not localize}
< (UpperCase(FTokens[1]) = 'TRIGGER') and {do not localize}
< ((UpperCase(FTokens[3]) = 'ACTIVE') or {do not localize}
< (UpperCase(FTokens[3]) = 'INACTIVE')) and {do not localize}
> (UpperCase(FTokens[0], TLocaleOptions.loInvariantLocale) = 'ALTER') and {do not localize}
> (UpperCase(FTokens[1], TLocaleOptions.loInvariantLocale) = 'TRIGGER') and {do not localize}
> ((UpperCase(FTokens[3], TLocaleOptions.loInvariantLocale) = 'ACTIVE') or {do not localize}
> (UpperCase(FTokens[3], TLocaleOptions.loInvariantLocale) = 'INACTIVE')) and {do not localize}
607c607
< NextWord := NextWord.ToUpper.Trim;
> NextWord := NextWord.ToUpperInvariant.Trim;
609c609
< FTokens.Add(NextWord.ToUpper);
> FTokens.Add(NextWord.ToUpperInvariant);
665,668c665,668
< ((UpperCase(FTokens[0]) = 'CREATE') or {do not localize}
< (UpperCase(FTokens[0]) = 'ALTER')) and {do not localize}
< ((UpperCase(NextWord) = 'PROCEDURE') or {do not localize}
< (UpperCase(NextWord) = 'TRIGGER')) and {do not localize}
> ((UpperCase(FTokens[0], TLocaleOptions.loInvariantLocale) = 'CREATE') or {do not localize}
> (UpperCase(FTokens[0], TLocaleOptions.loInvariantLocale) = 'ALTER')) and {do not localize}
> ((UpperCase(NextWord, TLocaleOptions.loInvariantLocale) = 'PROCEDURE') or {do not localize}
> (UpperCase(NextWord, TLocaleOptions.loInvariantLocale) = 'TRIGGER')) and {do not localize}
671c671
< if InSpecial and (NextWord.ToUpper = 'END;') then {do not localize}
> if InSpecial and (NextWord.ToUpperInvariant = 'END;') then {do not localize}
677,680c677,680
< (UpperCase(FTokens[0]) = 'ALTER') and {do not localize}
< (UpperCase(FTokens[1]) = 'TRIGGER') and {do not localize}
< ((UpperCase(FTokens[3]) = 'ACTIVE;') or {do not localize}
< ((UpperCase(FTokens[3]) = 'INACTIVE;'))) then {do not localize}
> (UpperCase(FTokens[0], TLocaleOptions.loInvariantLocale) = 'ALTER') and {do not localize}
> (UpperCase(FTokens[1], TLocaleOptions.loInvariantLocale) = 'TRIGGER') and {do not localize}
> ((UpperCase(FTokens[3], TLocaleOptions.loInvariantLocale) = 'ACTIVE;') or {do not localize}
> ((UpperCase(FTokens[3], TLocaleOptions.loInvariantLocale) = 'INACTIVE;'))) then {do not localize}
682c682
< if UpperCase(NextWord) = 'ACTIVE;' then {do not localize}
> if UpperCase(NextWord, TLocaleOptions.loInvariantLocale) = 'ACTIVE;' then {do not localize}
795,803c795,803
< Result := not ((FSQLParser.CurrentTokens[i].ToUpper = 'NO_RESERVE') or {do not localize }
< (FSQLParser.CurrentTokens[i].ToUpper = 'SET_PAGE_BUFFERS') or {do not localize }
< (FSQLParser.CurrentTokens[i].ToUpper = 'PREALLOCATE') or {do not localize }
< (FSQLParser.CurrentTokens[i].ToUpper = 'FORCE_WRITE') or {do not localize }
< (FSQLParser.CurrentTokens[i].ToUpper = 'SQL_DIALECT') or {do not localize }
< (FSQLParser.CurrentTokens[i].ToUpper = 'SET_GROUP_COMMIT') or {do not localize }
< (FSQLParser.CurrentTokens[i].ToUpper = 'SWEEP_INTERVAL') or {do not localize }
< (FSQLParser.CurrentTokens[i].ToUpper = 'TRANSACTION') or {do not localize }
< (FSQLParser.CurrentTokens[i].ToUpper = 'ODS_VERSION_MAJOR')); {do not localize }
> Result := not ((FSQLParser.CurrentTokens[i].ToUpperInvariant = 'NO_RESERVE') or {do not localize }
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'SET_PAGE_BUFFERS') or {do not localize }
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'PREALLOCATE') or {do not localize }
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'FORCE_WRITE') or {do not localize }
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'SQL_DIALECT') or {do not localize }
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'SET_GROUP_COMMIT') or {do not localize }
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'SWEEP_INTERVAL') or {do not localize }
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'TRANSACTION') or {do not localize }
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'ODS_VERSION_MAJOR')); {do not localize }
829c829
< (FSQLParser.CurrentTokens[i].ToUpper = 'USER') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'USER') then {do not localize}
836c836
< (FSQLParser.CurrentTokens[i].ToUpper = 'PASSWORD') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'PASSWORD') then {do not localize}
842c842
< if FSQLParser.CurrentTokens[i].ToUpper.Contains('PAGE_SIZE') then {do not localize}
> if FSQLParser.CurrentTokens[i].ToUpperInvariant.Contains('PAGE_SIZE') then {do not localize}
860,862c860,862
< (FSQLParser.CurrentTokens[i].ToUpper = 'WITH') and {do not localize}
< (FSQLParser.CurrentTokens[i+1].ToUpper = 'ADMIN') and {do not localize}
< (FSQLParser.CurrentTokens[i+2].ToUpper = 'OPTION') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'WITH') and {do not localize}
> (FSQLParser.CurrentTokens[i+1].ToUpperInvariant = 'ADMIN') and {do not localize}
> (FSQLParser.CurrentTokens[i+2].ToUpperInvariant = 'OPTION') then {do not localize}
869,871c869,871
< (FSQLParser.CurrentTokens[i].ToUpper = 'DEFAULT') and {do not localize}
< (FSQLParser.CurrentTokens[i+1].ToUpper = 'CHARACTER') and {do not localize}
< (FSQLParser.CurrentTokens[i+2].ToUpper = 'SET') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'DEFAULT') and {do not localize}
> (FSQLParser.CurrentTokens[i+1].ToUpperInvariant = 'CHARACTER') and {do not localize}
> (FSQLParser.CurrentTokens[i+2].ToUpperInvariant = 'SET') then {do not localize}
877c877
< if FSQLParser.CurrentTokens[i].ToUpper = 'NO_RESERVE' then {do not localize}
> if FSQLParser.CurrentTokens[i].ToUpperInvariant = 'NO_RESERVE' then {do not localize}
883c883
< if FSQLParser.CurrentTokens[i].ToUpper = 'SET_GROUP_COMMIT' then {do not localize}
> if FSQLParser.CurrentTokens[i].ToUpperInvariant = 'SET_GROUP_COMMIT' then {do not localize}
890c890
< (FSQLParser.CurrentTokens[i].ToUpper = 'SET_PAGE_BUFFERS') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'SET_PAGE_BUFFERS') then {do not localize}
897c897
< (FSQLParser.CurrentTokens[i].ToUpper = 'PREALLOCATE') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'PREALLOCATE') then {do not localize}
904c904
< (FSQLParser.CurrentTokens[i].ToUpper = 'ODS_VERSION_MAJOR') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'ODS_VERSION_MAJOR') then {do not localize}
911c911
< (FSQLParser.CurrentTokens[i].ToUpper = 'SQL_DIALECT') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'SQL_DIALECT') then {do not localize}
918c918
< (FSQLParser.CurrentTokens[i].ToUpper = 'SWEEP_INTERVAL') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'SWEEP_INTERVAL') then {do not localize}
925c925
< (FSQLParser.CurrentTokens[i].ToUpper = 'TRANSACTION') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'TRANSACTION') then {do not localize}
932c932
< (FSQLParser.CurrentTokens[i].ToUpper = 'FORCE_WRITE') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'FORCE_WRITE') then {do not localize}
934c934
< if FSQLParser.CurrentTokens[i+1].ToUpper = 'ASYNC' then {do not localize}
> if FSQLParser.CurrentTokens[i+1].ToUpperInvariant = 'ASYNC' then {do not localize}
936c936
< if FSQLParser.CurrentTokens[i+1].ToUpper = 'SYNC' then {do not localize}
> if FSQLParser.CurrentTokens[i+1].ToUpperInvariant = 'SYNC' then {do not localize}
938c938
< if FSQLParser.CurrentTokens[i+1].ToUpper = 'DIRECT' then {do not localize}
> if FSQLParser.CurrentTokens[i+1].ToUpperInvariant = 'DIRECT' then {do not localize}
944c944
< (FSQLParser.CurrentTokens[i].ToUpper = 'FILE') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'FILE') then {do not localize}
949c949
< (FSQLParser.CurrentTokens[i].ToUpper.Contains('LENGTH')) then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant.Contains('LENGTH')) then {do not localize}
966c966
< if FSQLParser.CurrentTokens[i].ToUpper = 'STARTING' then {do not localize}
> if FSQLParser.CurrentTokens[i].ToUpperInvariant = 'STARTING' then {do not localize}
971c971
< (FSQLParser.CurrentTokens[i].ToUpper = 'AT') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'AT') then {do not localize}
977c977
< (FSQLParser.CurrentTokens[i].ToUpper = 'PAGE') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'PAGE') then {do not localize}
989c989
< (FSQLParser.CurrentTokens[i].ToUpper = 'LENGTH') then {do not localize}
> (FSQLParser.CurrentTokens[i].ToUpperInvariant = 'LENGTH') then {do not localize}
1240,1241c1240,1241
< (UpperCase(FSQLParser.CurrentTokens[1]) = 'TO') and {do not localize}
< (UpperCase(FSQLParser.CurrentTokens[2]) = 'SAVEPOINT')) then {do not localize}
> (UpperCase(FSQLParser.CurrentTokens[1], TLocaleOptions.loInvariantLocale) = 'TO') and {do not localize}
> (UpperCase(FSQLParser.CurrentTokens[2], TLocaleOptions.loInvariantLocale) = 'SAVEPOINT')) then {do not localize}
1245,1246c1245,1246
< (UpperCase(FSQLParser.CurrentTokens[2]) = 'TO') and {do not localize}
< (UpperCase(FSQLParser.CurrentTokens[3]) = 'SAVEPOINT')) then {do not localize}
> (UpperCase(FSQLParser.CurrentTokens[2], TLocaleOptions.loInvariantLocale) = 'TO') and {do not localize}
> (UpperCase(FSQLParser.CurrentTokens[3], TLocaleOptions.loInvariantLocale) = 'SAVEPOINT')) then {do not localize}
diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBServices.pas" IBX/IBX.IBServices.pas
1047c1047
< pos_of_str := Params[i].ToLower.IndexOf(st);
> pos_of_str := Params[i].ToLowerInvariant.IndexOf(st);
1150c1150
< param_name := sl.Names[i].ToLower;
> param_name := sl.Names[i].ToLowerInvariant;
2192c2192
< sl.Add(UpperCase(S));
> sl.Add(UpperCase(S, TLocaleOptions.loInvariantLocale));
diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBTable.pas" IBX/IBX.IBTable.pas
585c585
< if Name.Trim.ToUpper.Contains('RDB$PRIMARY') then {do not localize} {mbcs ok}
> if Name.Trim.ToUpperInvariant.Contains('RDB$PRIMARY') then {do not localize} {mbcs ok}
diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBUpdateSQL.pas" IBX/IBX.IBUpdateSQL.pas
173c173
< Old := PName.ToUpper.StartsWith('OLD_'); {do not localize}
> Old := PName.ToUpperInvariant.StartsWith('OLD_'); {do not localize}
diff "C:\\Program Files (x86)\\Embarcadero\\Studio\\16.0\\source\\IBX/IBX.IBUtils.pas" IBX/IBX.IBUtils.pas
125c125
< Value := AnsiUpperCase(Value)
> Value := UpperCase(Value, TLocaleOptions.loInvariantLocale)
130c130
< Value := AnsiUpperCase(Value);
> Value := UpperCase(Value, TLocaleOptions.loInvariantLocale);
138c138
< Value := Value.ToUpper
> Value := Value.ToUpperInvariant
148c148
< Value := Value.ToUpper;
> Value := Value.ToUpperInvariant;
157c157
< Value := Value.ToUpper
> Value := Value.ToUpperInvariant
167c167
< Value := Value.ToUpper;
> Value := Value.ToUpperInvariant;
175c175
< Value := Value.Trim.ToUpper
> Value := Value.Trim.ToUpperInvariant
198c198
< if Result.ToLower.Contains(SWhere) then
> if Result.ToLowerInvariant.Contains(SWhere) then
378c378
< idx1 := DatabaseName.ToLower.IndexOf('?ssl=true'); {do not localize}
> idx1 := DatabaseName.ToLowerInvariant.IndexOf('?ssl=true'); {do not localize}
423c423
< idx1 := SSLSection.ToLower.IndexOf(SPF);
> idx1 := SSLSection.ToLowerInvariant.IndexOf(SPF);
427c427
< idx1 := SSLSection.ToLower.IndexOf(SPP);
> idx1 := SSLSection.ToLowerInvariant.IndexOf(SPP);
431c431
< idx1 := SSLSection.ToLower.IndexOf(CCF);
> idx1 := SSLSection.ToLowerInvariant.IndexOf(CCF);
435c435
< idx1 := SSLSection.ToLower.IndexOf(CPPF);
> idx1 := SSLSection.ToLowerInvariant.IndexOf(CPPF);
439c439
< idx1 := SSLSection.ToLower.IndexOf(CPP);
> idx1 := SSLSection.ToLowerInvariant.IndexOf(CPP);
Only in IBX: __history

Reproducible case


unit IBDataSetTestsUnit;
interface
uses
DUnitX.TestFramework;
type
[TestFixture]
TIBDataSetTests = class(TObject)
strict private
procedure Employee_Gdbs_Succeeds(const aLocaleID, aSQLDialect: Integer);
public
[Test]
procedure LocaleId_English_US_Employee_Gdbs_Dialect1_Succeeds;
[Test]
procedure LocaleId_English_US_Employee_Gdbs_Dialect3_Succeeds;
[Test]
procedure LocaleId_Turkish_Turkey_Employee_Gdbs_Dialect1_Succeeds;
[Test]
procedure LocaleId_Turkish_Turkey_Employee_Gdbs_Dialect3_Succeeds;
end;
implementation
uses
IBX.IB,
IBX.IBBatchUpdate,
IBX.IBBlob,
IBX.IBConnectionBroker,
IBX.IBCSMonitor,
// IBX.IBCustomDataSet,
// IBX.IBDatabase,
IBX.IBDatabaseInfo,
IBX.IBDatabaseINI,
IBX.IBErrorCodes,
IBX.IBEvents,
IBX.IBExternals,
IBX.IBExtract,
IBX.IBFilterDialog,
IBX.IBFilterSummary,
IBX.IBHeader,
IBX.IBInstall,
IBX.IBInstallHeader,
IBX.IBIntf,
IBX.IBQuery,
IBX.IBScript,
IBX.IBServices,
IBX.IBSQL,
IBX.IBSQLMonitor,
IBX.IBStoredProc,
IBX.IBSubscription,
IBX.IBTable,
IBX.IBUpdateSQL,
IBX.IBUtils,
IBX.IBVisualConst,
IBX.IBXConst,
IBX.IBXMLHeader,
System.SysUtils,
IBX.IBCustomDataSet,
IBX.IBDatabase,
Winapi.Windows;
procedure TIBDataSetTests.Employee_Gdbs_Succeeds(const aLocaleID, aSQLDialect: Integer);
var
lDatabase: TIBDatabase;
lDataSet: TIBDataSet;
lOriginalLocaleID: Integer;
lTransaction: TIBTransaction;
begin
lOriginalLocaleID := GetThreadLocale;
try
SetThreadLocale(aLocaleID);
GetFormatSettings();
lDatabase := TIBDatabase.Create(nil);
try
lDatabase.ServerType := 'IBServer'; // force to use gds32.dll
lDatabase.DatabaseName := '127.0.0.1:C:\ProgramData\Embarcadero\InterBase\gds_db\examples\database\employee.gdb';
// lDatabase.ServerType := 'IBEmbedded'; // force to use ibtogo.dll
// lDatabase.DatabaseName := 'C:\ProgramData\Embarcadero\InterBase\gds_db\examples\database\employee.gdb';
lDatabase.Params.Clear;
lDatabase.Params.Add('user_name=SYSDBA');
lDatabase.Params.Add('password=masterkey');
lDatabase.LoginPrompt := False;
lDatabase.IdleTimer := 0;
lDatabase.SQLDialect := aSQLDialect;
lDatabase.TraceFlags := [];
lTransaction := TIBTransaction.Create(nil);
try
lTransaction.DefaultDatabase := lDatabase;
lDatabase.Connected := True;
lDataSet := TIBDataSet.Create(nil);
try
lDataSet.Database := lDatabase;
lDataSet.Transaction := lTransaction;
lDataSet.SelectSQL.Text := 'select rdb$relation_id from rdb$database where rdb$relation_id <> :ID';
lDataSet.ParamByName('id').AsInteger := -aSQLDialect;
// when using the tr-TR locale (Turkish in Turkey),
// the above `ParamByName` throws `EIBClientError with message 'Field "id" not found'`
// because it uses `ToUpper` inside `FormatIdentifierValue instead of `ToUpperInvariant`
// so it cannot match 'id' against ':ID' in `lDataSet.SelectSQL.Text`
// Similar issues could potentially happen in other non-invariant locales as well.
finally
lDataSet.Free();
end;
finally
lTransaction.Free();
end;
finally
lDatabase.Free();
end;
finally
SetThreadLocale(lOriginalLocaleID);
GetFormatSettings();
end;
Assert.Pass();
end;
procedure TIBDataSetTests.LocaleId_English_US_Employee_Gdbs_Dialect1_Succeeds;
begin
Employee_Gdbs_Succeeds($0409, 1);
end;
procedure TIBDataSetTests.LocaleId_English_US_Employee_Gdbs_Dialect3_Succeeds;
begin
Employee_Gdbs_Succeeds($0409, 3);
end;
procedure TIBDataSetTests.LocaleId_Turkish_Turkey_Employee_Gdbs_Dialect1_Succeeds;
begin
Employee_Gdbs_Succeeds($041F, 1);
end;
procedure TIBDataSetTests.LocaleId_Turkish_Turkey_Employee_Gdbs_Dialect3_Succeeds;
begin
Employee_Gdbs_Succeeds($041F, 3);
end;
initialization
TDUnitX.RegisterTestFixture(TIBDataSetTests);
end.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.