How to use a FireDAC TFDConnection to iterate through tables, fields and field definitions - sqlite

I am teaching myself to use SQLite and FireDAC together in Delphi. I am not very experienced with the latest incarnations of databases and tools so after writing a very simple application to display a single table from an SQLite file, I decided that I would put together a simple viewer 'frame' that would help me learn and maybe give me (eventually) a debugging tool to put in my Application for engineering use.
So, I've used a simple TTreeView and I wish to populate it with a hierarchy of 'databases' (catalogues?), 'tables', 'field names' and 'field types'. So far it has been remarkably easy to list the catalogues, tables and fields (using TFDConnection.Getxxxxx) but I cant see how to go deeper to get field definitions. Is this possible to do from a TFDConnection? Or do I need to open a temporary query?
My existing code is shown below and my 'field types' would be a further nested loop when shown as '// xxxxxxxxxxxxxxxxxxx'
procedure TForm1.Button1Click(Sender: TObject);
procedure DatabaseToTreeView( AConnection : TFDConnection; ATreeView : TTreeView );
procedure ProcessConnection;
procedure ProcessCatalogueName( const ACatalogueName : string; ARoot : TTreeNode );
procedure ProcessTableName( const ATableName : string; ARoot : TTreeNode );
var
List : TStrings;
{Node : TTreeNode;}
I : integer;
begin
List := TStringList.Create;
try
AConnection.GetFieldNames( ACatalogueName, '', ATableName, '', List );
for I := 0 to List.Count-1 do
begin
{Node := }ATreeView.Items.AddChild( ARoot, List[I] );
// xxxxxxxxxxxxxxxxxxx
end;
finally
List.Free;
end;
end;
var
List : TStrings;
Node : TTreeNode;
I : integer;
begin
List := TStringList.Create;
try
AConnection.GetTableNames( ACatalogueName, '', '', List );
for I := 0 to List.Count-1 do
begin
Node := ATreeView.Items.AddChild( ARoot, List[I] );
ProcessTableName( List[I], Node );
end;
finally
List.Free;
end;
end;
var
List : TStrings;
Node : TTreeNode;
I : integer;
begin
List := TStringList.Create;
try
AConnection.GetCatalogNames( '', List );
if List.Count = 0 then
ProcessCatalogueName( '', nil )
else
for I := 0 to List.Count-1 do
begin
Node := ATreeView.Items.AddChild( nil, List[I] );
ProcessCatalogueName( List[I], Node );
end;
finally
List.Free;
end;
end;
begin
ATreeView.Items.Clear;
ATreeView.Items.BeginUpdate;
try
ProcessConnection;
finally
ATreeView.Items.EndUpdate;
end;
end;
begin
FDConnection1.Open;
FDQuery1.Active := true;
DatabaseToTreeView( FDConnection1, TreeView1 );
end;
Many thanks, Brian.

One solution is to instantiate a TFDTable, connect it to AConnection and call FieldDefs.Update. This won't fetch any data.

Use the TFDMetaInfoQuery component. It is unified for getting metadata information, so it's usable with any kind of supported DBMS. It populates metadata resultsets by the requested MetaInfoKind and given DBMS object description.

Instead of using a temporary query with a false condition to get table schema without any data (such as "select * from tablename where 1=0" - I assume that is what you meant), depending on your database, you could also use a query to get table information. Like;
For MySQL:
show columns from tablename;
For SQLite:
PRAGMA table_info(tablename)
For MS SQL Server:
select column_name, data_type, character_maximum_length from INFORMATION_SCHEMA.COLUMNS where table_name = 'tablename';
I believe PostgreSQL also has such a function, however I dont have a pgsql installation handy at this time.

Related

Update statement works in normal mode, but not in procedure

I want to upload image from directory to database blob field. For those reason I write this code. Code alone works well but is not working as a procedure.
What is the problem ? I cannot understand. Here is the code:
DECLARE
dest_loc BLOB;
src_loc BFILE;
BEGIN
src_loc:= BFILENAME('ALL_IMG_DIR','SDFGASDF1544.jpg');
DBMS_LOB.FILEOPEN(src_loc);
DBMS_LOB.CREATETEMPORARY(dest_loc,true);
DBMS_LOB.LOADFROMFILE(dest_lob => dest_loc, src_lob => src_loc,amount=>dbms_lob.getlength(src_loc) );
UPDATE STUDENT
SET IMAGE=dest_loc
WHERE
REG_CODE = 'SDFGASDF1544';
DBMS_LOB.CLOSE(src_loc);
end;
But when I write this code as a procedure, like
CREATE OR REPLACE PROCEDURE img_to_blob_student(Vreg_code varchar2)
is
dest_loc BLOB;
src_loc BFILE;
BEGIN
src_loc := BFILENAME('ALL_IMG_DIR','SDFGASDF1544.jpg');
DBMS_LOB.FILEOPEN(src_loc);
DBMS_LOB.CREATETEMPORARY(dest_loc,true);
DBMS_LOB.LOADFROMFILE(
dest_lob => dest_loc,
src_lob => src_loc,
amount=>dbms_lob.getlength(src_loc)
);
UPDATE STUDENT
SET IMAGE=dest_loc
WHERE REG_CODE = 'SDFGASDF1544';
DBMS_LOB.CLOSE(src_loc);
end;
And call like
img_to_blob_student('123');
I get
ERROR IS: `ORA-00900: invalid SQL statement in procedure`
to call the procedure, did you use the execstatement?
exec img_to_blob_student('123');

How can I create a new SQLite file and table at runtime using FieldDefs?

I'm using Delphi Seattle to create a brand new table in a brand new SQLite file and using only FieldDefs and non-visual code. I can create a table using the ExecSQL ('CREATE TABLE....' ) syntax but not as shown below (I get 'No such table 'MyTable' which is raised when I execute the CreateDataSet call). I'd like some solution that allows me to work with FieldDefs. This code is modelled on the example here. I notice though, that there is note regarding CreateDataSet that it only applies to TFDMemTable. Is there a runtime way of creating an SQLite table without using ExecSQL?
procedure Test;
const
MyDBFile = 'c:\scratch\hope.db';
var
Connection : TFDConnection;
DriverLink : TFDPhysSQLiteDriverLink;
Table : TFDTable;
begin
DeleteFile( MyDBFile );
DriverLink := TFDPhysSQLiteDriverLink.Create( nil );
Connection := TFDConnection.Create( nil );
try
Connection.Params.Values['DriverID'] := 'SQLite';
Connection.Params.Values['Database'] := MyDBFile;
Connection.Connected := True;
Table := TFDTable.Create( nil );
try
Table.TableName := 'MyTable';
Table.Connection := Connection;
Table.FieldDefs.Add( 'one', ftString, 20 );
Table.FieldDefs.Add( 'two', ftString, 20 );
Table.CreateDataSet;
// I would add records here....
finally
Table.Free;
end;
finally
Connection.Free;
DriverLink.Free;
end;
end;
CreateDataSet is usually a local operation for initializing a client-side dataset into an empty state. If TClientDataSet is anything to go by, afaik it cannot be used create a server-side table.
To create an actual server table, I would expect to have to construct the DDL SQL to create the table and then execute it using ExecSQL on the (client-side) dataset, as you have already tried.
update
The following seems to satisfy your requirement to do everything in code, though using a TFDTable component, which doesn't surface FieldDefs, so I've used code-created TFields instead. Tested in D10 Seattle.
procedure TForm3.CreateDatabaseAndTable;
const
DBName = 'd:\delphi\code\sqlite\atest.sqlite';
var
AField : TField;
begin
if FileExists(DBName) then
DeleteFile(DBName);
AField := TLargeIntField.Create(Self);
AField.Name := 'IDField';
AField.FieldName := 'ID';
AField.DataSet := FDTable1;
AField := TWideStringField.Create(Self);
AField.Size := 80;
AField.Name := 'NameField';
AField.FieldName := 'Name';
AField.DataSet := FDTable1;
FDConnection1.Params.Values['database'] := DBName;
FDConnection1.Connected:= True;
FDTable1.TableName := 'MyTable';
FDTable1.CreateTable(False, [tpTable]);
FDTable1.Open();
FDTable1.InsertRecord([1, 'First']);
FDConnection1.Commit;
FDConnection1.Connected:= False;
end;
I expect that someone a bit more familiar than I am could do similar using a TFDMemTable's FieldDefs if it were correctly connected to a server-side component (FDCommand?) via an FDTableAdaptor.
Fwiw, I've used a LargeInt ID field and WideString Name field because trying to use Sqlite with D7 a while back, I had no end of trouble trying to use Integer and string fields.
Btw, you if you know the structure you require in advance of deployment, you might find that you get more predictable/robust results if you simply copy an empty database + table into place, rather than try and create the table in situ. Ymmv, of course.
I would NEVER dream of creating database tables using fielddefs because you wind up having tables without a proper primary key, indexes and referential integrity. The resulting tables are totally "dumbed down".
Whenever you have a "where" clause in a query the database would do a full table scan to find the records matching the query. So your database slows down (and CPU use increases) with size. That's just bad design.
Regards,
Arthur
You can use the app SQLite Expert Professional, create SQLite database.
And using FDConnection connect to the database. And use it.
Method to database SQLite, the same way that MartynA have said.
Begin
FDConnection1.Connected:=false;
FDConnection1.Params.Clear;
FDConnection1.Params.Database:='D:\SQLiteDatabase.db';
FDConnection1.ConnectionDefName:='SQLite_Demo';
FDConnection1.DriverName:='SQLite';
FDConnection1.Connected:=true;
FDTable1.Open;
End;

how to get file size or object size in oracle

I have an table contain three columns ID,Obj_name,Object in a table. Object refers to metadata/File which is located in folder. How can write a script to check what is the file size of each object.
Output like
ID,Obj_name,Object,File_size.
let me know if there is any idea.
Try this :
DECLARE
v_fexists BOOLEAN;
v_file_length NUMBER;
v_block_size BINARY_INTEGER;
BEGIN
UTL_FILE.FGETATTR
('NFS_DIR', 'west.txt', v_fexists, v_file_length,
v_block_size);
DBMS_OUTPUT.PUT_LINE (v_file_length);
END;
Since object is a bfile, you can do something like
CREATE OR REPLACE FUNCTION( p_id IN INTEGER )
RETURN INTEGER
IS
l_bfile bfile;
l_length integer;
BEGIN
SELECT object
INTO l_bfile
FROM your_table
WHERE id = p_id;
DBMS_LOB.OPEN(l_bfile, DBMS_LOB.LOB_READONLY);
/* Get the length of the LOB: */
l_length := DBMS_LOB.GETLENGTH(l_bfile);
DBMS_LOB.CLOSE(l_bfile);
RETURN l_length;
END;
and then call that function from your query passing in the id. Note that this example is taken directly from the documentation on LOBs

multiset union distinct gives "wrong number of types or arguments passed" error

I am using a for loop to pass different values to a cursor, bulk collect the data and append it to the same nested table using MULTISET UNION operator. However, to avoid duplicate data, i tried to use MULTISET UNION DISTINCT and it throws the error PLS-00306: wrong number or types of arguments in call to 'MULTISET_UNION_DISTINCT' The codes works well without DISTINCT. Please let me know if i'm missing anything here.
I'm using Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
My code is as follows:
DECLARE
TYPE t_search_rec
IS
RECORD
(
search_id VARCHAR2(200),
search_name VARCHAR2(240),
description VARCHAR2(240) );
TYPE t_search_data
IS
TABLE OF t_search_rec;
in_user_id NUMBER;
in_user_role VARCHAR2(20);
in_period VARCHAR2(20) := 'Sep-15';
in_search_string VARCHAR2(20) := 'v';
l_search_tt t_search_data;
x_search_tt t_search_data;
v_entity_gl VA_LOGIN_API.t_entity_list ;
X_RETURN_CODE VARCHAR2(20);
X_RETURN_MSG VARCHAR2(2000);
CURSOR c_vendors_gl(v_entity VARCHAR2)
IS
SELECT UNIQUE vendor_id,
vendor,
NULL description
FROM XXPOADASH.XX_VA_PO_LINES
WHERE period = in_period
AND entity = v_entity
AND upper(vendor) LIKE upper(in_search_string)
||'%';
BEGIN
in_user_role := 'ROLE';
in_user_id := 4359;
VA_LOGIN_API.DATA_ACCESS_PROC( X_RETURN_CODE => X_RETURN_CODE, X_RETURN_MSG => X_RETURN_MSG, IN_PERSON_ID => in_user_id, IN_PERSON_ROLE => in_user_role, X_ACCESSED_ENTITY_LIST => v_entity_gl );
IF( v_entity_gl.COUNT >0) THEN
x_search_tt := t_search_data();
FOR I IN v_entity_gl.FIRST..v_entity_gl.COUNT
LOOP
OPEN c_vendors_gl(v_entity_gl(i));
FETCH c_vendors_gl BULK COLLECT INTO l_search_tt;
CLOSE c_vendors_gl;
x_search_tt := x_search_tt MULTISET
UNION DISTINCT l_search_tt;
l_search_tt.delete;
END LOOP;
IF x_search_tt.count = 0 THEN
x_return_msg := 'No lines found';
END IF;
END IF;
DBMS_OUTPUT.PUT_LINE(x_return_msg);
DBMS_OUTPUT.PUT_LINE(x_search_tt.count);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(x_return_msg);
DBMS_OUTPUT.PUT_LINE(x_search_tt.count);
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
multiset union distinct requires the elements of the collection to be comparable. In your case the elements are PL/SQL records that are unfortunately not comparable data structures (i.e. PL/SQL provides no build-in mechanism to compare PL/SQL records).
multiset union works because it doesn't need to compare the elements.
One possible workaround is to use Oracle object type instead of PL/SQL record. Object type allows you to implement a comparison method required by multiset union distinct.

SQLite insert on android works only once

I'm with a problem on Delphi, i create a simple app to test the mobile power of RadStudio, I created a simple app that put some data into some inputs and then add it to database when button is clicked. I followed this Embarcadero tutorial as starting point
The problem is that I only get one entry added, then no more entries are added or the list is not refreshed. Below some code:
Table creation:
procedure TTabbedForm.logAfterConnect(Sender: TObject);
begin
log.ExecuteDirect('CREATE TABLE IF NOT EXISTS lista (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,idCons INTEGER,nome TEXT,kms INTEGER,kmsAlarme INTEGER,quantidade INTEGER,quantidadeAlarme INTEGER,data INTEGER,dataAlarme INTEGER,alarmeMsg TEXT)');
end;
Add button code:
procedure TTabbedForm.btnGravarClick(Sender: TObject);
begin
try
SQLQueryInsert.ParamByName('idCons').AsInteger := PopupBoxTipo.ItemIndex;
SQLQueryInsert.ParamByName('nome').AsString := PopupBoxTipo.Text;
SQLQueryInsert.ParamByName('kms').AsInteger := StrToInt(kmsEdit.Text);
SQLQueryInsert.ParamByName('quantidade').AsInteger := StrToInt(qtdEdit.Text);
SQLQueryInsert.ParamByName('data').AsInteger := DateTimeToUnix(dtaEvento.Date);
SQLQueryInsert.ExecSQL();
lista.Refresh;
LinkFillControlToField1.BindList.FillList;
except
on e: Exception do
begin
ShowMessage(e.Message);
end;
end;
end;
If you need some more code snippet, please ask!
Thanks in advance for any reply!
Try this method it works for me
procedure TData.InsertItem(someObject: TSomeObjectClass);
var
qry: TFDQuery;
begin
qry := CreateQry( 'insert into SomeObject(id, description, something)'+
' values (:id, :description, :something);', false);
qry.Params.ParamByName('id').AsInteger := someObject.id;
qry.Params.ParamByName('description').asstring := someObject.description;
qry.Params.ParamByName('something').asstring := someObject.something;
qry.Prepare;
qry.execsql;
qry.Free;
end;
I've put and object in the parameter but you can also put the data you want to insert seperatly

Resources