Buenas,
Como ya sabemos (y sino lo sabréis ahora), Firebird (y Interbase) guardan la información de todo el metadato de una base de datos en unas tablas especiales llamadas tablas de sistema (o system tables en inglés). En este artículo quiero mostrar cómo extraer información de estas tablas para así poder, por ejemplo, personalizar mensajes o mostrar información al usuario o lo que creamos oportuno.
Tenemos que tener claro que estas tablas las usaremos sólo de consulta, nunca modificaremos una manualmente (sobre todo si no sabemos qué tocamos) ya que su modificación puede conllevar a la rotura de nuestra base de datos.
Dado que no he encontrado información de la relación entre las diferentes tablas de sistema y éstas lo único que tienen son índices únicos (no tienen claves primarias ni relaciones de integridad para saber con qué tabla se relacionan), he creado un pequeño diagrama de la mayoría de ellas que, más o menos, creo es el correcto. Si alguien discrepa, estaré encantado de leer su propuesta y hacer los cambios al modelo que sean necesarios. Para una mayor comprensión he eliminado el RDB$ de delante del nombre de las tablas y de los campos.
Referente al diagrama, cabe decir que las relaciones se hacen todas a través del campo con el mismo nombre en ambas tablas, a excepción de RELATION_FIELDS que usa FIELD_SOURCE para relacionarse con la tabla FIELDS.
Puesto que existe una muy buena documentación de qué contiene cada una de las tablas, el objetivo de esta entrada no será el explicarlo, sino el facilitar una herramienta que ya haga el trabajo de extraer esa información. Para ello he creado una clase con una serie de métodos que lanzan las sentencias SQL necesarias para devolver la información deseada en cada caso. Dado que hay que lanzar sentencias SQL, necesitamos un componente tipo «query» para poder realizar las consultas. Debido a ésto, la clase debería ser dependiente de unos determinados componentes de acceso a Firebird. Para evitarlo, he definido un par de métodos abstractos para crear y gestionar el objeto «query», así las clases descendientes sólo necesitarán redefinir estos dos métodos y será muy sencillo hacerlo accesible a cualquier juego de componentes.
La clase padre está definida como sigue:
TFBMetaData = class private FDataBase: TCustomConnection; FQuery: TDataSet; protected procedure Iterator(SQL: string; SL: TStringList); overload; procedure Iterator(SQL: string; SL1, SL2: TStringList); overload; procedure SetQuery; virtual; abstract; function AssignSQL(SQL: string): Boolean; virtual; abstract; function ExecSQL(SQL: string): Boolean; virtual; public constructor Create(DataBase: TCustomConnection); virtual; destructor Destroy; override; procedure GetTables(TablesName: TStringList); procedure GetViews(ViewsName: TStringList); procedure GetViewSource(ViewName: string; ViewSource: TStringList); procedure GetTableFields(TableName: string; FieldsName: TStringList); overload; procedure GetTableFields(TableName: string; FieldsName, FieldsType: TStringList); overload; procedure GetPrimaryKeyFields(TableName: string; FieldsName: TStringList); overload; procedure GetPrimaryKeyFields(TableName: string; FieldsName, FieldsType: TStringList); overload; procedure GetDependOnObject(ObjectName: string; DependObjects, ObjectsType: TStringList); procedure GetObjectDependsOn(ObjectName: string; DependObjects, ObjectsType: TStringList); procedure GetExceptions(ExceptName, ExceptMessage: TStringList); procedure GetFieldArrayDimension(TableName, FieldName: string; LowerBound, UpperBound: TStringList); procedure GetUDF(UDF: TStringList); procedure GetGenerators(Generators: TStringList); procedure GetIndexFields(IndexName: string; IndexFields: TStringList); procedure GetIndex(TableName: string; IndexName: TStringList; OnlyActive: Boolean = False; Unique: Boolean = False); procedure GetProcedures(ProcNames: TStringList); procedure GetProcedureSource(ProcName: string; ProcSource: TStringList); procedure GetProcedureInParam(ProcName: string; ProcInParam: TStringList); procedure GetProcedureOutParam(ProcName: string; ProcOutParam: TStringList); procedure GetRoles(RolesName: TStringList; NoSystemRoles: Boolean = True); procedure GetTriggers(TableName: string; TriggerType: TTriggerType; TriggerName: TStringList); procedure GetTriggerSource(TriggerName: string; TriggerSource: TStringList); end;
Aquí vemos como el constructor recibe como parámetro un database de conexión a nuestra base de datos. Éste es de tipo TCustomConnection
, por lo que sólo nos servirán aquellos componentes que su componente de conexión derive de éste (que son la gran mayoría).
También vemos los dos métodos a redefinir en los descendientes, que son AssignSQL
(el cual asignará la sentencia SQL al componente «query») y SetQuery
(que creará el componente «query»).
Así mismo vemos todos los métodos públicos disponibles. El propio nombre indica qué información devuelve cada uno, así que no creo que valga la pena comentarlos. La forma de devolver la información es en un parámetro (o dos) de tipo TStringList. Por ejemplo, GetTables
devolverá en el parámetro TablesName
las tablas definidas en la base de datos.
Para ilustrar cómo se haría un descendiente de esta clase, acompañan el ejemplo 2 clases más, una para FireDAC y otra para IBX.
Para FireDAC quedaría de la siguiente manera.
// clase para los componentes FireDAC TFBMDFireDAC = class(TFBMetaData) protected procedure SetQuery; override; function AssignSQL(SQL: string): Boolean; override; public constructor Create(DataBase: TADConnection); reintroduce; virtual; end;
Como vemos, se redefinen los dos métodos abstractos para ponerles el código específico de estos componentes. También redefino el constructor para forzar que el componente database que se pase por parámetro sea el específico de los componentes con los que quiero trabajar.
Del mismo modo he hecho el específico para los IBX.
// clase para los componentes IBX TFBMDIBX = class(TFBMetaData) protected procedure SetQuery; override; function AssignSQL(SQL: string): Boolean; override; public constructor Create(DataBase: TIBDatabase); reintroduce; virtual; end;
Otros componentes como DBExpress, IBObjects o ADO, funcionarían de la misma manera.
Todo esto es válido al menos hasta la versión 2.5 de Firebird, que es con la que he realizado las pruebas (Firebird 2.5.1.26351). Imagino que también lo será para Interbase, aunque no he hecho ninguna prueba.
Si alguien no tiene Firebird/Interbase instalado, le recomiendo que se descargue la versión Embedded de Firebird si no se quiere instalar el motor.
Descargar la demo y la unit aquí.
Espero os sea de utilidad.
Nos leemos
Hola estoy leyendo tu clase TFBError, pero no puedo bajármela porque el zip está dañado o no es legible, con los zip anteriores me pasa lo mismo, ¿podría tener acceso a un zip que fuese legible?
Un saludo y gracias de antemano
Hola Ricardo
Quizás tienes algún problema con el WinZip que tengas instalado en tu máquina, porque yo he probado de abrirlo en dos máquinas (win7) tanto con WinRar como con el Zip de Windows y no he tenido problemas.
Saludos
Amigo, muy buena la clase… La he bajado y la estuve analizando y veo que tiene units que no son del lenguaje, algunas de las mismas son creadas por vos pero no existen en el proyecto
Buenas
En el programa demo no hay ninguna unit «mía» a parte de la propia FBMetaData.pas
Los componentes usados en la demo son los que vienen en la propia instalación de Delphi (en las versiones más recientes) como son los FireDAC y los IBX (desde hace muchas versiones).
Dime qué unit crees que falta en el proyecto y te digo a qué conjunto de componentes pertenece.
Saludos
Hola Cadetill:
Utilizo Delphi 5. Al descargar el «system_tables_firebird» no vienen ningun demo ejecutable, y cuando trato de abrir el «SysTablesFB.dpr» me salta el error de Out of memory. (y no es por falta de memoria del equipo). ¿Como podría solucionarlo?.
Saludos cordiales.
Hola Marcial
Es un proyecto realizado en XE3 si no recuerdo mal, por lo que para abrirlo con una versión anterior de Delphi tendrás que abrir el .pas con un editor de texto y modificar los uses para hacerlo compatible con D5
Saludos