Page 1 of 2

Metadata and Stored Procedures

Posted: 03.02.2008, 11:20
by sandeep_c24
I am trying to retrieve the source code for Stored procedures using Zeos metadata component but it does not look like it is fetched. Am I missing something or has it nor been implemented yet?

If it has not been implemented can anyone help me implement this?

Regards

Sandeep

Posted: 03.02.2008, 13:21
by mdaems
Database?

Posted: 03.02.2008, 20:27
by sandeep_c24
I would like to do that for Firebird, Interbase, Postgres, SQLite, MySQL, MSSQL and others if possible.

Regards

Sandeep

Posted: 04.02.2008, 08:15
by sandeep_c24
Types/Domains are also not working. Can anything be done about them as well?

Regards

Sandeep

Posted: 04.02.2008, 11:58
by mdaems
Hi Sandeep,

Adding the procedure source to the StoredProcedure metadata may be fairly easy as the framework is already available. You could even reuse one of the 3 reserved fields that are available already.
Main problem may be the datatype of the column. I have the impression that until now all metadata columns are limited to 255 char strings or numeric types.

Just have a look at the ZDbcMetaData.pas and ZDbc<database>Metadata.pas files.

Concerning the other metadatatypes you are asking for : adding/modifying should be piece of cake for the person who implements the procedure sources as the code is very modular. The more, I've seen the 'TypeInfo'.

As you might know by now : good and safe contributions can be quickly integrated when posted in the 'User patches', 'User contributions' and 'Feature Requests' forums. For bug fixes, please use the bug tracker. Please keep bug fixes separated from new features. These have a different priority.

Mark

Posted: 07.02.2008, 10:31
by sandeep_c24
I have modified the code in ZDbcInterbase6Metadata.pas to display the TYPE_NAME and COLUMN_SIZE correctly. Please check the following and merge if it looks ok.

Please also look at FIELD_SUB_TYPE and suggest how to handle SUB_TYPE 0.

Please replace lines 1685 to 1690 with following lines.

Code: Select all

          Ord(ConvertInterbase6ToSqlType(TypeName, SubTypeName))); //DATA_TYPE
        // SC  - TYPE_NAME
        // !!!!!!!!!!!!!!!!!!!! Not Sure what FIELD_SUB_TYPE 0 is !!!!!!!!!!!!!!!!!!!!
        if  (TypeName = 16) then
        begin
          if (SubTypeName = 0) then
            Result.UpdateString(6, 'NULL');
          if (SubTypeName = 1) then
            Result.UpdateString(6, 'NUMERIC');
          if (SubTypeName = 2) then
            Result.UpdateString(6, 'DOUBLE');
        end
        else
        // SC
        Result.UpdateString(6,GetStringByName('RDB$TYPE_NAME'));    //TYPE_NAME
        
        // SC
        // !!!!!!!!!!!!!!!!!!!! Not Sure what FIELD_SUB_TYPE 0 is !!!!!!!!!!!!!!!!!!!!
        if  (TypeName = 16 )
        and (SubTypeName in [0, 1, 2]) then
          Result.UpdateInt(7, GetIntByName('RDB$FIELD_PRECISION')) //COLUMN_SIZE
        else
        // SC
        Result.UpdateInt(7, GetIntByName('RDB$FIELD_LENGTH'));    //COLUMN_SIZE
        
        Result.UpdateNull(8);    //BUFFER_LENGTH
Regards

Sandeep

Posted: 07.02.2008, 14:31
by mdaems
Thanks for the patch. I hope you can answer following questions so patching may happen quickly.

Did a quick check here
http://www.felix-colibri.com/papers/db/ ... ables.html

I have some questions left.
Why numeric/double instead of numeric/decimal as in the article.
Instead of Result.UpdateString(6, 'NULL') you better use Result.UpdateNull(6), I think.

Reading the article I wonder why you only treat Typename = 16 (and not 7 or 8 ) this way.

Can you demonstrate the difference between field_length, field_precision and field_scale using some samples?

Mark

Posted: 07.02.2008, 22:15
by sandeep_c24
>Why numeric/double instead of numeric/decimal as in the article.
That should be decimal not double, not sure why I typed double.

>Instead of Result.UpdateString(6, 'NULL') you better use Result.UpdateNull(6), I think.
Ok, I'll do that.

>Reading the article I wonder why you only treat Typename = 16 (and not 7 or 8 ) this way.
I was just testing for 16, but I think you are right it should be done for 7 and 8 as well.

>Can you demonstrate the difference between field_length, field_precision and field_scale using some samples?
In Firebird 2.0 Field_Length is 8 (I think) for above types but what I would like to see is Field_Precision and Field_Scale for field of type numeric(14, 4). This is what I get when I use SQLite. I am at office right now and do not have acces to sql command that gets the metadata. I can post it later if you want me to.

Regards

Sandeep

Posted: 07.02.2008, 22:40
by mdaems
Looks reasonable... Can you post the full corrected function, next time? (Easier to patch :) )

I'll ask cipto_kh, our unofficial Firebird specialist for a second opinion about the change. If he agrees and nobody else objects, your patch will be in.

Mark

Posted: 08.02.2008, 10:07
by sandeep_c24
Hi Mark

I have made some more changes to the function. Please have a look and see if they look alright.

Code: Select all

function TZInterbase6DatabaseMetadata.GetColumns(const Catalog: string;
  const SchemaPattern: string; const TableNamePattern: string;
  const ColumnNamePattern: string): IZResultSet;
var
  Key: string;
  SQL, Where, ColumnName, DefaultValue: string;
  TypeName, SubTypeName, FieldScale: integer;
  LTableNamePattern, LColumnNamePattern: string;
begin
  Key := Format('get-columns:%s:%s:%s:%s',
    [Catalog, SchemaPattern, TableNamePattern, ColumnNamePattern]);

  Result := GetResultSetFromCache(Key);
  if Result = nil then
  begin
    Result := ConstructVirtualResultSet(TableColColumnsDynArray);

    LTableNamePattern := ConstructNameCondition(TableNamePattern, 
      'a.RDB$RELATION_NAME');
    LColumnNamePattern := ConstructNameCondition(ColumnNamePattern,
      'a.RDB$FIELD_NAME');

    if StrPos(PChar(ServerVersion), 'Interbase 5') <> nil then
    begin
      SQL := 'SELECT a.RDB$RELATION_NAME, a.RDB$FIELD_NAME, a.RDB$FIELD_POSITION,'
        + ' a.RDB$NULL_FLAG, b. RDB$FIELD_LENGTH, b.RDB$FIELD_SCALE,'
        + ' c.RDB$TYPE_NAME, b.RDB$FIELD_TYPE, b.RDB$FIELD_SUB_TYPE,'
        + ' b.RDB$DESCRIPTION, b.RDB$CHARACTER_LENGTH, b.RDB$FIELD_SCALE'
        + ' as RDB$FIELD_PRECISION, a.RDB$DEFAULT_SOURCE, b.RDB$DEFAULT_SOURCE'
        + ' as RDB$DEFAULT_SOURCE_DOMAIN FROM RDB$RELATION_FIELDS a'
        + ' JOIN RDB$FIELDS b ON (b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE)'
        + ' LEFT JOIN RDB$TYPES c ON b.RDB$FIELD_TYPE = c.RDB$TYPE'
        + ' and c.RDB$FIELD_NAME = ''RDB$FIELD_TYPE''';

      Where := LTableNamePattern;
      if LColumnNamePattern <> '' then
      begin
        if Where = '' then
          Where := LColumnNamePattern
        else Where := Where + ' AND ' + LColumnNamePattern;
      end;
      if Where <> '' then
        Where := ' WHERE ' + Where;

      SQL := SQL + Where + ' ORDER BY a.RDB$RELATION_NAME, a.RDB$FIELD_POSITION';
    end
    else
    begin
      SQL := ' SELECT a.RDB$RELATION_NAME, a.RDB$FIELD_NAME, a.RDB$FIELD_POSITION,'
        + ' a.RDB$NULL_FLAG, a.RDB$DEFAULT_VALUE, b. RDB$FIELD_LENGTH,'
        + ' b.RDB$FIELD_SCALE, c.RDB$TYPE_NAME, b.RDB$FIELD_TYPE,'
        + ' b.RDB$FIELD_SUB_TYPE, b.RDB$DESCRIPTION, b.RDB$CHARACTER_LENGTH,'
        + ' b.RDB$FIELD_PRECISION, a.RDB$DEFAULT_SOURCE, b.RDB$DEFAULT_SOURCE'
        + ' as RDB$DEFAULT_SOURCE_DOMAIN FROM RDB$RELATION_FIELDS a'
        + ' JOIN RDB$FIELDS b ON (b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE)'
        + ' LEFT JOIN RDB$TYPES c ON (b.RDB$FIELD_TYPE = c.RDB$TYPE'
        + ' and c.RDB$FIELD_NAME = ''RDB$FIELD_TYPE'')';

      Where := LTableNamePattern;
      if LColumnNamePattern <> '' then
      begin
        if Where = '' then
          Where := LColumnNamePattern
        else Where := Where + ' AND ' + LColumnNamePattern;
      end;
      if Where <> '' then
        Where := ' WHERE ' + Where;

      SQL := SQL + Where + ' ORDER BY a.RDB$RELATION_NAME, a.RDB$FIELD_POSITION';
    end;

    with GetConnection.CreateStatement.ExecuteQuery(SQL) do
    begin
      while Next do
      begin
        TypeName := GetIntByName('RDB$FIELD_TYPE');
        SubTypeName := GetIntByName('RDB$FIELD_SUB_TYPE');
        FieldScale := GetIntByName('RDB$FIELD_SCALE');
        ColumnName := GetStringByName('RDB$FIELD_NAME');

        DefaultValue := GetStringByName('RDB$DEFAULT_SOURCE');
        if DefaultValue = '' then
          DefaultValue := GetStringByName('RDB$DEFAULT_SOURCE_DOMAIN');
        if StartsWith(Trim(UpperCase(DefaultValue)), 'DEFAULT') then
        begin
          DefaultValue := Trim(Copy(DefaultValue,
            Length('DEFAULT') + 1, Length(DefaultValue)));
        end;

        Result.MoveToInsertRow;
        Result.UpdateNull(1);    //TABLE_CAT
        Result.UpdateNull(2);    //TABLE_SCHEM
        Result.UpdateString(3,
          GetStringByName('RDB$RELATION_NAME'));    //TABLE_NAME
        Result.UpdateString(4, ColumnName);    //COLUMN_NAME
        Result.UpdateInt(5,
          Ord(ConvertInterbase6ToSqlType(TypeName, SubTypeName))); //DATA_TYPE
        // SC - Begin
        // TYPE_NAME
        case TypeName of
          7  : Result.UpdateString(6, 'SMALLINT');
          8  : Result.UpdateString(6, 'INTEGER' );
          16 :
            begin
              if (SubTypeName = 0) then
                Result.UpdateString(6, GetStringByName('RDB$TYPE_NAME'));
              if (SubTypeName = 1) then
                Result.UpdateString(6, 'NUMERIC');
              if (SubTypeName = 2) then
                Result.UpdateString(6, 'DECIMAL');
            end;
          37 : Result.UpdateString(6, 'VARCHAR'); // Instead of VARYING
          else Result.UpdateString(6, GetStringByName('RDB$TYPE_NAME'));
        end;
//        Result.UpdateString(6,GetStringByName('RDB$TYPE_NAME'));  //TYPE_NAME
        // SC - End
        
        // SC - Begin
        // COLUMN_SIZE.
        case TypeName of
          7, 8 : Result.UpdateInt(7, 0);
          16   : Result.UpdateInt(7, GetIntByName('RDB$FIELD_PRECISION'));
          else Result.UpdateInt(7, GetIntByName('RDB$FIELD_LENGTH'));
        end;
//        Result.UpdateInt(7, GetIntByName('RDB$FIELD_LENGTH'));   //COLUMN_SIZE
        // SC - End
        
        Result.UpdateNull(8);    //BUFFER_LENGTH

        if FieldScale < 0 then
          Result.UpdateInt(9, -1 * FieldScale)    //DECIMAL_DIGITS
        else Result.UpdateInt(9, 0);    //DECIMAL_DIGITS

        Result.UpdateInt(10, 10);   //NUM_PREC_RADIX

        if GetIntByName('RDB$NULL_FLAG') <> 0 then
          Result.UpdateInt(11, Ord(ntNoNulls))   //NULLABLE
        else Result.UpdateInt(11, Ord(ntNullable));

        Result.UpdateString(12,
          Copy(GetStringByName('RDB$DESCRIPTION'),1,255));   //REMARKS
        Result.UpdateString(13, DefaultValue);   //COLUMN_DEF
        Result.UpdateNull(14);   //SQL_DATA_TYPE
        Result.UpdateNull(15);   //SQL_DATETIME_SUB
        Result.UpdateInt(16,
          GetInt(7));   //CHAR_OCTET_LENGTH
        Result.UpdateInt(17,
          GetIntByName('RDB$FIELD_POSITION') + 1);   //ORDINAL_POSITION

        if IsNullByName('RDB$NULL_FLAG') then
          Result.UpdateString(18, 'YES')   //IS_NULLABLE
        else Result.UpdateString(18, 'NO');   //IS_NULLABLE

        Result.UpdateNullByName('AUTO_INCREMENT');

        if CompareStr(ColumnName, UpperCase(ColumnName)) = 0 then
          Result.UpdateBooleanByName('CASE_SENSITIVE', False)
        else
          Result.UpdateBooleanByName('CASE_SENSITIVE', True);

        Result.UpdateBooleanByName('SEARCHABLE', True);
        Result.UpdateBooleanByName('WRITABLE', True);
        Result.UpdateBooleanByName('DEFINITELYWRITABLE', True);
        Result.UpdateBooleanByName('READONLY', False);

        Result.InsertRow;
      end;
      Close;
    end;

    AddResultSetToCache(Key, Result);
  end;
end;
Regards

Sandeep

Posted: 09.02.2008, 11:10
by sandeep_c24
Hi Mark

I had a look at the unit ZDbcMetadata and all the reserved columns are of type string of size 255. Since the source of the procedure could be quite big how long should the length of one of the reserved column be set to?

Is it ok to use reserved column or should a new column be added?

Regards

Sandeep

Posted: 09.02.2008, 20:57
by mdaems
It would be OK to use a reserved one, but I think the datatype needs to be something like Text or BLOB. At least of unlimited length. Not sure if that's possible for this kind of datasets, however. I think that will be your biggest problem to solve. Other stuff is just copy/paste/imitate. If you need help you can ask using pm, skype , msn or yahoo. I haven't time to look into it right now, however.
Maybe a request from my side : can you add some option to the Metadata component to disable source fetching? It may be quite slow to get sources.

Mark

Posted: 11.02.2008, 07:03
by sandeep_c24
I have added a boolean property called GetSource to TZSQLMetadat to control fetching of the source.

Is the property name ok?

Regards

Sandeep

Posted: 11.02.2008, 10:18
by mdaems
I would call it 'IncludeSources' as 'Get...' is more like a method name.

Mark

Posted: 11.02.2008, 10:31
by sandeep_c24
Ok, I have done that.

Regards

Sandeep