Delphi 5.0 Open Interbase/FireBird connection via code - database

This is old stuff! Is related to delphi 5.0 and interbase 1.6.
I'm trying to open a DB connection via code. But this code is related to enabling the connection, all the components were added using delphi drag-drop User Interface: DataSource1, TForm1, DBGrid, DBNavigator etc..
procedure TDataModule2.DataModuleCreate(Sender: TObject);
begin
database.DatabaseName:='C:\MyDatabase.GDB';
database.Connected := true;
database.Open;
IBTransaction.Active := true;
myTable.Open;
end;
I have a TForm with DBGrid and DBNavigator component. I also have a TIBQuery (that DataSource1 is associated to) with this code on the SQLStrings:
SELECT * FROM NEW_TABLE
On the Form I have this code to enable DBNavigator and DBGrid to show the DB Values. The DB is very simple is just a table: NEW_TABLE with a NEW_VALUE of VARCHAR type
procedure TForm1.FormCreate(Sender: TObject);
begin
DataSource1.DataSet.Open;
// This will call the query associated to this DataSource1 the tibQuery1 to call
// SELECT * FROM NEW_TABLE, but the message below appears: IBClientError...
end;
The message appears:
IBClientError with message 'Database not assigned'
OBS1: If I connect the components by hand on delphi user interface, the connection with the DB is established.

You have to assign the Database property of your query, something like:
IBQuery1.Database := MyDatabase;

Related

Reading DateTime from Database using Delphi and FireDAC

I'm failing dismally to read a datetime from an SQLite database using Delphi 10.3 and FireDAC.
As the simplest example, I create a sample database using sqlite as follows:
.open Test.db
CREATE TABLE "TABLE1" ("Name"VarChar(16), "Time" datetime);
INSERT INTO Table1 (Name,Time) VALUES("Fred",time('now'));
Then
select * from Table1
gives Fred|16:52:57 as expected.
If I generate a Delphi program with an FDConnection1 and FDQuery1 linked to a datasource and DBgrid it will read "Fred" but not the time. The value returned by FDquery1 asstring is '' and asfloat is 0.
If I try the FireDAC explorer tool to look at the database it also fails to read the time value but I notice it does read datetimes from some of the example databases so it clearly can work.
Can anyone tell me what I'm missing.
Thanks
Trying to construct a SQL statement as a string in Delphi code can be a bit error prone. However, you should find that the following code executes correctly
procedure TForm2.btnInsertRowClick(Sender: TObject);
const
sInsertRow = ' INSERT INTO Table1 (Name,time) VALUES(''Fred'',datetime(''now''))';
begin
FDConnection1.ExecSql(sInsertRow);
end;
Btw, in general it would be better to use a parameterised INSERT statement than a literal one like the above, but it's not really practical here as you are inserting the now value returned by Sqlite rather than the Delphi now
function.
**Update: ** The code below is from a minimal project which creates your example table,
inserts a row into it and then presents it in db-aware controls (DBGrid, DBEdit) for editing.
It all works exactly as it ought to. In particular, any changes to the row data made through
those controls are retained when tha app is next run. Note that the SQL which creates the table specifies a primary key on the Name column/field: this is necessary for the FDQuery1 to generate the UPDATE statements needed to save changes back to the on-disk table.
The code in the btnSelectClick handler shows how to set the time field's DisplayFormat and EditMask properties so that it only displays the time part of the stored DateTime data.
type
TForm2 = class(TForm)
FDConnection1: TFDConnection;
FDQuery1: TFDQuery;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
DataSource1: TDataSource;
DBEdit1: TDBEdit;
btnCreateTable: TButton;
btnInsertRow: TButton;
btnSelect: TButton;
[...]
public
[...]
const
sCreateTable = 'CREATE TABLE ''TABLE1'' (''Name'' VarChar(16) primary key, ''Time'' datetime)';
sInsertRow = ' INSERT INTO Table1 (Name,time) VALUES(''Fred'',datetime(''now''))';
sSelect = 'select * from table1';
procedure TForm2.btnCreateTableClick(Sender: TObject);
begin
FDConnection1.Connected := True;
FDConnection1.ExecSql(sCreateTable);
end;
procedure TForm2.btnInsertRowClick(Sender: TObject);
const
begin
FDConnection1.ExecSql(sInsertRow);
end;
procedure TForm2.btnSelectClick(Sender: TObject);
var
AField : TDateTimeField;
begin
FDQuery1.SQL.Text := sSelect;
FDQuery1.Open;
// The following shows how to control how the time field is formatted for display
// in gui controls using the field's DisplayFormat
// and how to set up its EditMask for editing
AField := FDQuery1.FieldByName('time') as TDateTimeField;
AField.DisplayFormat := 'hh:nn:ss';
AField.EditMask := '!90:00:00;1;_';
end;
end.
.DFM file
object Form2: TForm2
[...]
object FDConnection1: TFDConnection
Params.Strings = (
'Database=D:\Delphi\Code\FireDAC\db1.sqlite'
'DriverID=SQLite')
LoginPrompt = False
end
object FDQuery1: TFDQuery
Connection = FDConnection1
end
object DataSource1: TDataSource
DataSet = FDQuery1
end
object DBGrid1: TDBGrid
DataSource = DataSource1
end
object DBNavigator1: TDBNavigator
DataSource = DataSource1
end
object DBEdit1: TDBEdit
DataField = 'Time'
DataSource = DataSource1
end
end

How to resume/retry a broken TADOConnection across the application?

I have a data module with a global TADOConnection (with the default KeepConnection set the true).
There are numerous datasets and queries in my existing application that use this global TADOConnection.
I was wondering if there is some smart way to resume/retry the ado connection in case of a short network disconnection? (this situation happens sometimes with clients who have a not so stable connections).
Its easy to reproduce what I need. simply open TADOConnection on start-up. open some TADODataSet, and then disable and enable your "Local Area Connection". if you try to refresh the dataset, an EOleException exception is raised
"Connection failure"
or
"[DBNETLIB][ConnectionWrite (send()).]General network error. Check
your network documentation"
If I restart the application all is good.
No events are fired by the TADOConnection at the time of network disconnections. and TADOConnection.Connectedremains true
of course I could use a try/catch for every TDataSet.Open or Execute but I'm looking for some "centralized" solution for my large application.
so in case of "Connection failure" I could know which dataset is trying to open, and retry.
No never firing at the time of network disconnections. But you can check connection before every command. So if AdoConnection disconnected, you can reconnect and execute your command after this.
If you wanna centralized solution and you have 1 Connection, you can do that like this;
Const
ConnectionTestString=' '; //Yes, it's work on Sql Server. If doesnt your db, you can use 'Select 1'
Procedures;
Procedure TDM.GetCommandResult_Conn(CText:String;Connection : TAdoConnection);
var Ado_Ds_Tmp:TAdoCommand;
Begin
Ado_Ds_Tmp:=TAdoCommand.Create(self);
try
Ado_Ds_Tmp.Connection:=Connection;
Ado_Ds_Tmp.ParamCheck := False;
Ado_Ds_Tmp.CommandText:=CText;
try
Ado_Ds_Tmp.Execute;
except
DM.RaiseExceptionCreate('Error ! Command, ('+StrToList(CText, ' ')[0]+')');
end;
finally
Ado_Ds_Tmp.Destroy;
end;
end;
procedure TDM.ADOConnection1WillExecute(Connection: TADOConnection;
var CommandText: WideString; var CursorType: TCursorType;
var LockType: TADOLockType; var CommandType: TCommandType;
var ExecuteOptions: TExecuteOptions; var EventStatus: TEventStatus;
const Command: _Command; const Recordset: _Recordset);
var
ErrorLogFileName : string;
ErrorFile : TextFile;
ErrorData : string;
Msg : String;
begin
try
if (CommandText<>ConnectionTestString) then begin
DM.GetCommandResult_Conn(ConnectionTestString, Connection);
end;
except
try
try
Connection.Connected := False;
except
end;
try
Connection.ConnectionString := AdoConnectionString;
Connection.Mode:=cmShareDenyNone;
finally
try
Connection.Connected := True;
// If you wanna log for frequency
ErrorLogFileName := ChangeFileExt(Application.ExeName,'.error.log');
AssignFile(ErrorFile, ErrorLogFileName);
if FileExists(ErrorLogFileName) then
Append(ErrorFile)
else
Rewrite(ErrorFile);
try
ErrorData := Format('%s : %s : %s (%s / %s)',[DateTimeToStr(Now), 'Disconnected but we reconnect.', '', 'UserName : '+DBUser, 'Client : '+GetComputerNetName]);
WriteLn(ErrorFile,ErrorData);
finally
CloseFile(ErrorFile)
end;
except
DM.RaiseExceptionCreate('ReConnection Failed!');
end;
end;
except
end;
end;
end;
Any question?
The idea could be to catch the connection error then manage it with retries.
The Suggestion:
This function returns the exception descripion
function GetStrException(ExceptObject: TObject; ExceptAddr: Pointer):String;
var
Buffer: array[0..1023] of Char;
begin
ExceptionErrorMessage(ExceptObject, ExceptAddr, Buffer, SizeOf(Buffer));
Result:=Buffer;
end;
this is a simple idea to test!
procedure TForm1.Button2Click(Sender: TObject);
var
s,error:String;
begin
//a select as an example
S := 'SELECT COUNT(*) FROM MyTable';
TRY
WITH ADOQuery1 DO BEGIN SQL.Clear; SQL.Add(s);OPEN;END;
Memo1.Lines.ADD(ADOQuery1.Fields[0].AsString);
EXCEPT
error:=(GetStrException(ExceptObject,ExceptAddr));
//using MySql my error case is when the connection is lost, so the error is "Server has gone away"
if pos(error,'has gone away')>0 then
begin
Showmessage('Connection Error, please try again');
try
sleep(1000)
AdoConnection1.close;
AdoConnection1.open;
except
Showmessage('The connection Error persists, please contact the support');
end;
end;
END;
end;
A good solution is centralize the insert/update and select procedure, then
catch the error and try to adjust the situation one or two times before
show message to the user

Datasnap Delphi XE5 Server side dynamic sql

I am having a small problem with datasnap in delphi Xe5.
I need to run dynamic queries on the server side. I am using an ADO connection on the server methods.
Server Procedure:
var
qryNew: TADOQuery;
dspNew: TDatasetProvider;
begin
qryNew := TADOQuery.Create(nil);
qryNew.Connection := Adoconnection1;
qryNew.SQL.Text := SQL;
qryNew.Name := 'qry'+IntToStr(1);
dspNew := TDatasetProvider.Create(nil);
dspNew.Name := 'dsp'+IntToStr(1);
dspNew.Dataset := qryNew;
qryNew.Open;
Result := dspNew.Name;
end;
Client side procedure:
ClientDataset1.ProviderName :=clientmodule3.ServerMethods1Client.GetDataForClient('Select top 10 * from ODBC.trans_day_2009_1111;');
On the main form i have a SQLconnection, DSProvider connection and a client dataset. When I try to set the provider name for the client with the above code, i get the error "Remote error: Provider not exported : DSP1"
Usually (not always), error messages are helpful if you spend the time to understand them. If it says the provider is not exported, you should have checked why it wasn't - i.e. the doc says:
"To enable the remote data module to pass calls to the provider, the provider's Exported property must be True, and its Owner property must specify the remote data module."
http://docwiki.embarcadero.com/Libraries/XE5/en/Datasnap.Provider.TDataSetProvider
Your provider has no owner.

Bug with multiple TADOConnections

I already figured out a display table bug using ADO and SQL Server and different connections to one database. ( see details at BUG #1 )
Another issues come while trying to delete records
The code goes like this
function resettable (tablename, databasename, servername) : Boolean;
var
aADOQuery : TADOQuery;
aADOConnection : TAdoConnection,
begin
/// Create ADO stuff
aADOQuery := TADOQuery.Create;
aADOConnection := TAdoConnection.Create;
/// connect to DB & Table
....
///
aADOQuery.sql.add('delete * from ' + Tablename;
aADOQuery.execsql;
/// free objects after use
....
end;
This code works fine when the ADOConnection is alone on the database. If there has been some activity by any other ADO connection and some modification done, the code fails while the "database is not updated error message"
How to create a solution which will do an update prior to the delete record statement ???

How to make ADO parameter to update SQL Server datetime column?

edit I should have mentioned that I'm trying to do this with Delphi 2006.
OK, I think I have hit on a question with no previous answers.
I have a SQL Server database with columns of type datetime. When I try to insert a row with a parametrized command, I get
Exception class EOleException with message
'[Microsoft][ODBC SQL Server Driver]Optional feature not implemented'.
My insert procedure looks like this:
procedure TForm1.btnZoomClick(Sender: TObject);
const
InsCmd = 'insert into dbo.foo (added, edited, editor, narrative) ' +
'values (:dateAdded, :dateEdited, :theEditor, :theNarrative);';
begin
dmDbToy2.DataModule2.ADOCommand1.CommandText := InsCmd;
with DataModule2.ADOCommand1.Parameters do
begin
// the following line was an attempt to trick VarAsType into making a
// adDbTimeStamp: VarAsType is having none of it.
// FindParam('dateAdded').Value := VarAsType(VarFromDateTime(Now), 135);
FindParam('dateAdded').Value := VarFromDateTime(Now);
FindParam('dateEdited').Value := Null;
FindParam('theEditor').Value := 'wades';
FindParam('theNarrative').Value := Null;
end;
DataModule2.ADOCommand1.Execute;
end;
I found some postings via google which seem to indicate that SQL Server wants a adDbTimeStamp type to update these columns, but VarAsType does not want to make one for me.
Is there a way to create a value for the dateAdded and dateEdited parameters in the code sample?
In the comments thread on the original question, user RRUZ made a suggestion that turned out to resolve the issue: The problem was with the provider. Namely, I was using the OLEDB Provider for ODBC rather than the OLEDB Provider for SQL Server. Changing the provider as suggested made the 'Optional feature not implemented' error message go away and enabled the insert to work with a simple assignment of TDateTime to TParameter.Value, thusly:
FindParam('dateAdded').Value := Now;
Set the datatype for the parameter, it might do a difference in how the parameters is treated. I would also recommend that you use ParamByName instead of FindParam. With ParamByName you get a Param xx not found exception if the parameters does not exist in the Parameters collection. FindParam returns nil if it is not found. I have never needed to use any variant conversion stuff when assigning parameters for a TADOCommand so think you should remove that as well.
Try this.
with ParamByName('dateAdded') do
begin
DataType := ftDateTime;
Value := Now;
end;
with ParamByName('dateEdited') do
begin
DataType := ftDateTime;
Value := Null;
end;
with ParamByName('theEditor') do
begin
DataType := ftString; // or ftWideString if you use nchar/nvarchar
Value := 'wades';
end;
with ParamByName('theNarrative') do
begin
//DataType := ftString // Don't know datatype here
Value := Null;
end;
Just set the parameter as a datetime. I do it all the time in ADO and other conection layers
DataModule2.ADOCommand1.Parameters.ParamByName('dateAdded').Value := Now();
//other code
DataModule2.ADOCommand1.Parameters.ParamByName('dateEdited').Value := Null;
//other code
DataModule2.AdoCommand1.Execute;

Resources