UTF8, IBX and Firebird with Euro symbol - delphi

I am connecting to Firebird through IBX components in Delphi 10.2 Tokyo.
I'm trying to connect to the DB through the following code:
IBDatabase.Connected := False;
IBDatabase.Params.Clear;
IBDatabase.DatabaseName := FDBFileName;
IBDatabase.Params.Values['user_name'] := FDBUserName;
IBDatabase.Params.Values['password'] := FDBPassword;
IBDatabase.Params.Values['lc_ctype'] := 'UTF8';
IBDatabase.Connected := True;
By trapping the exception during connection, I can realize if the database does not exist, then I create it using the following code:
IBDatabase.Params.Clear;
IBDatabase.DatabaseName := FDBFileName;
IBDatabase.Params.Add('USER ''SYSDBA''');
IBDatabase.Params.Add('PASSWORD ''masterkey''');
IBDatabase.Params.Add('PAGE_SIZE 16384');
IBDatabase.Params.Add('DEFAULT CHARACTER SET UTF8');
IBDatabase.CreateDatabase;
The above operation creates the database and leaves the TIBDatabase component connected to the DB.
In this case, I am creating:
UTF8 string domains
Table with UTF8 field
and setting the Euro symbol (€) in it.
The app opens and the field is visible.
When I restart the app I constantly get an error:
No mapping for the Unicode character exists in the target multi-byte code page

I decided to write this post in order to share the solution I found after almost a week and sleepless nights.
The problem was generated by the creation of the database code.
Since parameters are different and from the usual connection parameters I put DEFAULT CHARACTER SET UTF8, believing that any DDL or statement I was going to operate in the database had the UTF8 character set.
This is not true, the database connection doesn't behave like the one with the lc_ctype parameter set.
To solve this I just had to close connection to the database, reset the parameters like I did the first time:
IBDatabase.Connected:= False;
IBDatabase.Params.Clear;
IBDatabase.DatabaseName:= FDBFileName;
IBDatabase.Params.Values['user_name']:= FDBUserName;
IBDatabase.Params.Values['password']:= FDBPassword;
IBDatabase.Params.Values['lc_ctype']:= 'UTF8';
IBDatabase.Connected:= True;
and do all the operations. This way it works properly.

Related

Parameter value from TUIBQuery gets truncated when read back for Database.Charset=csUTF8

I am working on an application built in Delphi 2010 that uses UIB to connect to a Firebird 2.5 database. The application has been running using the default character set for a long time, i.e. nobody gave character sets any special thought and it has simply been working. Currently I am trying to make it work correctly with UTF-8 data.
In doing so I have hit upon a problem with TUIBQuery and parameterized queries. When using Database.Charset=csUTF8 and setting a parameter value for a CHAR(n)-field and retrieving it before executing the query the value is truncated.
Unfortunately some of my code writes and reads parameter like this in a number of places and therefore dies an ugly death.
To isolate and demonstrate the problem I created a simple fresh database with DEFAULT CHARACTER SET UTF-8 and a table like this:
CREATE TABLE TEST (
CHARFIELD CHAR(20),
VARCHARFIELD VARCHAR(20)
);
I set up the application to connect to the database using TUIBDatabase and TUIBTransaction. Then I created a TUIBQuery-instance, set SQL to a parameterized INSERT-statement into this table, and set the parameters:
Query := TUIBQuery.Create(NIL);
Query.Transaction := Transaction;
Query.SQL.Text := 'INSERT INTO TEST (CHARFIELD, VARCHARFIELD) VALUES (:CHARFIELD, :VARCHARFIELD)';
Query.Prepare(True);
s:= 'ABC';
Query.Params.ByNameAsString['CHARFIELD'] := s;
Query.Params.ByNameAsString['VARCHARFIELD'] := s;
When I now read the parameter-values back like this:
s := Query.Params.ByNameAsString['CHARFIELD'];
s := Query.Params.ByNameAsString['VARCHARFIELD'];
The results are correct for Database.Charset=csNone. But when I instead specify DataBase.Charset=csUTF8 the value for CHARFIELD is truncated to 'A' instead of 'ABC'. The value for VARCHARFIELD is fine. The behaviour is independent of the actual data, I do not have to actually use non-ASCII-characters to provoke it, as the sample shows.
Calling ExecSQL() on the query works correctly and INSERTs the data as expected in both cases.
I have uploaded sourcecode to my simple test program as UIB_UTF8_Test.zip.
Does someone here have any idea what I may be doing wrong and how to do it right?

Imap4 client command LSUB

I have a problem with function TIdIMAP4.ListSubscribedMailBoxes(AMailBoxList: TStrings): Boolean; with this implementation :
function TIdIMAP4.ListSubscribedMailBoxes(AMailBoxList: TStrings): Boolean;
begin
{CC2: This is one of the few cases where the server can return only "OK completed"
meaning that the user has no subscribed mailboxes.}
Result := False;
CheckConnectionState([csAuthenticated, csSelected]);
SendCmd(NewCmdCounter, IMAP4Commands[cmdLSub] + ' "" *',
[IMAP4Commands[cmdList], IMAP4Commands[cmdLSub]]); {Do not Localize}
if LastCmdResult.Code = IMAP_OK then begin
// ds - fixed bug # 506026
ParseLSubResult(AMailBoxList, LastCmdResult.Text);
Result := True;
end;
end;
When I debug I see that the LastCmdResult.Text stringlist is empty, but the LastCmdResult.FormattedReply stringlist has all folders on my email server (Inbox, Sent, Trash, ...). When I tried to use LastCmdResult.FormattedReply count or text, it had immediately lost its data and gave LastCmdResult.FormattedReply.Count=0 and LastCmdResult.FormattedReply.Text=''. So I'd like to know if there is a way to enter the data inside LastCmdResult.FormattedReply and get my email server folders or there is another way to solve my problem ?
I have a problem with function TIdIMAP4.ListSubscribedMailBoxes(AMailBoxList: TStrings): Boolean; with this implementation :
Works fine for me when I try it using the latest SVN version of Indy.
When I debug I see that the LastCmdResult.Text stringlist is empty, but the LastCmdResult.FormattedReply stringlist has all folders on my email server (Inbox, Sent, Trash, ...).
When I run it, the opposite happens. LastCmdResult.Text contains the expected text, and LastCmdResult.FFormattedReply is empty (notice I mention the FFormattedReply data member directly, see below).
When I tried to use LastCmdResult.FormattedReply count or text, it had immediately lost its data and gave LastCmdResult.FormattedReply.Count=0 and LastCmdResult.FormattedReply.Text=''.
That is by design. The FormattedReply property is intended to be used by a client to parse a server reply so it can populate TIdReply's property values, and to be used by a server to generate a new reply using TIdReply's property values. So, you cannot read from the FormattedReply property on the client side.
So I'd like to know if there is a way to enter the data inside LastCmdResult.FormattedReply and get my email server folders or there is another way to solve my problem ?
The whole purpose of ListSubscribedMailBoxes() is to return the folder names in the AMailBoxList parameter. If that is not working for you, then either
you are using a older/buggy version of Indy.
your server is sending the data in a format that TIdIMAP4 is not able to parse.
Without knowing which version of Indy you are actually using, or what the server's reply data actually looks like, there is no way to diagnose your issue one way or the other.

EOleException: Either BOF or EOF is True, or the current record has been deleted. Requested operation requires a current record

I get this exception:
EOleException: Either BOF or EOF is True, or the current record has been deleted. Requested operation requires a current record
trying to delete a record:
if SetupTable.Locate('MyFieldName', AKey, []) then
SetupTable.Delete
What happens here is that there is a check for if a record in the database exists, and if so it is deleted. My problem is that the record does exist, but deleting it produces the above exception.
It's a single user setup, and there is no way the record could disappear in between the execution of those two lines.
This code has been running fine for 8 years. The exception only occurs on Windows 8. I've seen it at 5 different customer sites. Customers using Windows XP, Vista, 7 dont see the problem.
I use Delphi XE to access an MS Access database through ADO with provider Microsoft.Jet.OLEDB.4.0
here is a stack dump:
0073f59d DSALon.exe ADODB 4712 DoRecordsetDelete
0073f5b6 DSALon.exe ADODB 4719 TCustomADODataSet.InternalDelete
006b3dd1 DSALon.exe DB 12947 TDataSet.CheckOperation
006b3ad8 DSALon.exe DB 12856 TDataSet.Delete
007cade6 DSALon.exe DatabaseUnit 629 ClearSetup
The offending bit of code in ADODB look like this:
procedure DoRecordsetDelete(DataSet: TCustomADODataSet; AffectRecords: TAffectRecords);
begin
with DataSet do
try
Recordset.Delete(AffectRecordsValues[AffectRecords]);
{ When CacheSize > 1, Recordset allows fetching of deleted records.
Calling MovePrevious seems to work around it }
if (CacheSize > 1) and (PRecInfo(ActiveBuffer).RecordNumber <> 1) then
begin
Recordset.MovePrevious;
Recordset.MoveNext;
end;
Recordset.MoveNext;
except
on E: Exception do
begin
Recordset.CancelUpdate;
DatabaseError(E.Message);
end;
end;
end;
which leads me to think that the CacheSize is relevant here. I do use a CacheSize > 1.
The MSDN help on ADO CacheSize property says:
If CacheSize is set to a value greater than one, the navigation methods (Move, MoveFirst, MoveLast, MoveNext, and MovePrevious) may result in navigation to a deleted record, if deletion occurs after the records were retrieved. After the initial fetch, subsequent deletions will not be reflected in your data cache until you attempt to access a data value from a deleted row. However, setting CacheSize to one eliminates this issue since deleted rows cannot be fetched.
Still, I'm 100% sure that the record that I'm trying to delete isn't already deleted.
But maybe doing a Resync before the Locate might avoid the problem..

Creating a database in Firebird using FireDac (Delphi)

I recently changed from AnyDac to FireDac (8.0.5.3365). We're running Delphi 2006.
When I was using the AnyDac version of this component I could create a new database by doing the following..
Setup my connection
fConnection.LoginPrompt := false;
fConnection.ResourceOptions.SilentMode := true;
fConnection.Params.Clear;
fConnection.Params.Add(Format('DriverID=%s', ['IB']));
fConnection.Params.Add(Format('Database=%s', [fConnectionInfo.xDatabase]));
fConnection.Params.Add(Format('CharacterSet=%s', ['UTF8']));
fConnection.Params.Add(Format('user_name=%s', [fConnectionInfo.xUserName]));
fConnection.Params.Add(Format('password=%s', [fConnectionInfo.xPassword]));
fConnection.Params.Add(Format('ExtendedMetadata=%s', ['True']));
fConnection.Params.Add(Format('CreateDatabase=%s', ['Yes']));
fConnection.Params.Add(Format('Protocol=%s', ['Local']))
//database path = C:\Users\LoginName\AppData\Local\AppName\TestDB.FDB
Open and close the connection
fConnection.Open;
fConnection.Close;
And then I could run my create table sql scripts on the existing database.
But now when I do this with the FireDac version, the Open command raises the fbe_unavailable error as if I didn't specify the CreateDatabase parameter.
Should I be doing this a different way?
Thanks for your time.
Corey.
You have a full example here http://docwiki.embarcadero.com/RADStudio/Rio/en/Executing_SQL_Scripts_%28FireDAC%29
For example, the following Firebird script creates a database, and can be executed using TFDScript:
SET SQL DIALECT 3;
SET NAMES UTF8;
SET CLIENTLIB 'C:\fb25\bin\fbclient.dll';
CREATE DATABASE 'E:\Test2.ib'
USER 'sysdba' PASSWORD 'masterkey'
PAGE_SIZE 16384
DEFAULT CHARACTER SET NONE;
SET TERM ^ ;
CREATE PROCEDURE MY_PROC RETURNS (aParam INTEGER) AS
BEGIN
aParam = 10;
END^
You should use CreateDatabase=Yes connection definition parameter
additionally to other required parameters:
http://docwiki.embarcadero.com/RADStudio/Rio/en/Connect_to_Firebird_(FireDAC)

Advantage Table File in use error. How can I resolve?

I'm having trouble getting a certain table to open up in more then one instance of my program.
Whats happening is I'm trying to allow users to open up and replace a current table(part of a data dictionary - FileForm.ImagesTable) with an older table (not included in the data dictionary). It works great for one instance of the program but when we try to open up that same file simultaneously on another instance. I get the following error.
FileName.ADT This file is in use. Enter a new name or close the file that's open in another program.
Below is the code I have reassigning the table name and datapath to the selected table.
OpenDialog1.FileName := '*.adt';
OpenDialog1.Filter := 'Software 6.0 Files (*.adt)|*.adt|Software 5.x Files (*.dbf)|*.dbf';
OpenDialog1.InitialDir := DataPath;
if OpenDialog1.Execute then
begin
Str1 := Trim(OpenDialog1.FileName);
if Length(Str1) = 0 then
Exit;
DSImage.Enabled := False;
with FileForm.ImagesTable do
begin
Active := False;
AfterOpen := FileForm.TableOther.AfterOpen;
DataBaseName := ExtractFilePath(Str1);
TableName := ExtractFileName(Str1);
Active := True;
end;
end;
Edit * Using Advtantage 8.1, Seems to be a windows error because the error happens in the dialogue window. And yes Exclusive is set to false.
Any thougths on why this is happening and how this could be resolved are appreciated.
Thanks
You're not clear on the specific error - is it a Windows error or an Advantage error?
If it's a Windows error, it may be because you've specified exclusive access to the table (ImageTable.Exclusive = True). This would mean that the first instance of the app could open it, but subsequent tries would fail with a File is in use error.
If it's an Advantage error, the Advantage help file (here in v11's documentation, since you didn't specify a version of ADS - note it's in a frame, so you may need to use this link, navigate to Advantage Developers Guide, expand the Part 1->Chapter 4 - Dictionaries->Understanding Dictionaries topic) says:
A data dictionary is a special file that serves as the sole access point for database tables
Note the sole access point. Once a table is in the data dictionary, it belongs to the data dictionary. You're trying to replace that reference with something outside the scope of the dictionary, and that isn't allowed. I'm pretty sure that the problem is related to that - ADS puts a proprietary lock on tables that are included in the dictionary, and controls access to those files through the server by way of the dictionary.
You'll need to either remove the table from the dictionary and use it as a free table, or come up with a different strategy for removing the current data and replacing it with other data to preserve the integrity of the dictionary.
It looks like you are only using the Open Dialog to get the name of the table.
On the Open dialog try setting the option ofShareAware
OpenDialog1.Options := OpenDialog1.Options + [ ofShareAware ];
Once the table is open with Advantage the mode is both deny write, deny read and as a result will return a sharing error if anything non-advantage tries to open the table.

Resources