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

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).

Related

IBStoredProc does not commit insert if returns data?

I have a stored procedure which insert/update, and then returns result.
create or alter procedure sp_update_system_sticker (
i_sticker_id integer,
i_file_name file_name_type,
i_sticker_name item_name_type,
i_group_id integer)
returns (
o_result integer)
as
begin
update
system_stickers s
set
s.file_name = :i_file_name,
s.name = :i_sticker_name,
s.fk_stickers_groups = :i_group_id
where
s.id = :i_sticker_id;
o_result = -1;
suspend;
end
I am setting it in Delphi in a IBStoredProc, and execute it as follow:
procedure TDataModule_.updateSystemSticker(stickerId, groupId: integer;
stickerName, fileName: String);
var
r : Integer;
begin
with IBStoredProc_UpdateSystemSticker do
begin
Transaction.Active := true;
ParamByName( 'I_STICKER_ID' ).AsInteger := stickerId;
ParamByName( 'I_GROUP_ID' ).AsInteger := groupID;
ParamByName( 'I_STICKER_NAME' ).AsString := stickerName;
ParamByName( 'I_FILE_NAME' ).AsString := fileName;
ExecProc;
Transaction.Commit;
end;
end;
Anyway it does not commit the result into the database.
If I remove the returns - it start to commit.
How to execute and commit properly stored procedure with IBStoreProc which returns results ?
The problem is the presence of SUSPEND. This makes your stored procedure a selectable procedure, and not an executable procedure. When you use a selectable procedure, then all work done since the previous fetched row will be undone when the cursor is closed (which happens on commit). If you fetched nothing, this means that it is as if no work was performed by the stored procedure*.
In other words, you need to remove the SUSPEND (an executable stored procedure outputs a single row immediately on execute without having to wait for a fetch).
I don't program Delphi, so I can't comment on the specifics of getting results in Delphi.
*: Recent versions of Firebird can prefetch rows, so this might not be entirely accurate
Since you made it a "selectable stored procedure" by adding SUSPEND PSQL statement - just do a select from it.
Use regular TIBQuery instead of TIBStoredProc with a command like
select * from StoredProcedureName( :input_param1, :input_param2, :input_param3 )
I would not recommended to use IBExpress to directly call stored procedures in Firebird. Interbase turned up to have a bug in stored procedures execution, AFAIR something wrong with errors handling. To counter it IBX team added an intentional bug of executing SPs twice (under some conditions), which usually (not always) neutralized the Interbase bug. When Firebird team fixed the server bug - this IBX counter-bug started breaking data. IBX team refused to revert to normal behavior for Firebird databases as Firebird was considered competitor to Interbase.
Equally, IBX would not support Firebird-specific changes made after Interbase 6.x/Firebird 0.9 split. For example:
client DLL name change to avoid collision: it became fbclient.dll or fbembed.dll from gds32.dll, however IBX only supports the legacy name. It is hardcoded and can not be changed. If you have IBX sources you may patch it and recompile the library - but why bother?
Firebird's new datatypes like 64-bit integer and boolean. Again, if you have IBX sources...
Firebird's new APIs are not supported.
That said, there is an IBX add-on library by Dmitry Loginov, IBX FB Utils, and it has a number of rather comfy wrappers, on top of IBX. It alone might be a good pro-IBX argument.
FPC (FreePascal) folks started IBX fork they named IBX2, which would hopefully have first-class support for Firebird. I do not know about quality and development speed of it, but i suspect it might appear an easiest migration route out of IBX, or not.
Personally for Firebird-centric Delphi projects i prefer opensource UIB (Unified Interbase) library. However
Being "lean thin API wrapper" it is not TDataSet derived, albeit having a read-only TDataSet wrapper and trying to keep API closely resembling one of TDataSet.
being Henri's Delphi project it has little documentation (tests and examples mostly) and is abandoned by the author (albeit some other guy was adding patches later)
it has neat features like SQL scripter component (but you might need to extend it to support all Firebird new SQL commands, at least i did it to support FB2's MERGE) and for RECORD in SQLQuery do... loop (albeit you can extract it and make it into a separate add-on over any your DB library of choice)

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

Disabling the login prompt without using the TDatabase bypass

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.

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 :)

DataSnap Limits Inbound Request to 16k

I built a DataSnap server with Delphi XE2 that implements TDSHTTPService. When the inbound request comes in, TIdIOHandler.InitComponent is called in a thread before execution is handed to the method called in TServerMethods. I do not have any Indy components in the server, so DataSnap is using Indy 10 under-the-hood.
.InitComponent() sets the IO handler's max line length to a hard-coded value (FMaxLineLength := IdMaxLineLengthDefault;), which is 16384. I can't find a way to increase the value. I even tried copying the IdIOHandler Unit to the project folder and changing the constant value. But it still picks up the IdIOHandler.dcu from the Indy 10 build, and ignores the copied file in my project folder. I also tried adding a TIdIOHandlerStream component to the server project and setting its MaxLineLength to no avail.
Plan A = Properly set the MaxLineLength value in the DataSnap server.
Plan B = Somehow compile a modified IdIOHandler.pas file into my project.
Are either of these possible? I've been working on this for hours and can't find anything similar in all my searching, and can't seem to make any headway by experimenting.
After recompiling all Indy packages in Delphi XE3, having changed the IdMaxLineLengthDefault constant to 512 * 1024, and working as expected after that, I began searching for the simplest solution to this problem. So, I found out this an easy workaround for this limit.
You can implement a procedure for the OnContextCreated event of the TIdHTTPWebBrokerBridge object used in the main unit of the DataSnap REST Server project. In that event, the AContext object is received, which is created for each request to the DataSnap server. So, in the code for this procedure you just have to override the default value for this property as follows:
procedure TForm1.FormCreate(Sender: TObject);
begin
FServer := TIdHTTPWebBrokerBridge.Create(Self);
{Here you assign the new procedure for this event}
FServer.OnContextCreated:= OnContextCreated;
end;
procedure TForm1.OnContextCreated(AContext: TIdContext);
begin
AContext.Connection.IOHandler.MaxLineLength:= 512*1024 {or whatever value you need);
end;
Short of removing the Delphi XE2 install of Indy 10 and downloading the source, tweaking the constant values and compiling / maintaining my own build forever going forward..., I solve the issue.
I created an additional method in the DataSnap server so that I could create a record in the database with a call to the first method, and then incrementally stream the rest of the data by passing it to the second method 16k at a time -- buffering it in the DataSnap server until all parts are received. Then I update the record in the database with the fully buffered value from the DataSnap server.
Maybe not the most effective solution, but it works and it will scale as needed.

Resources