Page 1 of 1
Metadata: mdTables
Posted: 05.12.2007, 11:02
by Roaster
Hi,
I'm currently trying to fetch the tables metadata of a Firebird 2.x database. Especially I'm interested in the description of each table I've added before.
When using a tool like FlameRobin or SQl Manager 2005 Lite to look at the description of each table I can see the correct text.
However when using TZSQLMetadata, setting MetadataType to mdTables, it returns all available tables and a column REMARKS which is empty in any case.
How can I access this column and read the description? Of course I could directly read the system table rdb$relations and use column rdb$description but it would be more convenient to use TZSQLMetadata instead.
It may be worth noting that I do get the remarks/description of MetadataType mdColumns.
So I guess it is just an issue with type mdTables.
Posted: 05.12.2007, 11:38
by mdaems
Hi Roaster...
Have a look at the code below. It seems this code only fills 2 fields on the result dataset.
I think with a little effort you can complete these?
Please make sure the columns you use from the system tables are available in Interbase and older firebird versions too... If not, could you tell what's only available in which version, so the queries can be made driver dependent?
If you can send me a reasonable patch, it will be applied to the standard zeos distribution.
Form ZDbcMetadata.pas:
Code: Select all
TableColumns: array[1..TableColumnCount]
of TZMetadataColumnDef =(
(Name: 'TABLE_CAT'; SQLType: stString; Length: 255),
(Name: 'TABLE_SCHEM'; SQLType: stString; Length: 255),
(Name: 'TABLE_NAME'; SQLType: stString; Length: 255),
(Name: 'TABLE_TYPE'; SQLType: stString; Length: 255),
(Name: 'REMARKS'; SQLType: stString; Length: 255)
);
From ZDbcInterbase6.pas :
Code: Select all
function TZInterbase6DatabaseMetadata.GetTables(const Catalog: string;
const SchemaPattern: string; const TableNamePattern: string;
const Types: TStringDynArray): IZResultSet;
var
Key: string;
SQL, TableType: string;
LTableNamePattern: string;
BLR: IZBlob;
I, SystemFlag, ViewContext: Integer;
begin
Key := GetTablesMetaDataCacheKey(Catalog,SchemaPattern,TableNamePattern,Types);
Result := GetResultSetFromCache(Key);
if Result = nil then
begin
Result := ConstructVirtualResultSet(TableColumnsDynArray);
LTableNamePattern := ConstructNameCondition(TableNamePattern,
'a.RDB$RELATION_NAME');
SQL := 'SELECT DISTINCT a.RDB$RELATION_NAME, b.RDB$SYSTEM_FLAG,'
+ ' b.RDB$VIEW_CONTEXT, a.RDB$VIEW_SOURCE FROM RDB$RELATIONS a'
+ ' JOIN RDB$RELATION_FIELDS b ON a.RDB$RELATION_NAME'
+ '=b.RDB$RELATION_NAME';
if LTableNamePattern <> '' then
SQL := SQL + ' WHERE ' + LTableNamePattern;
with GetConnection.CreateStatement.ExecuteQuery(SQL) do
begin
while Next do
begin
SystemFlag := GetIntByName('RDB$SYSTEM_FLAG');
ViewContext := GetIntByName('RDB$VIEW_CONTEXT');
if SystemFlag = 0 then
begin
if ViewContext = 0 then
BLR := GetBlobByName('RDB$VIEW_SOURCE');
if BLR.IsEmpty then
TableType := 'TABLE'
else TableType := 'VIEW';
end else
TableType := 'SYSTEM TABLE';
if Length(Types) = 0 then
begin
Result.MoveToInsertRow;
Result.UpdateNull(1);
Result.UpdateNull(2);
Result.UpdateString(3, GetStringByName('RDB$RELATION_NAME'));
Result.UpdateString(4, TableType);
Result.UpdateNull(5);
Result.InsertRow;
end
else begin
for I := 0 to High(Types) do
begin
if Types[I] = TableType then
begin
Result.MoveToInsertRow;
Result.UpdateNull(1);
Result.UpdateNull(2);
Result.UpdateString(3, GetStringByName('RDB$RELATION_NAME'));
Result.UpdateString(4, TableType);
Result.UpdateNull(5);
Result.InsertRow;
end;
end;
end;
end;
Close;
end;
AddResultSetToCache(Key, Result);
end;
end;
Posted: 05.12.2007, 13:08
by Roaster
Thanks mdaems,
I appreciate your help very much and I already found those lines but needed confirmation about possible changes.
Anyway it seems to be unit ZDbcInterbase6Metadata.pas and changed three lines commented in the code below.
I cannot speak for Interbase databases though, for this reason someone who is able to test should jump in and test those changes. OTH when looking at function GetColumns I noticed that at least Interbase 5 knows about RDB$DESCRIPTION, too.
Code: Select all
function TZInterbase6DatabaseMetadata.GetTables(const Catalog: string;
const SchemaPattern: string; const TableNamePattern: string;
const Types: TStringDynArray): IZResultSet;
var
Key: string;
SQL, TableType: string;
LTableNamePattern: string;
BLR: IZBlob;
I, SystemFlag, ViewContext: Integer;
begin
Key := GetTablesMetaDataCacheKey(Catalog,SchemaPattern,TableNamePattern,Types);
Result := GetResultSetFromCache(Key);
if Result = nil then
begin
Result := ConstructVirtualResultSet(TableColumnsDynArray);
LTableNamePattern := ConstructNameCondition(TableNamePattern,
'a.RDB$RELATION_NAME');
SQL := 'SELECT DISTINCT a.RDB$RELATION_NAME, b.RDB$SYSTEM_FLAG,'
// + ' b.RDB$VIEW_CONTEXT, a.RDB$VIEW_SOURCE FROM RDB$RELATIONS a'
// Added RDB$DESCRIPTION in result set //
+ ' b.RDB$VIEW_CONTEXT, a.RDB$VIEW_SOURCE, a.RDB$DESCRIPTION FROM RDB$RELATIONS a'
+ ' JOIN RDB$RELATION_FIELDS b ON a.RDB$RELATION_NAME'
+ '=b.RDB$RELATION_NAME';
if LTableNamePattern <> '' then
SQL := SQL + ' WHERE ' + LTableNamePattern;
with GetConnection.CreateStatement.ExecuteQuery(SQL) do
begin
while Next do
begin
SystemFlag := GetIntByName('RDB$SYSTEM_FLAG');
ViewContext := GetIntByName('RDB$VIEW_CONTEXT');
if SystemFlag = 0 then
begin
if ViewContext = 0 then
BLR := GetBlobByName('RDB$VIEW_SOURCE');
if BLR.IsEmpty then
TableType := 'TABLE'
else TableType := 'VIEW';
end else
TableType := 'SYSTEM TABLE';
if Length(Types) = 0 then
begin
Result.MoveToInsertRow;
Result.UpdateNull(1);
Result.UpdateNull(2);
Result.UpdateString(3, GetStringByName('RDB$RELATION_NAME'));
Result.UpdateString(4, TableType);
// Result.UpdateNull(5);
// Changed this line
Result.UpdateString(5, GetStringByName('RDB$DESCRIPTION'));
Result.InsertRow;
end
else begin
for I := 0 to High(Types) do
begin
if Types[I] = TableType then
begin
Result.MoveToInsertRow;
Result.UpdateNull(1);
Result.UpdateNull(2);
Result.UpdateString(3, GetStringByName('RDB$RELATION_NAME'));
Result.UpdateString(4, TableType);
// Result.UpdateNull(5);
// Changed this line
Result.UpdateString(5, GetStringByName('RDB$DESCRIPTION'));
Result.InsertRow;
end;
end;
end;
end;
Close;
end;
AddResultSetToCache(Key, Result);
end;
end;
Also I notice that RDB$DESCRIPTION is actually defined as type blob in the Firebird database but the result set field REMARKS is just able to hold up to 255 characters:
Code: Select all
TableColumns: array[1..TableColumnCount]
of TZMetadataColumnDef =(
(Name: 'TABLE_CAT'; SQLType: stString; Length: 255),
(Name: 'TABLE_SCHEM'; SQLType: stString; Length: 255),
(Name: 'TABLE_NAME'; SQLType: stString; Length: 255),
(Name: 'TABLE_TYPE'; SQLType: stString; Length: 255),
(Name: 'REMARKS'; SQLType: stString; Length: 255) <<< Possible conflict
);
So there might by a conflict later. This seems to be also true for fetching the descriptions when using type mdColumns, too.
cu,
Michael
Posted: 05.12.2007, 14:08
by Roaster
Hmm,
something strange happens. I added those changes mentioned above. Now when manually creating a TZSQLMetadata object it does not return anything from the REMARKS field.
Code: Select all
function TDBDescription.DBFetchTableMetaData(TableName : string) : string;
// Fetch the tables metadata from the database; returns the tables description
var
ZMetadata : TZSQLMetadata;
begin
Result := '';
ZMetaData := TZSQLMetadata.Create(Nil);
try
ZMetadata.Connection := FConnection;
ZMetadata.MetadataType := mdTables;
ZMetadata.TableName := TableName;
ZMetadata.Active := True;
if (ZMetadata.RecordCount > 0) and (ZMetadata.FindField('REMARKS') <> Nil) then
begin
ZMetadata.First;
Result := ZMetadata.FieldByName('REMARKS').AsString; <<< Is always empty
end;
finally
ZMetadata.Active := False;
FreeAndNil(ZMetaData);
end;
end;
It even does not stop at any breakpoint in unit ZDbcInterbase6Metadata.pas where I made those changes, so what's the deal?
Btw I guess I've found a little bug in unit ZDbcInterbase6. The statement 'CRATE DATABSE...' has got two typos. I will file a Mantis report.
Code: Select all
procedure TZInterbase6Connection.Open;
var
DPB: PChar;
FDPBLength: Word;
DBName: array[0..512] of Char;
begin
if not Closed then Exit;
if TransactIsolationLevel = tiReadUncommitted then
raise EZSQLException.Create('Isolation level do not capable');
DPB := GenerateDPB(Info, FDPBLength, FDialect);
if HostName <> '' then
begin
if Port <> 3050 then
StrPCopy(DBName, HostName + '/' + IntToStr(Port) + ':' + Database)
else
StrPCopy(DBName, HostName + ':' + Database)
end else
StrPCopy(DBName, Database);
try
{ Create new db if needed }
if Info.Values['createNewDatabase'] <> '' then
begin
CreateNewDatabase(Info.Values['createNewDatabase']);
{ Logging connection action }
DriverManager.LogMessage(lcConnect, FPlainDriver.GetProtocol,
Format('CRATE DATABSE "%s" AS USER "%s"', [Info.Values['createNewDatabase'], User]));
end;
Done:
Mantis Report 79
SOLVED: I had to recompile some more (ZEOS) units to retrieve the REMARKS content again.
Posted: 05.12.2007, 15:39
by mdaems
Thanks for the bug report! As it's just a typo in a log message, that won't take long to fix!
Concerning your patch above : that one will be applied as wel... What about adding Copy(...,1,255) ? (No LeftStr, because it's not available in D5)
My advice when changing things to Zeoslib : before testing build the changed package and all 'higher' packages before reinstalling the components.
Sequence : ZCore, ZParseSql, ZPlain, ZDbc, ZComponent and ZComponentDesign. This means : for a Zdbc change you have to compile ZDbc, ZComponent and ComponentDesign.
I suppose you figured this out already, but maybe this information helps somebody else.
Posted: 05.12.2007, 21:36
by Roaster
mdaems wrote:Concerning your patch above : that one will be applied as wel... What about adding Copy(...,1,255) ? (No LeftStr, because it's not available in D5)
Thanks for your answer and patch! when mentioning the string limitation of 255 characters I just meant that the Firebird database seems to be capable of holding more than those 255 characters in the description BLOB field.
I guess when using GetStringByName('RDB$DESCRIPTION') this will just fill the REMARKS field up to 255 characters and not more, simply because there is no more space left.
But what happens if RDB$DESCRIPTION has got more than 255 characters? At the moment I guess it will be cut off.
Is this intended?
Posted: 06.12.2007, 10:00
by mdaems
Intended... No idea.
I suppose this limit could be lifted. But that probably impacts the way these data are schown by components. Now the data is visible in a simple grid. Making it a blob field probably changes this behaviour. So it could be a feature request and eventually be added to a future release. I will not add it now as I'm busy creating the 6.6.2-rc version and we can't change behaviour now.
Concerning the copy(...,1,255) : you're probably right... I'm mainly programming in PL/SQL and there you get exeptions when putting a big string in a small variable. But you must admit : now it will always be clear that truncation occurs. (It's already in SVN, I will not remove it unless there are problems)
Mark
Posted: 06.12.2007, 11:41
by Roaster
mdaems wrote:Concerning the copy(...,1,255) : you're probably right... I'm mainly programming in PL/SQL and there you get exeptions when putting a big string in a small variable. But you must admit : now it will always be clear that truncation occurs. (It's already in SVN, I will not remove it unless there are problems)
Mark,
of course it's probably clearer to read and to understand, if using a truncating Copy but I took that idea from other coding where a limitation exists, too, and no Copy function was used either. So I liked the idea of being code-conform.
Thanks for adding this to SVN.
P.S when will you expect a 6.6.2-rc release?
Posted: 06.12.2007, 16:22
by mdaems
Actually, I'm writing the ant script now, starting from the SVN release scripts. Shouldn't take too long, but I do it at home when I have the time, so it all depends on wife, kids, family, hobbies, ... So in a few weeks should be a reasonable timing. Looking for a Christmas gift
?
There's also the dilemma about what to add. Your opinion?
- binaries (bpl's) -->not all Delphi versions available (and what if I would install 'not official' versions, would the result be legal?)
- outdated documentation
- doxygen class information
- dll's for the databases. Which are legal? What to include to avoid licence trouble?
- build scripts
- test suite
Mark