ADO - how to get primary key fields - database

I'm using ADO (Delphi & C++ Builder) and I would like get primary key fields (their names) that are in some TADOTable component. How to do it?
I found out that I need to use ADOConnection->OpenSchema but don't know how to use this..
Now I tried this:
int bounds[] = {0,2};
OleVariant A(bounds,1, varVariant);
A.PutElement(varEmpty,0);
A.PutElement(varEmpty,1);
A.PutElement("MyDBTable",2);
OleVariant EmptyParam;
EmptyParam.VType = VT_ERROR;
EmptyParam.VError = DISP_E_PARAMNOTFOUND;
TADODataSet *temp = new TADODataSet(NULL);
AdoConnection1->OpenSchema(siPrimaryKeys, A, EmptyParam, temp);
temp->Open();
temp->First();
while (!temp->Eof)
{
Memo1->Lines->Add(temp->Fields->Fields[0]->AsString);
temp->Next();
}
temp->Close();
delete temp;
When running this code I get: "Object or provider is not capable of performing requested operation."?

References for OpenSchema Method (ADO), examples can be found here
An example implemention in Delphi for Microsoft Access and MSSqlServer could look like this:
Procedure OpenPrimaryKeyInfo ( Connection:TAdoConnection
; DatabaseName , SchemaName , TableName : Variant
; Display:TAdodataset );
begin
Connection.OpenSchema( siPrimaryKeys
, VarArrayOf([ DatabaseName , SchemaName , TableName ])
, EmptyParam , Display );
end;
Example call for Microsoft Access:
OpenPrimaryKeyInfo( AdoConnection2 , UnAssigned , UnAssigned , 'TableX' , Adodataset1 );
Example call for MSSqlServer:
OpenPrimaryKeyInfo( AdoConnection1 , 'MyDataBase' , 'dbo' , 'TableX' , Adodataset1 );

You establish the connection and open it as usual (using TADOConnection.ConnectionString and TADOConnection.Open), and then ask for the schema using OpenSchema. The TADODataSet you provide as the last parameter will contain a RecordSet that you can use just like any other dataset.
Here's a quick sample I threw together (thanks to #bummi for the correction to the third parameter - Unassigned and Null both compiled, but didn't actually work when tested). I dropped a TADOConnection, TADODataSet, and TMemo on a new form and configured the TADOConnection quickly to point to a simple SQL Server Express database I have for some testing - I included the connection string; the only change I made to it was in the computer name provided in the Data Source portion).
procedure TForm3.FormShow(Sender: TObject);
var
i: Integer;
sLine: string;
begin
Memo1.Clear;
ADOConnection1.ConnectionString := 'Provider=SQLOLEDB.1;' +
'Integrated Security=SSPI;' +
'Persist Security Info=False;' +
'Initial Catalog=Contacts;' +
'Data Source=MyComputer\SQLEXPRESS';
ADOConnection1.Connected := True;
ADOConnection1.OpenSchema(siPrimaryKeys, Unassigned, EmptyParam, ADODataSet1);
sLine := '';
for i := 0 to ADODataSet1.FieldCount - 1 do
sLine := sLine + ADODataSet1.Fields[i].FieldName + #9;
Memo1.Lines.Add(sLine);
Memo1.Lines.Add('');
while not ADODataSet1.Eof do
begin
sLine := '';
for i := 0 to ADODataSet1.FieldCount - 1 do
sLine := sLine + ADODataSet1.Fields[i].AsString + #9;
Memo1.Lines.Add(sLine);
ADODataSet1.Next;
end;
end;
The possible values for the SchemaInfo value (the first parameter passed to OpenSchema) can be found in the ADODB unit - they're documented in the Delphi help file (note that the documentation says that not all of them are available via ADO):
TSchemaInfo = (siAsserts, siCatalogs, siCharacterSets, siCollations,
siColumns, siCheckConstraints, siConstraintColumnUsage,
siConstraintTableUsage, siKeyColumnUsage, siReferentialConstraints,
siTableConstraints, siColumnsDomainUsage, siIndexes, siColumnPrivileges,
siTablePrivileges, siUsagePrivileges, siProcedures, siSchemata,
siSQLLanguages, siStatistics, siTables, siTranslations, siProviderTypes,
siViews, siViewColumnUsage, siViewTableUsage, siProcedureParameters,
siForeignKeys, siPrimaryKeys, siProcedureColumns, siDBInfoKeywords,
siDBInfoLiterals, siCubes, siDimensions, siHierarchies, siLevels,
siMeasures, siProperties, siMembers, siProviderSpecific);

Related

How to get the count of elements in database using delphi XE8

I am trying to make an e-mail sender with Delphi. I have database with statuses and emails and I need to get the number of elements in my database so that I can make a for loop. This is part of my code:
procedure TForm1.bDataBaseClick(Sender: TObject);
var
i : integer;
begin
FDConnection1.StartTransaction;
FDConnection1.Open();
//FDQuery1.ExecSQL('UPDATE Vasko '
//+' SET BatchNumber = 112233 '
//+' WHERE StatusOfProd = 4');
//FDQuery1.ExecSQL('select * from Vasko');
for i := 0 to FDQuery1.ComponentCount - 1 do
showmessage('hello');
FDConnection1.Commit;
FDConnection1.Close();
end;
For now I just want to make the loop that shows message several times. I think that I have mistake in this line:
for i := 0 to FDQuery1.ComponentCount - 1 do
Thank you in advance!

Inno Setup String literal too long insert BLOB into table

I'm trying to write a script by inno setup to insert a raw in a table which has a BLOB column and I don't know how to write my code correctly ,My database is Oracle and my Inno setup compiler version is 5.5.3 as well.Does anyone help me to find a solution?
This is all my efforts :
var
Content: String;
try
ADOCommand := CreateOleObject('ADODB.Command');
ADOCommand.ActiveConnection := ADOConnection;
Stream := TFileStream.Create('D:\test.png', fmOpenRead);
Count := Stream.Size-1;
Stream.Seek(-Count, soFromEnd);
SetLength(Buffer, 1);
Content := '$' ;
for Index := 1 to Count do
begin
Stream.ReadBuffer(Buffer, 1);
Content:= Content + (Format('%2.2x', [Ord(Buffer[1])]));
end;
ADOCommand.CommandText :='insert into TestBinaryTable(id, blobData) values(1 ,''' + Content + ''')';
ADOCommand.Execute();
I do not think you should prefix the raw data with $.
Remove this line:
Content := '$' ;

Cannot use MSSQL Timestamp as a parameter in Delphi XE8

We are in the process of upgrading one of our projects from Delphi XE to XE8. Our audit code makes use of a TIMESTAMP field in a MSSQL (2012 in this instance) database and selects from a table using this as a parameter in the WHERE clause.
We now are no longer getting any results running the following code:
procedure TForm2.Button1Click(Sender: TObject);
begin
ADODataset1.CommandText := 'SELECT * FROM CURRENCYAUDIT';
ADODataset2.CommandText := 'SELECT * FROM CURRENCYAUDIT WHERE Audit_Timestamp = :Timestamp';
ADODataset2.Parameters.Refresh;
ADODataset1.Open;
if ADODataset1.FieldByName('audit_timestamp').IsNull or ADODataset1.IsEmpty then
begin
showmessage('nothing to compare');
end;
ADODataset2.Parameters[0].Value := ADODataset1.FieldByName('audit_timestamp').Value;
ADODataset2.Open;
caption := inttostr(ADODataset2.RecordCount);
end;
Where CurrencyAudit is any old MSSQL table containing an notnull timestamp audit_timestamp field.
The caption of the form is 0 with no message shown.
Any idea how I can get this to work? Tried AsString (nonsense string, 0 results), AsSQLTimestamp (parameter doesn't accept) and AsBytes (0 return). Unfortunately the return of the .Value only evalates as 'variant array of byte' which isn't helpful to visualise/see what it is.
Edit: Running it as .AsBytes and viewing that in the debugger I can see that the XE verison is returning 0,0,0,0,0,8,177,22 whereas the XE8 is returning 17,32,0,0,0,0,0,0. Checking other fields of the (real) database shows the record is the same. Looks like a bug in reading TIMESTAMPs from the DB
I'm using two AdoQueries. The following works fine for me in D7, correctly returning 1 row in AdoQuery2, but 0 records in XE8, so obviously has the same XE8 problem as you've run into.
var
S : String;
V : Variant;
begin
AdoQuery1.Open;
S := AdoQuery1.FieldByName('ATimeStamp').AsString;
V := AdoQuery1.FieldByName('ATimeStamp').AsVariant;
Caption := S;
AdoQuery2.Parameters.ParamByName('ATimeStamp').Value := V;
AdoQuery2.Open;
Just for testing, I'm running my AdoQuery1 and AdoQuery2 against the same server table.
Update : I've got a similar method to the one in your answer working that avoids the need for your Int64ToByteArray, at the expense of some slightly messier (and less efficient) Sql, which may not be to your taste.
In my source AdoQuery, I have this Sql
select *, convert(int, atimestamp) as inttimestamp from timestamps
and in the destination one
select * from timestamps where convert(int, atimestamp) = :inttimestamp
which of course avoids the need for a varBytes parameter on the second AdoQuery, since one can pick up the integer version of the timestamp column value and assign it to the inttimestamp param.
Btw, in your original q
if ADODataset1.FieldByName('audit_timestamp').IsNull or ADODataset1.IsEmpty then
the two expressions would better be written the other way around. Unless ADODataset1 has persistent fields, if it contains no records when opened, referring to the audit_timestamp should raise a "Field not found" exception.
It appears that EMBT have broken the conversion of a TIMESTAMP into a byte array. The XE version of the bytearray is correct and manually pulling down the data as an int64 and then building the bytearray by hand (is there an out-of-the-box function for this?) and using that as the parameter works in XE8.
I've no idea whether this is a similar issue with other binary data types. Hope not!
Working code:
procedure TForm2.Button1Click(Sender: TObject);
var
TestArray: TArray<Byte>;
j: integer;
function Int64ToByteArray(const inInt: uint64): TArray<Byte>;
var
i: integer;
lInt: int64;
begin
SetLength(result, 8);
lInt := inint;
for i := low(result) to high(result) do
begin
result[high(result)-i] := lInt and $FF;
lInt := lInt shr 8;
end;
end;
begin
ADODataset1.CommandText := 'SELECT *, cast(audit_timestamp as bigint) tmp FROM CURRENCYAUDIT';
ADODataset2.CommandText := 'SELECT * FROM CURRENCYAUDIT WHERE Audit_Timestamp = :Timestamp';
ADODataset2.Parameters.Refresh;
ADODataset1.Open;
if ADODataset1.FieldByName('audit_timestamp').IsNull or ADODataset1.IsEmpty then
begin
showmessage('nothing to compare');
end;
ADODataset2.Parameters[0].Value := Int64ToByteArray(ADODataset1.FieldByName('tmp').asInteger);
ADODataset2.Open;
caption := inttostr(ADODataset2.RecordCount);
end;
I've also checked this going down my entire (real) table and ensuring all other fields match to make sure it's not a one-off!
I'll raise a ticket with EMBT for them to sit on and ignore for 5 years ;-)
https://quality.embarcadero.com/browse/RSP-11644

"Must declare the variable #myvariable" error with ADO parameterized query

i am trying to use parameterized queries with ADO. Executing the Command object throws the error:
Must declare the variable '#filename'
i declare the parameter #filename using CreateParameter/Append:
sql := 'INSERT INTO Sqm(Filename, data) VALUES(#filename, #data)';
command := CoCommand.Create;
command.Set_ActiveConnection(Connection.ConnectionObject);
command.Set_CommandText(sql);
command.Set_CommandType(adCmdText);
command.Parameters.Append(Command.CreateParameter('#filename', adLongVarWChar, adParamInput, -1, Filename));
command.Parameters.Append(Command.CreateParameter('#data', adLongVarWChar, adParamInput, -1, xml);
command.Execute({out}recordsAffected, EmptyParam, adCmdText or adExecuteNoRecords);
What am i doing wrong?
As far i know ADO doesn't supports named parameters in SQL sentences (SELECT, INSERT, UPDATE), so you must use the ? char to indicate the parameter
sql := 'INSERT INTO Sqm(Filename, data) VALUES(?, ?)';
and then assign the parameters values in the same order as are used in the sql sentence.
ADO 2.6 Introduces the NamedParameters property, but it seems which only works with stored procedures.
try this
uses ADODB, DB;
...
...
... and then in some event handler (e.g. button click),
var
aCommand :TADOCommand;
begin
aCommand := TADOCommand.create(self);
aCommand.ConnectionString := 'build the connection string or use TADOConnection and assign to Connection property instead of ConnectionString property';
aCommand.commandText := 'INSERT INTO Sqm(Filename, data) VALUES(:filename, :data);';
aCommand.parameters.paramByName('filename').value := 'test';
aCommand.parameters.paramByName('data').value := 'some data';
aCommand.execute;
aCommand.free;
end;
I have been using parameter by names this way for TADOCommand and TADOQuery with no problem.
Use Parameters.AddWithValue as shown below
connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Jet OLEDB:Database Password=RainbowTrout;";
InsertQry = "Insert into Sections(Name, PartNumber, VersionNumber, Channel, Address, Status, IPAddr) "
+ "values(#SectionName, #PartNumber, #VersionNumber, #Channel, #Address, #Status, #IPAddr)";
NewCfgConnection.ConnectionString = string.Format(connectionString, ConfigFN);
NewCfgCommand.Connection = NewCfgConnection;
NewCfgCommand.CommandText = InsertQry;
NewCfgConnection.Open();
// Clear parameter values from last record
NewCfgCommand.Parameters.Clear();
// Insert record into sections table - set parameters
NewCfgCommand.Parameters.AddWithValue("#SectionName", sSectionName);
NewCfgCommand.Parameters.AddWithValue("#PartNumber", sPartNumber);
NewCfgCommand.Parameters.AddWithValue("#VersionNumber", sVersionNumber);
NewCfgCommand.Parameters.AddWithValue("#Channel", iChannel);
NewCfgCommand.Parameters.AddWithValue("#Address", iAddress);
NewCfgCommand.Parameters.AddWithValue("#Status", iStatus);
NewCfgCommand.Parameters.AddWithValue("#IPAddr", iIP);
NewCfgCommand.ExecuteNonQuery();

View output of 'print' statements using ADOConnection in Delphi

Some of my MS SQL stored procedures produce messages using the 'print' command. In my Delphi 2007 application, which connects to MS SQL using TADOConnection, how can I view the output of those 'print' commands?
Key requirements:
1) I can't run the query more than once; it might be updating things.
2) I need to see the 'print' results even if datasets are returned.
That was an interesting one...
The OnInfoMessage event from the ADOConnection works but the Devil is in the details!
Main points:
use CursorLocation = clUseServer instead of the default clUseClient.
use Open and not ExecProc with your ADOStoredProc.
use NextRecordset from the current one to get the following, but be sure to check you have one open.
use SET NOCOUNT = ON in your stored procedure.
SQL side: your stored procedure
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[FG_TEST]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[FG_TEST]
GO
-- =============================================
-- Author: François
-- Description: test multi ADO with info
-- =============================================
CREATE PROCEDURE FG_TEST
AS
BEGIN
-- SET NOCOUNT ON absolutely NEEDED
SET NOCOUNT ON;
PRINT '*** start ***'
SELECT 'one' as Set1Field1
PRINT '*** done once ***'
SELECT 'two' as Set2Field2
PRINT '*** done again ***'
SELECT 'three' as Set3Field3
PRINT '***finish ***'
END
GO
Delphi side:
Create a new VCL Forms Application.
Put a Memo and a Button in your Form.
Copy the following text, change the Catalog and Data Source and Paste it onto your Form
object ADOConnection1: TADOConnection
ConnectionString =
'Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security In' +
'fo=False;Initial Catalog=xxxYOURxxxDBxxx;Data Source=xxxYOURxxxSERVERxxx'
CursorLocation = clUseServer
LoginPrompt = False
Provider = 'SQLOLEDB.1'
OnInfoMessage = ADOConnection1InfoMessage
Left = 24
Top = 216
end
object ADOStoredProc1: TADOStoredProc
Connection = ADOConnection1
CursorLocation = clUseServer
ProcedureName = 'FG_TEST;1'
Parameters = <>
Left = 24
Top = 264
end
In the OnInfoMessage of the ADOConnection put
Memo1.Lines.Add(Error.Description);
For the ButtonClick, paste this code
procedure TForm1.Button1Click(Sender: TObject);
const
adStateOpen = $00000001; // or defined in ADOInt
var
I: Integer;
ARecordSet: _Recordset;
begin
Memo1.Lines.Add('==========================');
ADOStoredProc1.Open; // not ExecProc !!!!!
ARecordSet := ADOStoredProc1.Recordset;
while Assigned(ARecordSet) do
begin
// do whatever with current RecordSet
while not ADOStoredProc1.Eof do
begin
Memo1.Lines.Add(ADOStoredProc1.Fields[0].FieldName + ': ' + ADOStoredProc1.Fields[0].Value);
ADOStoredProc1.Next;
end;
// switch to subsequent RecordSet if any
ARecordSet := ADOStoredProc1.NextRecordset(I);
if Assigned(ARecordSet) and ((ARecordSet.State and adStateOpen) <> 0) then
ADOStoredProc1.Recordset := ARecordSet
else
Break;
end;
ADOStoredProc1.Close;
end;
In .net's connection classes there is an event called InfoMessage. In a handler for this event you can retrieve the InfoMessage (print statements) from the event args.
I believe Delphi has a similar event called "OnInfoMessage" that would help you.
I dont think that is possible.
You might use a temp table to dump print statements and return it alongwith results.
Some enhancements to Francois' code (as tested with DXE2) to cater for multiple print statements and the results from a variable number of selects. The changes are subtle.
procedure TForm1.ADOConnection1InfoMessage(Connection: TADOConnection;
const Error: Error; var EventStatus: TEventStatus);
var
i: integer;
begin
// show ALL print statements
for i := 0 to AdoConnection1.Errors.Count - 1 do
begin
// was: cxMemo1.Lines.Add(Error.Description);
cxMemo1.Lines.Add(
ADOConnection1.Errors.Item[i].Description);
end;
end;
procedure TForm1.cxButton1Click(Sender: TObject);
const
adStateOpen = $00000001; // or uses ADOInt
var
records: Integer;
ARecordSet: _RecordSet;
begin
cxMemo1.Lines.Add('==========================');
ADOStoredProc1.Open;
try
ARecordSet := ADOStoredProc1.RecordSet; // initial fetch
while Assigned(ARecordSet) do
begin
// assign the recordset to a DataSets recordset to traverse
AdoDataSet1.Recordset := ARecordSet;
// do whatever with current ARecordSet
while not ADODataSet1.eof do
begin
cxMemo1.Lines.Add(ADODataSet1.Fields[0].FieldName +
': ' + ADODataSet1.Fields[0].Value);
AdoDataSet1.Next;
end;
// fetch next recordset if there is one
ARecordSet := ADOStoredProc1.NextRecordSet(records);
if Assigned(ARecordSet) and ((ARecordSet.State and adStateOpen) <> 0) then
ADOStoredProc1.Recordset := ARecordSet
else
Break;
end;
finally
ADOStoredProc1.Close;
end;
end;

Resources