DBMS_METADATA: Get table SXML with triggers - database

I want to read the SXML representation of a table using this simple function:
CREATE OR REPLACE FUNCTION get_table_sxml(name IN VARCHAR2) RETURN CLOB IS
open_handle NUMBER;
transform_handle NUMBER;
doc CLOB;
BEGIN
open_handle := DBMS_METADATA.OPEN('TABLE');
DBMS_METADATA.SET_FILTER(open_handle,'NAME',name);
transform_handle := DBMS_METADATA.ADD_TRANSFORM(open_handle, 'SXML');
dbms_metadata.set_transform_param(transform_handle,'REF_CONSTRAINTS', true);
dbms_metadata.set_transform_param(transform_handle,'CONSTRAINTS', true);
doc := DBMS_METADATA.FETCH_CLOB(open_handle);
DBMS_METADATA.CLOSE(open_handle);
RETURN doc;
END;
When i select the generated XML with "SELECT get_table_sxml('TABLENAME') FROM dual" i get the complete xml representation, including constraints and ref constraints.
However the triggers associated with this table are missing in the output.
Can anyone give me a hint what i have to do to get the triggers into the output-xml?
The resulting XML is supposed to be used on another database to compare the tables and build diff-scripts using the DBMS_METADATA-package. So i need to use the "sxml" format.

Related

Storing Files in SQL Server Table (varbinary(max) column) using ADOQuery Component

I'm using SQL Server 2019 and Delphi 10.3.
I need to store any kind of files ( like pdf, txt, docx, etc) in a 'Personal_Files' table.
This table is composed by a column with the file extension ( as varchar) and a varbinary(max) column to store the file itself.
I did some research on how to store these files on a table, but without success. Below some example:
var
Input,Output: TStream;
FName: TFileName;
begin
...
//Create Streams and encode Base64:
Input := TFileStream.Create(FName,fmOpenRead);
Output := TFileStream.Create(FName+'Temp',fmCreate);
TNetEncoding.Base64.Encode(Input,Output);
... // Some validations
// In the ADOQuery component, I did this:
with ADOQuery, sql do
begin
close;
clear;
add('INSERT INTO MyDatabase.dbo.MyFilesTable (EXTENSION,FILEBIN)');
add('VALUES (:wextension, :wfilebin)');
Parameters.ParamByName('wextension').Value := TPath.GetExtension(FName);
Parameters.ParamByName('wfilebin').Value := Output.toString;
ExecSQL;
end;
In this example, I tried to parse the stream as String, after the encode, but when I look in the SQL Table, it's the same stream for all the archives I tried. The parameter doesn't accept TStream type. Thank you in advance.
After some research, and some advices, I found a way to send the file to my SQL Server table with ADOQuery. Altough, I learned that this isn't recommended, so it's just to answer my question directly:
Just a change on the final part of the code answers my question ( but again, it's not the recommended way to store files, as commented on the question.):
with ADOQuery, sql do
begin
close;
clear;
add('INSERT INTO MyDatabase.dbo.MyFilesTable (EXTENSION,FILEBIN)');
add('VALUES (:wextension, :wfilebin)');
Parameters.ParamByName('wextension').Value := TPath.GetExtension(FName);
Parameters.Items[1].LoadFromStream(Output,ftVarBytes);
ExecSQL;
end;
Just changing the way I was setting the parameter solved the problem. In this example, using the 'LoadFromStream' on the Paremeters.Items[n], where n is the parameter index, it worked very well. The ftVarBytes is the field type parameter.

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

Create table with firedac without SQL Script

Firedac library centralizes database behavior and have a lot of methods which works fine without care about the Database Type. Actually, using native drivers for most common databases, Firedac hides subtle differences on syntax allowing very flexible changes of database platform.
For example, generators and autoinc fields are easily detectable, CAST and parameters works fine allowing easy migration between databases.
How to use Firedac power to create New Table without instantiate FDQuery, which runs a SQL Script CREATE TABLE?
I hope to create any Object and, calling specific FieldByName for each Object Field, record it on database, but first I need to certify:
If Table is already created
If Field is already created
If record is already created
This is the code I have, so far:
TRecCustomer = record
Id:integer;
Name:String;
Birthday:TDate;
end;
ICustomer = interface
procedure setCustomerId(Value: Integer);
procedure setCustomerName(Value: String);
procedure SetBirthday(Value: TDate);
procedure Post;
end;
TCustomer = class(TInterfacedObjet, ICustomer)
CustomerObject=TRecCustomer;
procedure setCustomerId(Value: Integer);
procedure setCustomerName(Value: String);
procedure SetBirthday(Value: TDate);
procedure Post;
end;
procedure TCustomer.Post;
begin
if not TableExists('Customer') then CreateTable('Customer');
if not FieldExists('Name') then CreateField('Customer','name',ftString,[],40);
if not FieldExists('Id') then CreateField('Customer','Id',ftInteger,[cAutoInc,cNotNull]);
if not FieldExists('Birthday') then CreateField('Customer','birthday',ftDate);
end;
Imagine the procedures
CreateTable(Tablename: String)
CreateField(FieldName: String; FieldType: TDataType; Constraints: TConstraints; Length: Integer = 0);
where
TConstraints = set of (cAutoInc, cNotNull, cUnique, cEtc);
I can do it for specific database, for example Sqlite or Firebird, but I don't know hou to do for any database using Firedac resources.
I found FireDAC.Comp.Client.TFDTable.CreateTable(ARecreate: Boolean = True; AParts: TFDPhysCreateTableParts = [tpTable .. tpIndexes]), suggested by #Ondrej Kelle but I don't understood AParts usage. Somebody have an example? http://docwiki.embarcadero.com/Libraries/Berlin/en/FireDAC.Comp.Client.TFDTable.CreateTable
Thanks a lot.
You can create a TFDTable object, describe your table at least by specifying TableName and adding field definitions in the FieldDefs collection. In practice you'll usually create also a primary key index for some field (that's what can be done by the AddIndex method). After you describe your table, call CreateTable method. A minimal example can be:
var
Table: TFDTable;
begin
Table := TFDTable.Create(nil);
try
Table.Connection := FDConnection1;
{ specify table name }
Table.TableName := 'MyTable';
{ add some fields }
Table.FieldDefs.Add('ID', ftInteger, 0, False);
Table.FieldDefs.Add('Name', ftString, 50, False);
{ define primary key index }
Table.AddIndex('pkMyTableID', 'ID', '', [soPrimary]);
{ and create it; when the first parameter is True, an existing one is dropped }
Table.CreateTable(False);
finally
Table.Free;
end;
end;

Procedure to download file from a given url in Oracle 11g and save it into the blob type column

I am stuck with a problem. I need to create a procedure in Oracle 11g which will get the URL from a given row and download the file from that URL and will save it in a blob type column. Can you guys tell me what my approach should be to achieve this?
you need see documentation for package UTL_HTTP
this is the sample of using
this is the code from the sample above
begin
load_binary_from_url('http://www.oracle.com/us/hp07-bgf3fafb-db12c-2421053.jpg');
end;
CREATE TABLE http_blob_test (
id NUMBER(10),
url VARCHAR2(255),
data BLOB,
CONSTRAINT http_blob_test_pk PRIMARY KEY (id)
);
CREATE SEQUENCE http_blob_test_seq;
CREATE OR REPLACE PROCEDURE load_binary_from_url (p_url IN VARCHAR2) AS
l_http_request UTL_HTTP.req;
l_http_response UTL_HTTP.resp;
l_blob BLOB;
l_raw RAW(32767);
BEGIN
-- Initialize the BLOB.
DBMS_LOB.createtemporary(l_blob, FALSE);
-- Make a HTTP request and get the response.
l_http_request := UTL_HTTP.begin_request(p_url);
l_http_response := UTL_HTTP.get_response(l_http_request);
-- Copy the response into the BLOB.
BEGIN
LOOP
UTL_HTTP.read_raw(l_http_response, l_raw, 32767);
DBMS_LOB.writeappend (l_blob, UTL_RAW.length(l_raw), l_raw);
END LOOP;
EXCEPTION
WHEN UTL_HTTP.end_of_body THEN
UTL_HTTP.end_response(l_http_response);
END;
-- Insert the data into the table.
INSERT INTO http_blob_test (id, url, data)
VALUES (http_blob_test_seq.NEXTVAL, p_url, l_blob);
-- Relase the resources associated with the temporary LOB.
DBMS_LOB.freetemporary(l_blob);
EXCEPTION
WHEN OTHERS THEN
UTL_HTTP.end_response(l_http_response);
DBMS_LOB.freetemporary(l_blob);
RAISE;
END load_binary_from_url;
/
Create Table for image
CREATE TABLE BLOB_TEST (BL BLOB);
Procedure
CREATE OR REPLACE PROCEDURE LOAD_IMAGE_FROM_URL (P_URL IN VARCHAR2) AS
V_BLOB BLOB;
BEGIN
V_BLOB := HTTPURITYPE.CREATEURI(P_URL).GETBLOB();
INSERT INTO BLOB_TEST VALUES (V_BLOB);
EXCEPTION
WHEN OTHERS THEN
NULL;
END LOAD_IMAGE_FROM_URL;
SELECT * FROM BLOB_TEST;

Format a date according to ADO provider

I have a Delphi 2010 application using ADO to support a database that can be either SQL Server or MS Access. When sending SQL to the database using parameterized queries differences in date representation are handled correctly. But I occasionally have the need to form dynamic SQL and send that to the database as well.
Is there any way to either have the TADOConnection format my date into a text string appropriate for the current database, or to interrogate the connection to learn how I should format the date? Otherwise I'm stuck building a table of provider names and date formatting functions.
You should be able to use parameters with dynamic sql as well. I have done this in several versions of my own OPF framework.
Just write a SQL statement using parameters, assign that as a string to the SQL text of a TAdoQuery (or TAdoCommand). The component should then parse your text and set up the parameters collections for you. After that you should be able to assign the values to your parameters and call Open or Execute...
To give you an idea:
DS := DatasetClass.Create( self.Connection );
try
DS.QueryText := SQL.Text;
FillSelectParams( DS );
DS.Open;
try
...
finally
DS.Close;
end;
finally
DS.Free;
end;
In which the FillSelectParams calls the following FillParams procedure:
procedure TSQLDataManager.FillParams(ADS: TCustomDataset);
var
i: integer;
ParamName: string;
Attr: TCustomDomainAttribute;
Ref: TCustomDomainObject;
Value: Variant;
begin
for i := 0 to ADS.ParamCount - 1 do begin
Value := Null;
ParamName := ADS.ParamName[i];
if ParamName = 'Id' then begin
ParamName := 'Identity';
end;
Attr := CDO.AttrByName[ParamName];
if Attr <> nil then begin
Value := ADS.AdjustParamValue( Attr );
end else begin
Ref := CDO.ReferenceByName[ParamName];
if ( Ref <> nil ) and ( Ref.Identity <> C_UnassignedIdentity ) then begin
Value := Ref.Identity;
end;
end;
if Value <> Null then begin
ADS.ParamValue[i] := Value;
end;
end;
end;
In this case the param values are taken from a custom domain object (CDO), but you can substitute your own flavour here.
The AdjustParamValue function takes care of a couple of conversions. It has been implemented in the ADO version of the TCustomDataSet class descendant used, to take care of component variations with different TDataSet descendants, but nowhere does the SQL database type come into play:
function TADODCDataset.AdjustParamValue(Attr: TCustomDomainAttribute): Variant;
begin
if Attr is TIdentityAttribute then begin
if Attr.AsInteger = 0 then begin
Result := Null;
end else begin
Result := Attr.Value;
end;
end else if Attr is TBooleanAttribute then begin
if Attr.AsBoolean then begin
Result := Integer( -1 );
end else begin
Result := Integer( 0 );
end;
end else if Attr is TDateTimeAttribute then begin
if Attr.AsDateTime = 0 then begin
Result := Null;
end else begin
Result := Attr.Value;
end;
end else if Attr is TEnumAttribute then begin
Result := Attr.AsString
end else begin
Result := Attr.Value;
end;
end;
Larry, i will give you an answer for the SQL Server Part.
you can use the sys.syslanguages system view to get information about the languages installed in the sql server, one of the columns returned by this view is called dateformat which indicate the Date order, for example, DMY.
also using the ##langid (which returns the local language identifier (ID) of the language that is currently being used.) function you can write something like this to obtain the current date format used by the Sql Server.
select dateformat from sys.syslanguages where langid=##langid
so now you will have a string which you can parse in delphi to format your date.
Another option is pick one of the predefined formats of SQL Server and use the CONVERT function in your SQL sentence for convert the string to the date.
check this sample which uses the ISO format to convert
//The ISO format is yyyymmdd so i can use the FormatDateTime function to convert any TdateTime to a Iso format sdatetiem string
DateStr:=FormatDateTime('YYYYMMDD',Now);
//Now construct the sql server sentence
SqlSentence:=Format('UPDATE MyTable SET DateField=CONVERT(DATETIME,%s,112)',QuotedStr(DateStr));
Try to use the ODBC date escape sequence, which may be supported by SQL Server and Access OLEDB providers. For example:
{d '2011-01-14'}
As Marjan suggests, you shoud always use parameters. (try searching for SQL-injection)
Another reason to use parameters is that query plans can be reused on the sql server.
If the first statement generated is SELECT * from customer where created>'2010-12-21' and the next statetement generated is SELECT * from customer where created>'2010-12-22', the query optimizer/plan gets generated/compiled both times (different statement). But if the statement both times are SELECT * from customer where created>?, the plan is reused -> (slightly) lower presure on SQL server.
I you just wan't a quick and dirty solution all the SQL (implementations) i have tried (I havn't tries Access) can accept and understand the ISO date format (eg. '2010-12-21')

Resources