Disabling the login prompt without using the TDatabase bypass - delphi

I am currently trying to connect to a database using an ODBC Alias to SQL Server. The problem I'm having is that when I use my TQuery object to get the information it always requests login details (nevermind whether I've specified them in the ODBC creation). I don't mind manually setting them in the code, but I can't find how to do that.
The most common solution I've found is to use the database component and go through that. However that comes with its own issues. Due to my dataset being so large and the database component converting the dataset to a Paradox table I keep getting a BDE error of 'Temporary Table Resource Limit'.
I don't get this error if I ignore the database component (which is fine) however this leaves me with the login prompt issue. Has anyone found a way to bypass this for TQuerys without swapping to other connection paths such as ADO?

I'm a bit rusty with the BDE but I don't think there's an easy way to avoid the login prompt if what you're saying is that you're not using a TDatabase component in your project.
The reason is that when you attempt to open your TQuery without a TDatabase (or TSession) component in your project, the default Session object in your app will call the routine below from within your TQuery's OpenCursor:
{ from DBTables.Pas }
function TSession.DoOpenDatabase(const DatabaseName: string; AOwner: TComponent): TDatabase;
var
TempDatabase: TDatabase;
begin
Result := nil;
LockSession;
try
TempDatabase := nil;
try
Result := DoFindDatabase(DatabaseName, AOwner);
if Result = nil then
begin
TempDatabase := TDatabase.Create(Self);
TempDatabase.DatabaseName := DatabaseName;
TempDatabase.KeepConnection := FKeepConnections;
TempDatabase.Temporary := True;
Result := TempDatabase;
end;
Result.Open;
Inc(Result.FRefCount);
except
TempDatabase.Free;
raise;
end;
finally
UnLockSession;
end;
end;
As you can see, if the session can't find an existing TDatabase component with the right name, it creates a temporary one, and it's the call to Result.Open that pops up the login prompt, without, so far as I can see, giving you any opportunity to supply the password + user name before the pop-up (the Session's OnPassword doesn't seem to get called in the course of this).
Obviously you need to check using the debugger that that's what's happening in your app, a temporary TDatabase being created, I mean.
If what I've suggested in the Update below didn't work and I were desperate to avoid using a TDatabase component, I would look into the possibility of maybe deriving a TQuery descendant, and trying to override its OpenCursor to see if I could jam in the user name/password.
Anyway, seeing as you say you're not using an explicit TDatabase, if I understand you correctly, because of the "Temporary Table ..." issue, and seeing as the Session will create a temporary one anyway, I suppose it might be worth your while investigating why the temporary one doesn't provoke the "Temporary Table" error, whereas using a TDatabase component in your app evidently does. Idapi32.Cfg configuration issue, maybe? At the moment, I can't help you with that because I can't reproduce your "Temporary Table" error, despite using my TQuery to do a SELECT on a SqlServer table to return 250,000+ rows.
Oh, that's a point: Does your table contain any BLOBs? I seem to recall there's an Idapi config parameter that lets you reduce the temporary storage space the BDE uses for BLOBs (to zero, maybe, but it's been a long time since I used the BDE "for real").
Update: The thought just occurred to me that since your query seems to work with Session dynamically creating a TDatabase object, maybe it would also work with a TDatabase which you dynamically create yourself. I just tried the following, and it works for me:
procedure TForm1.DatabaseLogin(Database: TDatabase;
LoginParams: TStrings);
begin
LoginParams.Add('user name=sa');
LoginParams.Add('password=1234');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ADatabase : TDatabase;
begin
ADatabase := TDatabase.Create(Self);
ADatabase.AliasName := 'MAT41032';
ADatabase.DatabaseName := 'MAT41032';
ADatabase.SessionName := 'Default';
ADatabase.OnLogin := DatabaseLogin;
Query1.Open;
end;
+1 for an interesting question, btw.

Related

when packing a dbf table, an error is file is in use

When trying to make a request, it displays an error:
File is in use
How can I solve that program?
procedure TForm1.Button4Click(Sender: TObject);
var data,ffg:string;
begin
data:=formatdatetime('ddmm',(DateTimePicker1.Date));
Adoquery2.SQL.Clear;
adoquery2.SQL.text:='Delete from g_rabn where data=data';// deleting data from g_rabn
adoquery2.ExecSQL;
ShowMessage(SysErrorMessage(GetLastError));
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
Adoquery3.close;
Adoquery3.SQL.Clear;
adoquery3.SQl.text:='pack table g_rabn';// packing tablr g_rabn
adoquery3.Open;
ShowMessage(SysErrorMessage(GetLastError));
end;
end.
I can not delete data from the table, they are marked as deleted but require packaging. How to do it programmatically? He writes that file is in use when packing what to do?
You should execute the statement, not open it as a query. One way to achieve that, is to run it using an TADOCommand, not an TADOQuery, or use the ExecSQL method of the TADOQuery.
Also, all other connections to the DBF must be closed, otherwise you can't get the exclusive access that you need for packing the table.
I found this thread from 2005 on another forum, where somebody made this work with two notable parameters:
Using the provider VFPOLEDB.1
Using just the command pack filename.dbf (without the table keyword).
Lastly, I'm not so sure about the line ShowMessage(SysErrorMessage(GetLastError));. This will show you the last API error, but that's on a low level. You are using the ADO components, so if anything is going wrong, you should expect ADO to throw an exception. For all you know ADO already worked around the issue one way or the other, and the error message you're seeing is not even relevant.
I am surprised that I could not get the solution suggested in Golez Troi's answer
to work, especially as it refers to a newsgroup post from someone who had seemingly managed to pack a dBASE table using ADO; as I said in a comment, if I try to call 'Pack xxxx' to pack a dBASE table via ADO, however I do it, I get
Invalid SQL Statement; DELETE, INSERT, PROCEDURE, SELECT or UPDATE expected
.
I was also surprised to notice something in the MS ODBC dBASE docs that I'd not noticed before, namely that the MS ODBC driver for dBASE files requires the BDE
Note
Accessing dBASE ISAM files through the ODBC Desktop Database Drivers requires installation of the Borland database engine
So, seeing as accessing dBASE files via Ado requires the BDE anyway, there seems to me to be
no point avoiding using the BDE to pack the dBASE table using the standard BDE method, namely to call DbiPackTable. I added a TDatabase and TTable
to my ADO test project, after which I was able to execute this code without any problem
procedure TForm1.Button2Click(Sender: TObject);
begin
try
// Insert code here to close any Ado object (TAdoConnection, TAdoCommand, etc) pointing
// at the dBASE table/file
// Also check that not Ado object pointing at it is open in the IDE
//
// Then ...
Database1.DatabaseName := 'MADBF2';
Database1.Connected := True;
Table1.TableName := 'MATest.Dbf';
Table1.Exclusive := True;
Table1.Open;
// Following uses a call to DbiPackTable to pack the target table
Check(DbiPackTable(Table1.DBHandle, Table1.Handle, nil, nil,True));
finally
Table1.Close;
Database1.Connected := False;
end;
end;
FWIW, while I was writing this answer, I noticed that the BDE.Int file (which gives the declarations but not the implementation of the BDE interface) was on the D7 distribution CD but was apparently not installed by default).

Delphi DBGrid date format for Firebird timestamp field

I display the content of a Firebird database into a TDBgrid. The database has a 'TIMESTAMP' data type field that I would like to display with date/time format:
'YYYY/MM/DD HH:mm:ss'. (Now its displayed as 'YYMMDD HHmmss')
How to achieve this?
I tried this:
procedure TDataModule1.IBQuery1AfterOpen(DataSet: TDataSet);
begin
TDateTimeField(IBQuery1.FieldByName('timestamp_')).DisplayFormat := 'YYYY/MM/DD HH:mm:ss';
end;
But this causes some side effects at other parts of the program, so its not an alternative. For example at the 'IBQuery1.Open' statement I get the '...timestamp_ not found...' debugger message in the method that I clear the database with.
function TfrmLogger.db_events_clearall: integer;
begin
result := -1;
try
with datamodule1.IBQuery1 do begin
Close;
With SQL do begin
Clear;
Add('DELETE FROM MEVENTS')
end;
if not Prepared then
Prepare;
Open; //Exception here
Close;
Result := 1;
end;
except
on E: Exception do begin
ShowMessage(E.ClassName);
ShowMessage(E.Message);
Datamodule1.IBQuery1.close;
end;
end;
end;
I get the same exception message when trying to open the query for writing into the database.
*EDIT >>
I have modified the database clear as the following:
function TfrmLogger.db_events_clearall: integer;
var
IBQuery: TIBQuery;
IBTransaction: TIBTransaction;
DataSource: TDataSource;
begin
result := -1;
//Implicit local db objects creation
IBQuery := TIBQuery.Create(nil);
IBQuery.Database := datamodule1.IBdbCLEVENTS;
DataSource := TDataSource.Create(nil);
DataSource.DataSet := IBQuery;
IBTransaction := TIBTransaction.Create(nil);
IBTransaction.DefaultDatabase := datamodule1.IBdbCLEVENTS;
IBQuery.Transaction := IBTransaction;
try
with IBQuery do begin
SQL.Text := DELETE FROM MSTEVENTS;
ExecSQL;
IBTransaction.Commit;
result := 1;
end;
except
on E : Exception do
begin
ShowMessage(E.ClassName + ^M^J + E.Message);
IBTransaction.Rollback;
end;
end;
freeandnil(IBQuery);
freeandnil(DataSource);
freeandnil(IBTransaction);
end;
After clearing the database yet i can load the records into the dbgrid, seems like the database has not been updated. After the program restart i can see all the records been deleted.
The whole function TfrmLogger.db_events_clearall seems very dubious.
You do not provide SQL_DELETE_ROW but by the answer this does not seem to be SELECT-request returning the "resultset". So most probably it should NOT be run by ".Open" but instead by ".Execute" or ".ExecSQL" or something like that.
UPD. it was added SQL_DELETE_ROW = 'DELETE FROM MEVENTS'; confirming my prior and further expectations. Almost. The constant name suggests you want to delete ONE ROW, and the query text says you delete ALL ROWS, which is correct I wonder?..
Additionally, since there is no "resultset" - there is nothing to .Close after .Exec.... - but you may check the .RowsAffected if there is such a property in DBX, to see how many rows were actually scheduled to be deleted.
Additionally, no, this function DOES NOT delete rows, it only schedules them to be deleted. When dealing with SQL you do have to invest time and effort into learning about TRANSACTIONS, otherwise you would soon get drown in side-effects.
In particular, here you have to COMMIT the deleting transaction. For that you either have to explicitly create, start and bind to the IBQuery a transaction, or to find out which transaction was implicitly used by IBQuery1 and .Commit; it. And .Rollback it on exceptions.
Yes, boring, and all that. And you may hope for IBX to be smart-enough to do commits for you once in a while. But without isolating data changes by transactions you would be bound to hardly reproducible "side effects" coming from all kinds of "race conditions".
Example
FieldDefs.Clear; // frankly, I do not quite recall if IBX has those, but probably it does.
Fields.Clear; // forget the customizations to the fields, and the fields as well
Open; // Make no Exception here
Close;
Halt; // << insert this line
Result := 1;
Try this, and I bet your table would not get cleared despite the query was "opened" and "closed" without error.
The whole With SQL do begin monster can be replaced with the one-liner SQL.Text := SQL_DELETE_ROW;. Learn what TStrings class is in Delphi - it is used in very many places of Delphi libraries so it would save you much time to know this class services and features.
There is no point to Prepare a one-time query, that you execute and forget. Preparation is done to the queries where you DO NOT CHANGE the SQL.Text but only change PARAMETERS and then re-open the query with THE SAME TEXT but different values.
Okay, sometimes I do use(misuse?) explicit preparation to make sure the library fetches parameters datatypes from the server. But in your example there is neither. Your code however does not use parameters and you do not use many opens with the same neverchanging SQL.text. Thus, it becomes a noise, making longer to type and harder to read.
Try ShowMessage(E.ClassName + ^M^J + E.Message) or just Application.ShowException(E) - no point to make TWO stopping modal windows instead of one.
Datamodule1.IBQuery1.close; - this is actually a place for rolling back the transaction, rather than merely closing queries, which were not open anyway.
Now, the very idea to make TWO (or more?) SQL requests going throw ONE Delphi query object is questionable per se. You make customization to the query, such as fixing DisplayFormat or setting fields' event handlers, then that query is quite worth to be left persistently customized. You may even set DisplayFormat in design-time, why not.
There is little point in jockeying on one single TIBQuery object - have as many as you need. As of now you have to pervasively and accurately reason WHICH text is inside the IBQuery1 in every function of you program.
That again creates the potential for future side effects. Imagine you have some place where you do function1; function2; and later you would decide you need to swap them and do function2; function1;. Can you do it? But what if function2 changes the IBQuery1.SQL.Text and function1 is depending on the prior text? What then?
So, basically, sort your queries. There should be those queries that do live across the function calls, and then they better to have a dedicated query object and not get overused with different queries. And there should be "one time" queries that only are used inside one function and never outside it, like the SQL_DELETE_ROW - those queries you may overuse, if done with care. But still better remake those functions to make their queries as local variables, invisible to no one but themselves.
PS. Seems you've got stuck with IBX library, then I suggest you to take a look at this extension http://www.loginovprojects.ru/download.php?getfilename=uploads/other/ibxfbutils.zip
Among other things it provides for generic insert/delete functions, which would create and delete temporary query objects inside, so you would not have to think about it.
Transactions management is still on you to keep in mind and control.

Max length TSQLConnection.Params values

Hello fellow StackOverflowers,
Currently I'm facing a situation where it seems that there is a maximum length for the Database property of a TSQLConnection object in Delphi.
When I open the connection to my database I get the following error when I use a rather long (154 chars) database name:
dbExpress Error: [0x0015]: Connection failed
SQL Server Error: unrecognized database parameter block
wrong version of database parameter block
When I relocate my database file to another location (and with that reduce the length of the path) it will connect to the database.
I am currently using the Object Inspector to set the connection properties of the TSQLConnection object.
Basically, my question comes down to this:
Does a TSQLConnection have a maximum length for the values set in the Params property? And if so, what is the maximum length of these values?
Update
I've found two ways to open a copy of Employee.Gdb in a folder with a 160-character name ('abcdefghij0123456789' x 8).
What I did firstly was to edit the DBXConnections.Ini file and changed the Database parameter in the [IBConnection] section to read
Database=localhost:D:\abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890\employee.gdb
Then, I can successfully connect to it, open the Employee.Gdb and make changes to the Customer table. I have verified the changes in IBConsole just in case the copy of Employee.Gdb wasn't the one I assumed it was.
Subsequently, I've found that I can create and open the db in code using Delphi Seattle and Interbase XE7, as follows:
function LongPath : String;
begin
Result := 'D:\' + DupeString('abcdefghij0123456789', 8);
end;
function LongDBName : String;
begin
Result := LongPath + '\Employee.Gdb';
end;
procedure TForm1.OpenDB;
var
Ini : TMemIniFile;
const
scDBXConIni = 'C:\Users\Public\Documents\Embarcadero\Studio\dbExpress\17.0\dbxconnections.ini';
scSourceDB = 'D:\Delphi\Databases\Interbase\Employee.Gdb';
begin
Ini := TMemIniFile.Create(scDBXConIni);
try
// First, blank out the Database value in the IBConnection section
// of DBXConnections.Ini
Ini.WriteString('IBConnection', 'Database', '');
Ini.UpdateFile;
// Next, create the long-named directory and copy Employee.Gdb to it
if not DirectoryExists(LongPath) then
MkDir(LongPath);
Assert(CopyFile(PChar(scSourceDB), PChar(LongDBName), False));
// Set LoadParamsOnConnect to False so that the SqlConnection uses
// the value of the Database we are about to give it
SqlConnection1.LoadParamsOnConnect := False;
SqlConnection1.Params.Values['Database'] := LongDBName;
SqlConnection1.Connected := True;
// Open the CDS to view the data
CDS1.Open;
finally
Ini.Free;
end;
end;
The critical step in doing it this way is setting LoadParamsOnConnect to False, which I confess I'd overlooked in earlier attempts to get this code to work.
I've got some earlier versions of Delphi on this machine, so if you're not using Seattle and the above code doesn't work for you, tell me which one you are using and I'll see if I can try that.
**[Original answer]
Actually, I think that this may be an error occurring in one of the DBX DLLs.
I created a folder with a 160-character name, then copied the demo Employee.Gdb database into it. Interbase XE7's IBConsole can open the db without error. So could a small test project contructed with IBX components in Delphi Seattle.
However, with an equivalent DBX project, when I use the code below
procedure TForm1.Button1Click(Sender: TObject);
begin
SqlConnection1.Params.Values['database'] := 'D:\abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890\employee.gdb';
SqlConnection1.Connected := True;
end;
I get an error in
procedure TDBXDynalinkConnection.DerivedOpen;
var
Count: TInt32;
Names: TWideStringArray;
Values: TWideStringArray;
IsolationLevel: Longint;
DBXError: TDBXErrorCode;
begin
Count := FConnectionProperties.Properties.Count;
FConnectionProperties.GetLists(Names, Values);
CheckResult(FMethodTable.FDBXConnection_Connect(FConnectionHandle, Count, Names, Values));
DBXError := FMethodTable.FDBXConnection_GetIsolation(FConnectionHandle, IsolationLevel);
'I/O error for file "database.gdb"
Error while trying to open file
The operation completed successfully'
and the Database param of the SqlConnection is left at the value 'Database.Gdb', which is not the value I specified, of course, nor was it the value specified in the params in the IDE, which was 'd:\delphi\databases\interbase\employee.gdb'.
I wondered if I could work around this problem by SUBSTing a drive to the 'abcdefg ...' path. I tried that and opening the database as "x:\employee.gdb" , but I get the same error in my DBX app, and also IBConsole cannot access the db either.
I think you need a shorter physical path!**
This is related to MSSql Server:
As a general guideline, long path names greater than 160 characters
might cause problems.
from Microsoft TechNet - https://technet.microsoft.com/en-us/library/ms165768(v=sql.105).aspx

Connecting from a Delphi app to an InterbaseXE7 server on another machine

I have XE8 and the version of InterbaseXE7 that comes with it installed on two machines, A & B. Using IBX or DBX I can connect to the IB server running on the same machine and access its databases without any problem. Btw, I am not a regular IB user.
I had no luck at all connecting from a Delphi app on machine A to an IB database on machine B: I got all manner of errors including a mystifying one about not being able to find the file specified (despite doing a DIR from a CMD prompt to verify that I had the name right) until I discovered that in those circumstances (connecting to a remote server), the database name has to be capitalized in the Delphi app on A exactly as it is on the db host B.
So, assuming there is no way to configure IB and/or IBX to avoid this case-sensitivity, how can I programmatically retrieve a list of the database names, correctly capitalized, on B (assuming I have no access to B's file-system) from a Delphi app on A?
I've tried using the TIBServerProperties component to do this but using code like this:
procedure TForm1.btnPropertiesClick(Sender: TObject);
var
S : String;
begin
IBServerProperties1.Active := True;
IBServerProperties1.FetchDatabaseInfo;
S := IBServerProperties1.DatabaseInfo.DbName[0];
Caption := S;
end;
, the database names are returned from the IB host server in all capitals, which obviously doesn't solve the problem of finding their correct capitalizations.
It turns out that the TIBServerProperties can get DB Aliases from a remote server with the correct capitalization, but not using the DatabaseInfo property. The information can be obtained from its AliasInfo property instead (one of those things that's kind-of obvious with the benefit of hindsight), as shown below.
procedure TForm1.btnPropertiesClick(Sender: TObject);
var
S : String;
i : Integer;
begin
IBServerProperties1.Active := True;
IBServerProperties1.FetchAliasInfo;
for i :=0 to IBServerProperties1.AliasCount - 1 do begin
S := IBServerProperties1.AliasInfo[i].Alias; // <- the .Alias has the
// same capitalization as on the server
S := S + ' ' + IBServerProperties1.AliasInfo[i].DBPath;
Memo2.Lines.Add(S);
end;
end;
, which is good enough for my immediate purpose.
I'd still be interested to know, though, if there is an IB configuration parameter or similar that avoids the case-sensitivity that provoked my q.

Getting Delphi 7 to play with SQL Server Compact 3.5

We have an old application that was written in Delphi 7. It is currently connected to an old Oracle Lite database that is being retired. The powers that be have chosen to move the data to a Microsoft SQL Server Compact database instead. After sepending a good amount of time moving everything over to the SQL CE database, I am now tasked with getting the Delphi application to play nice with the new databases.
The people who are supposed to be smarter than I am (my boss), tell me that I should be able to simply modify the connection and everything should be back in order. However, I have been banging my head against my monitor for two days trying to get the ADO connection in the Delphi application to work with our new SQL CE database.
A slightly simplified example of what I'm working with:
The connection is made in a global object with a TADOConnection named "adoConn":
procedure TGlobal.DataModuleCreate(Sender: TObject);
begin
adoConn.ConnectionString := 'Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=path\db.sdf;';
adoConn.Connected := True;
end;
Shortly after this, a procedure is called to populate some messages. In an effort to trouble shoot the application, I've simplified the code to make a simple query and show the results in a message box. The procedure receives a parameter for the SQL string, but I'm ignoring it for now and manually inserting a simple select statement:
procedure Select(const SQL: string);
var
adoQuery : TADOQuery;
begin
adoQuery := TADOQuery.Create(nil);
try
adoQuery.Connection := Global.adoConn;
adoQuery.SQL.Text := 'select * from CLT_MESSAGES';
adoQuery.ExecSQL;
While not adoQuery.Eof do
begin
// Here I just created a MessageDlg to output a couple of fields.
adoQuery.Next;
end;
finally
adoQuery.Free;
end;
end;
Everything compiles just fine, but when I run the application I get the following error:
"Multiple-step operation generated errors. Check each status value."
I've done some additional trouble-shooting and found that the error is happening at adoQuery.ExecSQL. I've tried several different versions of the connection string and a couple different ways of trying to query the data, but it all ends up the same. I either can't connect to the database or I get that stupid "Mutliple-step" error.
I appreciate, in advance, any assistance that can be offered.
Don't use ExecSQL for queries that return recordsets.
Set either the AdoQuery.Active property to True or use AdoQuery.Open to execute a SELECT statement.
UPDATE
After changing your code we see the real error which is DB_E_OBJECTOPEN.
UPDATE2
After digging deeper it seems that this is a known bug in the OLE DB provider and nvarchar fields bigger than 127 characters.
these references seem to confirm this:
SO: SQL Server Compact Edition 3.5 gives "Multiple-step operation generated errors" error for simple query
ref1: http://www.tech-archive.net/Archive/SQL-Server/microsoft.public.sqlserver.ce/2008-07/msg00019.html
ref2: https://forums.embarcadero.com/thread.jspa?messageID=474517
ref3: http://social.msdn.microsoft.com/Forums/en-US/sqlce/thread/48815888-d4ee-42dd-b712-2168639e973c
Changing the cursor type to server side solved the 127 char issue for me :)

Resources