ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/nongui/IBExtract.pas
Revision: 209
Committed: Wed Mar 14 12:48:51 2018 UTC (6 years, 1 month ago) by tony
Content type: text/x-pascal
File size: 125414 byte(s)
Log Message:
Fixes Merged

File Contents

# User Rev Content
1 tony 209 {************************************************************************}
2     { }
3     { The contents of this file are subject to the InterBase }
4     { Public License Version 1.0 (the "License"); you may not }
5     { use this file except in compliance with the License. You }
6     { may obtain a copy of the License at http://www.Inprise.com/IPL.html }
7     { Software distributed under the License is distributed on }
8     { an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either }
9     { express or implied. See the License for the specific language }
10     { governing rights and limitations under the License. }
11     { }
12     { The Original Code was created by Jeff Overcash. }
13     { Portions based upon code by Inprise Corporation are Copyright (C) }
14     { Inprise Corporation. All Rights Reserved. }
15     { }
16     { IBX Version 4.2 or higher required }
17     { Contributor(s): Jeff Overcash }
18     { }
19     { IBX For Lazarus (Firebird Express) }
20     { Contributor: Tony Whyman, MWA Software http://www.mwasoftware.co.uk }
21     { Portions created by MWA Software are copyright McCallum Whyman }
22     { Associates Ltd 2011 - 2018 }
23     { }
24     {************************************************************************}
25    
26     { Syntax Enhancements Supported (by Firebird Version no.):
27    
28     Multi-action triggers (1.5)
29     CREATE SEQUENCE (2.0)
30     Database Triggers (2.1)
31     Global Temporary Tables (2.1)
32     Boolean Type (3.0)
33     Identity Column Type (3.0)
34     DDL Triggers (3.0)
35     }
36    
37     unit IBExtract;
38    
39     {$Mode Delphi}
40     {$codepage UTF8}
41    
42     interface
43    
44     uses
45     {$IFDEF WINDOWS }
46     Windows,
47     {$ELSE}
48     unix,
49     {$ENDIF}
50     SysUtils, Classes, IBDatabase, IBDatabaseInfo,
51     IBSQL, IBUtils, IBHeader, IB;
52    
53     type
54     TExtractObjectTypes =
55     (eoDatabase, eoDomain, eoTable, eoView, eoProcedure, eoFunction,
56     eoGenerator, eoException, eoBLOBFilter, eoRole, eoTrigger, eoForeign,
57     eoIndexes, eoChecks, eoData);
58    
59     TExtractType =
60     (etDomain, etTable, etRole, etTrigger, etForeign,
61     etIndex, etData, etGrant, etCheck, etGrantsToUser);
62    
63     TExtractTypes = Set of TExtractType;
64    
65     TProcDDLType = (pdCreateProc,pdCreateStub,pdAlterProc);
66    
67     { TIBExtract }
68    
69     TIBExtract = class(TComponent)
70     private
71     FAlwaysQuoteIdentifiers: boolean;
72     FDatabase : TIBDatabase;
73     FTransaction : TIBTransaction;
74     FMetaData: TStrings;
75     FDatabaseInfo: TIBDatabaseInfo;
76     FShowSystem: Boolean;
77     { Private declarations }
78     procedure Add2MetaData(const Msg: string; IsError: boolean=true);
79     function GetDatabase: TIBDatabase;
80     function GetIndexSegments ( indexname : String) : String;
81     function GetTransaction: TIBTransaction;
82     function GetTriggerType(TypeID: Int64): string;
83     procedure SetDatabase(const Value: TIBDatabase);
84     procedure SetTransaction(const Value: TIBTransaction);
85     function PrintValidation(ToValidate : String; flag : Boolean) : String;
86     procedure ShowGrants(MetaObject: String; Terminator : String; NoUserGrants: boolean=false);
87     procedure ShowGrantsTo(MetaObject: String; ObjectType: integer;
88     Terminator: String);
89     procedure ShowGrantRoles(Terminator : String);
90     procedure GetProcedureArgs(Proc : String);
91     protected
92     function ExtractDDL(Flag: Boolean; TableName: String; ExtractTypes: TExtractTypes =
93     []): Boolean;
94     function ExtractListTable(RelationName, NewName: String; DomainFlag: Boolean): Boolean;
95     procedure ExtractListView (ViewName : String);
96     procedure ListData(ObjectName : String);
97     procedure ListRoles(ObjectName : String = ''; IncludeGrants:boolean=false);
98     procedure ListGrants(ExtractTypes : TExtractTypes = []);
99     procedure ListProcs(ProcDDLType: TProcDDLType = pdCreateProc; ProcedureName : String = '';
100     IncludeGrants:boolean=false);
101     procedure ListAllTables(flag : Boolean);
102     procedure ListTriggers(ObjectName: String=''; ExtractTypes: TExtractTypes = [etTrigger]);
103     procedure ListCheck(ObjectName : String = ''; ExtractType : TExtractType = etCheck);
104     function PrintSet(var Used : Boolean) : String;
105     procedure ListCreateDb(TargetDb : String = '');
106     procedure ListDomains(ObjectName : String = ''; ExtractType : TExtractType = etDomain);
107     procedure ListException(ExceptionName : String = '');
108     procedure ListFilters(FilterName : String = '');
109     procedure ListForeign(ObjectName : String = ''; ExtractType : TExtractType = etForeign);
110     procedure ListFunctions(FunctionName : String = '');
111     procedure ListGenerators(GeneratorName : String = ''; ExtractTypes: TExtractTypes=[]);
112     procedure ListIndex(ObjectName : String = ''; ExtractType : TExtractType = etIndex);
113     procedure ListViews(ViewName : String = '');
114     procedure Notification(AComponent: TComponent; Operation: TOperation); override;
115     function QuoteIdentifier(Value: String): String;
116    
117     { Protected declarations }
118     public
119     { Public declarations }
120     constructor Create(AOwner : TComponent); override;
121     destructor Destroy; override;
122     function GetArrayField(FieldName : String) : String;
123     function GetFieldType(FieldType, FieldSubType, FieldScale, FieldSize,
124     FieldPrec, FieldLen : Integer) : String;
125     function GetCharacterSets(CharSetId, Collation : integer; CollateOnly : Boolean) : String;
126     procedure ExtractObject(ObjectType : TExtractObjectTypes; ObjectName : String = '';
127     ExtractTypes : TExtractTypes = []);
128     property DatabaseInfo : TIBDatabaseInfo read FDatabaseInfo;
129     property Items : TStrings read FMetaData;
130    
131     published
132     { Published declarations }
133     property Database : TIBDatabase read GetDatabase write SetDatabase;
134     property Transaction : TIBTransaction read GetTransaction write SetTransaction;
135     property ShowSystem: Boolean read FShowSystem write FShowSystem;
136     property AlwaysQuoteIdentifiers: boolean read FAlwaysQuoteIdentifiers write FAlwaysQuoteIdentifiers;
137     end;
138    
139     TSQLType = record
140     SqlType : Integer;
141     TypeName : String;
142     end;
143    
144     TPrivTypes = record
145     PrivFlag : Integer;
146     PrivString : String;
147     end;
148    
149     TSQLTypes = Array[0..14] of TSQLType;
150    
151     const
152    
153     priv_UNKNOWN = 1;
154     priv_SELECT = 2;
155     priv_INSERT = 4;
156     priv_UPDATE = 8;
157     priv_DELETE = 16;
158     priv_EXECUTE = 32;
159     priv_REFERENCES = 64;
160     priv_USAGE = 128;
161    
162     PrivTypes : Array[0..6] of TPrivTypes = (
163     (PrivFlag : priv_DELETE; PrivString : 'DELETE' ),
164     (PrivFlag : priv_EXECUTE; PrivString : 'EXECUTE' ),
165     (PrivFlag : priv_INSERT; PrivString : 'INSERT' ),
166     (PrivFlag : priv_SELECT; PrivString : 'SELECT' ),
167     (PrivFlag : priv_UPDATE; PrivString : 'UPDATE' ),
168     (PrivFlag : priv_REFERENCES; PrivString : 'REFERENCES'),
169     (PrivFlag : priv_USAGE; PrivString : 'USAGE' ));
170    
171     ColumnTypes : TSQLTypes = (
172     (SqlType : blr_short; TypeName : 'SMALLINT'), { NTX: keyword }
173     (SqlType : blr_long; TypeName : 'INTEGER'), { NTX: keyword }
174     (SqlType : blr_quad; TypeName : 'QUAD'), { NTX: keyword }
175     (SqlType : blr_float; TypeName : 'FLOAT'), { NTX: keyword }
176     (SqlType : blr_text; TypeName : 'CHAR'), { NTX: keyword }
177     (SqlType : blr_double; TypeName : 'DOUBLE PRECISION'), { NTX: keyword }
178     (SqlType : blr_varying; TypeName : 'VARCHAR'), { NTX: keyword }
179     (SqlType : blr_cstring; TypeName : 'CSTRING'), { NTX: keyword }
180     (SqlType : blr_blob_id; TypeName : 'BLOB_ID'), { NTX: keyword }
181     (SqlType : blr_blob; TypeName : 'BLOB'), { NTX: keyword }
182     (SqlType : blr_sql_time; TypeName : 'TIME'), { NTX: keyword }
183     (SqlType : blr_sql_date; TypeName : 'DATE'), { NTX: keyword }
184     (SqlType : blr_timestamp; TypeName : 'TIMESTAMP'), { NTX: keyword }
185     (SqlType : blr_int64; TypeName : 'INT64'),
186     (SqlType : blr_bool; TypeName : 'BOOLEAN'));
187    
188     SubTypes : Array[0..8] of String = (
189     'UNKNOWN', { NTX: keyword }
190     'TEXT', { NTX: keyword }
191     'BLR', { NTX: keyword }
192     'ACL', { NTX: keyword }
193     'RANGES', { NTX: keyword }
194     'SUMMARY', { NTX: keyword }
195     'FORMAT', { NTX: keyword }
196     'TRANSACTION_DESCRIPTION', { NTX: keyword }
197     'EXTERNAL_FILE_DESCRIPTION'); { NTX: keyword }
198    
199     IntegralSubtypes : Array[0..2] of String = (
200     'UNKNOWN', { Defined type, NTX: keyword }
201     'NUMERIC', { NUMERIC, NTX: keyword }
202     'DECIMAL'); { DECIMAL, NTX: keyword }
203    
204     ODS_VERSION6 = 6; { on-disk structure as of v3.0 }
205     ODS_VERSION7 = 7; { new on disk structure for fixing index bug }
206     ODS_VERSION8 = 8; { new btree structure to support pc semantics }
207     ODS_VERSION9 = 9; { btree leaf pages are always propogated up }
208     ODS_VERSION10 = 10; { V6.0 features. SQL delimited idetifier,
209     SQLDATE, and 64-bit exact numeric
210     type }
211     ODS_VERSION12 = 12; {Firebird 3}
212    
213     { flags for RDB$FILE_FLAGS }
214     FILE_shadow = 1;
215     FILE_inactive = 2;
216     FILE_manual = 4;
217     FILE_cache = 8;
218     FILE_conditional = 16;
219    
220     { flags for RDB$LOG_FILES }
221     LOG_serial = 1;
222     LOG_default = 2;
223     LOG_raw = 4;
224     LOG_overflow = 8;
225    
226    
227    
228     MAX_INTSUBTYPES = 2;
229     MAXSUBTYPES = 8; { Top of subtypes array }
230    
231     { Object types used in RDB$DEPENDENCIES and RDB$USER_PRIVILEGES }
232    
233     obj_relation = 0;
234     obj_view = 1;
235     obj_trigger = 2;
236     obj_computed = 3;
237     obj_validation = 4;
238     obj_procedure = 5;
239     obj_expression_index = 6;
240     obj_exception = 7;
241     obj_user = 8;
242     obj_field = 9;
243     obj_index = 10;
244     obj_count = 11;
245     obj_user_group = 12;
246     obj_sql_role = 13;
247    
248     implementation
249    
250     uses FBMessages, IBDataOutput;
251    
252     const
253     TERM = ';';
254     ProcTerm = '^';
255    
256     CollationSQL =
257     'SELECT CST.RDB$CHARACTER_SET_NAME, COL.RDB$COLLATION_NAME, CST.RDB$DEFAULT_COLLATE_NAME ' +
258     'FROM RDB$COLLATIONS COL JOIN RDB$CHARACTER_SETS CST ON ' +
259     ' COL.RDB$CHARACTER_SET_ID = CST.RDB$CHARACTER_SET_ID ' +
260     'WHERE ' +
261     ' COL.RDB$COLLATION_ID = :COLLATION AND ' +
262     ' CST.RDB$CHARACTER_SET_ID = :CHAR_SET_ID ' +
263     'ORDER BY COL.RDB$COLLATION_NAME, CST.RDB$CHARACTER_SET_NAME';
264    
265     NonCollationSQL =
266     'SELECT CST.RDB$CHARACTER_SET_NAME ' +
267     'FROM RDB$CHARACTER_SETS CST ' +
268     'WHERE CST.RDB$CHARACTER_SET_ID = :CHARSETID ' +
269     'ORDER BY CST.RDB$CHARACTER_SET_NAME';
270    
271     PrecisionSQL =
272     'SELECT * FROM RDB$FIELDS ' +
273     'WHERE RDB$FIELD_NAME = :FIELDNAME';
274    
275     ArraySQL =
276     'SELECT * FROM RDB$FIELD_DIMENSIONS FDIM ' +
277     'WHERE ' +
278     ' FDIM.RDB$FIELD_NAME = :FIELDNAME ' +
279     'ORDER BY FDIM.RDB$DIMENSION';
280    
281     type
282     TTriggerPhase = (tpNone,tpCreate,tpAlter,tpDrop);
283    
284     TDDLTriggerMap = record
285     ObjectName: string;
286     Bits: integer;
287     Bit1: TTriggerPhase;
288     Bit2: TTriggerPhase;
289     Bit3: TTriggerPhase;
290     end;
291    
292     const
293     DDLTriggers : array [0..15] of TDDLTriggerMap = (
294     (ObjectName: 'TABLE'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
295     (ObjectName: 'PROCEDURE'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
296     (ObjectName: 'FUNCTION'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
297     (ObjectName: 'TRIGGER'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
298     (ObjectName: 'Empty slot'; Bits: 3; Bit1: tpNone; Bit2: tpNone; Bit3: tpNone),
299     (ObjectName: 'EXCEPTION'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
300     (ObjectName: 'VIEW'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
301     (ObjectName: 'DOMAIN'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
302     (ObjectName: 'ROLE'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
303     (ObjectName: 'INDEX'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
304     (ObjectName: 'SEQUENCE'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
305     (ObjectName: 'USER'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
306     (ObjectName: 'COLLATION'; Bits: 2; Bit1: tpCreate; Bit2: tpDrop; Bit3: tpNone),
307     (ObjectName: 'CHARACTER SET'; Bits: 1; Bit1: tpAlter; Bit2: tpNone; Bit3: tpNone),
308     (ObjectName: 'PACKAGE'; Bits: 3; Bit1: tpCreate; Bit2: tpAlter; Bit3: tpDrop),
309     (ObjectName: 'PACKAGE BODY'; Bits: 2; Bit1: tpCreate; Bit2: tpDrop; Bit3: tpNone)
310     );
311    
312     { TIBExtract }
313    
314     { ArrayDimensions
315     Functional description
316     Retrieves the dimensions of arrays and prints them.
317    
318     Parameters: fieldname -- the actual name of the array field }
319    
320     function TIBExtract.GetArrayField(FieldName: String): String;
321     var
322     qryArray : TIBSQL;
323     begin
324     qryArray := TIBSQL.Create(FDatabase);
325     Result := '';
326     qryArray.SQL.Add(ArraySQL);
327     qryArray.Params.ByName('FieldName').AsString := FieldName;
328     qryArray.ExecQuery;
329    
330     { Format is [lower:upper, lower:upper,..] }
331    
332     if not qryArray.Eof then
333     begin
334     Result := '[';
335     while not qryArray.Eof do
336     begin
337     if (qryArray.FieldByName('RDB$DIMENSION').AsInteger > 0) then
338     Result := Result + ', ';
339     Result := Result + qryArray.FieldByName('RDB$LOWER_BOUND').AsString + ':' +
340     qryArray.FieldByName('RDB$UPPER_BOUND').AsString;
341     qryArray.Next;
342     end;
343     Result := Result + '] ';
344     end;
345    
346     qryArray.Free;
347    
348     end;
349    
350     constructor TIBExtract.Create(AOwner: TComponent);
351     begin
352     inherited;
353     FMetaData := TStringList.Create;
354     FDatabaseInfo := TIBDatabaseInfo.Create(nil);
355     FDatabaseInfo.Database := FDatabase;
356     if AOwner is TIBDatabase then
357     Database := TIBDatabase(AOwner);
358     if AOwner is TIBTransaction then
359     Transaction := TIBTransaction(AOwner);
360     end;
361    
362     destructor TIBExtract.Destroy;
363     begin
364     FMetaData.Free;
365     FDatabasEInfo.Free;
366     inherited;
367     end;
368    
369     function TIBExtract.ExtractDDL(Flag: Boolean; TableName: String;
370     ExtractTypes: TExtractTypes): Boolean;
371     var
372     DidConnect : Boolean;
373     DidStart : Boolean;
374     begin
375     Result := true;
376     DidConnect := false;
377     DidStart := false;
378    
379     if not FDatabase.Connected then
380     begin
381     FDatabase.Connected := true;
382     didConnect := true;
383     end;
384    
385     FMetaData.Add(Format('SET SQL DIALECT %d;', [FDatabase.SQLDialect]));
386     FMetaData.Add('SET AUTODDL ON;');
387     FMetaData.Add('');
388    
389     if not FTransaction.Active then
390     begin
391     FTransaction.StartTransaction;
392     DidStart := true;
393     end;
394    
395     if TableName <> '' then
396     begin
397     if not ExtractListTable(TableName, '', true) then
398     Result := false;
399     end
400     else
401     begin
402     ListCreateDb;
403     ListFilters;
404     ListFunctions;
405     ListDomains;
406     ListAllTables(flag);
407     if etData in ExtractTypes then
408     ListData('');
409     ListIndex;
410     ListForeign;
411     if etData in ExtractTypes then
412     ListGenerators('',[etData])
413     else
414     ListGenerators;
415     ListViews;
416     ListCheck;
417     ListException;
418     ListProcs(pdCreateStub);
419     ListTriggers;
420     ListProcs(pdAlterProc);
421     ListGrants(ExtractTypes);
422     end;
423    
424     if DidStart then
425     FTransaction.Commit;
426    
427     if DidConnect then
428     FDatabase.Connected := false;
429     end;
430    
431     { ExtractListTable
432     Functional description
433     Shows columns, types, info for a given table name
434     and text of views.
435     If a new_name is passed, substitute it for relation_name
436    
437     relation_name -- Name of table to investigate
438     new_name -- Name of a new name for a replacement table
439     domain_flag -- extract needed domains before the table }
440    
441     function TIBExtract.ExtractListTable(RelationName, NewName: String;
442     DomainFlag: Boolean): Boolean;
443     const
444     TableListSQL =
445     'SELECT * FROM RDB$RELATIONS REL JOIN RDB$RELATION_FIELDS RFR ON ' + {Do Not Localize}
446     ' RFR.RDB$RELATION_NAME = REL.RDB$RELATION_NAME JOIN RDB$FIELDS FLD ON ' +
447     ' RFR.RDB$FIELD_SOURCE = FLD.RDB$FIELD_NAME ' +
448     'WHERE REL.RDB$RELATION_NAME = :RelationName ' +
449     'ORDER BY RFR.RDB$FIELD_POSITION, RFR.RDB$FIELD_NAME';
450    
451     ConstraintSQL =
452     'SELECT RCO.RDB$CONSTRAINT_NAME, RDB$CONSTRAINT_TYPE, RDB$RELATION_NAME, ' +
453     'RDB$DEFERRABLE, RDB$INITIALLY_DEFERRED, RDB$INDEX_NAME, RDB$TRIGGER_NAME ' +
454     'FROM RDB$RELATION_CONSTRAINTS RCO, RDB$CHECK_CONSTRAINTS CON ' +
455     'WHERE ' +
456     ' CON.RDB$TRIGGER_NAME = :FIELDNAME AND ' +
457     ' CON.RDB$CONSTRAINT_NAME = RCO.RDB$CONSTRAINT_NAME AND ' +
458     ' RCO.RDB$CONSTRAINT_TYPE = ''NOT NULL'' AND ' +
459     ' RCO.RDB$RELATION_NAME = :RELATIONNAME';
460    
461     RelConstraintsSQL =
462     'SELECT * FROM RDB$RELATION_CONSTRAINTS RELC ' +
463     'WHERE ' +
464     ' (RELC.RDB$CONSTRAINT_TYPE = ''PRIMARY KEY'' OR ' +
465     ' RELC.RDB$CONSTRAINT_TYPE = ''UNIQUE'') AND ' +
466     ' RELC.RDB$RELATION_NAME = :RELATIONNAME ' +
467     'ORDER BY RELC.RDB$CONSTRAINT_TYPE desc, RELC.RDB$CONSTRAINT_NAME';
468    
469     GetGeneratorSQL =
470     'SELECT * FROM RDB$GENERATORS WHERE RDB$GENERATOR_NAME = :GENERATOR';
471    
472     var
473     Collation, CharSetId : integer;
474     i : integer;
475     Column, Constraint : String;
476     SubType : integer;
477     IntChar : integer;
478     qryTables, qryPrecision, qryConstraints, qryRelConstraints, qryGenerators : TIBSQL;
479     PrecisionKnown, ValidRelation : Boolean;
480     FieldScale, FieldType : Integer;
481     CreateTable: string;
482     TableType: integer;
483     begin
484     Result := true;
485     IntChar := 0;
486     ValidRelation := false;
487    
488     if DomainFlag then
489     ListDomains(RelationName);
490     qryTables := TIBSQL.Create(FDatabase);
491     qryPrecision := TIBSQL.Create(FDatabase);
492     qryConstraints := TIBSQL.Create(FDatabase);
493     qryRelConstraints := TIBSQL.Create(FDatabase);
494     qryGenerators := TIBSQL.Create(FDatabase);
495     try
496     qryTables.SQL.Add(TableListSQL);
497     RelationName := trim(RelationName);
498     qryTables.Params.ByName('RelationName').AsString := RelationName;
499     qryTables.ExecQuery;
500     qryPrecision.SQL.Add(PrecisionSQL);
501     qryConstraints.SQL.Add(ConstraintSQL);
502     qryRelConstraints.SQL.Add(RelConstraintsSQL);
503     qryGenerators.SQL.Add(GetGeneratorSQL);
504     if not qryTables.Eof then
505     begin
506     ValidRelation := true;
507     TableType := qryTables.FieldByName('RDB$RELATION_TYPE').AsInteger;
508     if (not qryTables.FieldByName('RDB$OWNER_NAME').IsNull) and
509     (Trim(qryTables.FieldByName('RDB$OWNER_NAME').AsString) <> '') then
510     FMetaData.Add(Format('%s/* Table: %s, Owner: %s */%s',
511     [LineEnding, RelationName,
512     qryTables.FieldByName('RDB$OWNER_NAME').AsString, LineEnding]));
513     if TableType > 3 then
514     CreateTable := 'CREATE GLOBAL TEMPORARY TABLE'
515     else
516     CreateTable := 'CREATE TABLE';
517     if NewName <> '' then
518     FMetaData.Add(Format('%s %s ', [CreateTable,QuoteIdentifier(NewName)]))
519     else
520     FMetaData.Add(Format('%s %s ', [CreateTable,QuoteIdentifier(RelationName)]));
521     if not qryTables.FieldByName('RDB$EXTERNAL_FILE').IsNull then
522     FMetaData.Add(Format('EXTERNAL FILE %s ',
523     [QuotedStr(qryTables.FieldByName('RDB$EXTERNAL_FILE').AsString)]));
524     FMetaData.Add('(');
525     end;
526    
527     while not qryTables.Eof do
528     begin
529     Column := ' ' + QuoteIdentifier( qryTables.FieldByName('RDB$FIELD_NAME').AsString) + TAB;
530    
531     { Check first for computed fields, then domains.
532     If this is a known domain, then just print the domain rather than type
533     Domains won't have length, array, or blob definitions, but they
534     may have not null, default and check overriding their definitions }
535    
536     if not qryTables.FieldByName('rdb$computed_blr').IsNull then
537     begin
538     Column := Column + ' COMPUTED BY ';
539     if not qryTables.FieldByName('RDB$COMPUTED_SOURCE').IsNull then
540     Column := Column + PrintValidation(qryTables.FieldByName('RDB$COMPUTED_SOURCE').AsString, true);
541     end
542     else
543     begin
544     FieldType := qryTables.FieldByName('RDB$FIELD_TYPE').AsInteger;
545     FieldScale := qryTables.FieldByName('RDB$FIELD_SCALE').AsInteger;
546     if not ((Copy(qryTables.FieldByName('RDB$FIELD_NAME1').AsString, 1, 4) = 'RDB$') and
547     (qryTables.FieldByName('RDB$FIELD_NAME1').AsString[5] in ['0'..'9'])) and
548     (qryTables.FieldByName('RDB$SYSTEM_FLAG').AsInteger <> 1) then
549     begin
550     Column := Column + QuoteIdentifier( trim(qryTables.FieldByName('RDB$FIELD_NAME1').AsString));
551     { International character sets }
552     if (qryTables.FieldByName('RDB$FIELD_TYPE').AsInteger in [blr_text, blr_varying])
553     and (not qryTables.FieldByName('RDB$COLLATION_ID').IsNull)
554     and (qryTables.FieldByName('RDB$COLLATION_ID').AsInteger <> 0) then
555     begin
556     Collation := qryTables.FieldByName('RDB$COLLATION_ID').AsInteger;
557     Column := Column + GetCharacterSets(qryTables.FieldByName('RDB$CHARACTER_SET_ID').AsShort,
558     Collation, true);
559     end;
560     end
561     else
562     begin
563     { Look through types array }
564     for i := Low(Columntypes) to High(ColumnTypes) do
565     begin
566     PrecisionKnown := false;
567     if qryTables.FieldByname('RDB$FIELD_TYPE').AsShort = ColumnTypes[i].SQLType then
568     begin
569    
570     if FDatabaseInfo.ODSMajorVersion >= ODS_VERSION10 then
571     begin
572     { Handle Integral subtypes NUMERIC and DECIMAL }
573     if qryTables.FieldByName('RDB$FIELD_TYPE').AsInteger in
574     [blr_short, blr_long, blr_int64] then
575     begin
576     qryPrecision.Params.ByName('FIELDNAME').AsString :=
577     qryTables.FieldByName('RDB$FIELD_NAME1').AsString;
578     qryPrecision.ExecQuery;
579    
580     { We are ODS >= 10 and could be any Dialect }
581     if not qryPrecision.FieldByName('RDB$FIELD_PRECISION').IsNull then
582     begin
583     { We are Dialect >=3 since FIELD_PRECISION is non-NULL }
584     if (qryPrecision.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger > 0) and
585     (qryPrecision.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger <= MAX_INTSUBTYPES) then
586     begin
587     Column := column + Format('%s(%d, %d)',
588     [IntegralSubtypes[qryPrecision.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger],
589     qryPrecision.FieldByName('RDB$FIELD_PRECISION').AsInteger,
590     -qryPrecision.FieldByName('RDB$FIELD_SCALE').AsInteger]);
591     PrecisionKnown := TRUE;
592     end;
593     end;
594     qryPrecision.Close;
595     end;
596     end;
597    
598     if PrecisionKnown = FALSE then
599     begin
600     { Take a stab at numerics and decimals }
601     if (FieldType = blr_short) and (FieldScale < 0) then
602     Column := Column + Format('NUMERIC(4, %d)', [-FieldScale])
603     else
604     if (FieldType = blr_long) and (FieldScale < 0) then
605     Column := Column + Format('NUMERIC(9, %d)', [-FieldScale])
606     else
607     if (FieldType = blr_double) and (FieldScale < 0) then
608     Column := Column + Format('NUMERIC(15, %d)', [-FieldScale])
609     else
610     Column := Column + ColumnTypes[i].TypeName;
611     end;
612     end;
613     end;
614     if FieldType in [blr_text, blr_varying] then
615     if qryTables.FieldByName('RDB$CHARACTER_LENGTH').IsNull then
616     Column := Column + Format('(%d)', [qryTables.FieldByName('RDB$FIELD_LENGTH').AsInteger])
617     else
618     Column := Column + Format('(%d)', [qryTables.FieldByName('RDB$CHARACTER_LENGTH').AsInteger]);
619    
620     { Catch arrays after printing the type }
621    
622     if not qryTables.FieldByName('RDB$DIMENSIONS').IsNull and (qryTables.FieldByName('RDB$DIMENSIONS').AsInteger > 0) then
623     Column := column + GetArrayField(qryTables.FieldByName('RDB$FIELD_SOURCE').AsString);
624    
625     if FieldType = blr_blob then
626     begin
627     subtype := qryTables.FieldByName('RDB$FIELD_SUB_TYPE').AsShort;
628     Column := Column + ' SUB_TYPE ';
629     if (subtype > 0) and (subtype <= MAXSUBTYPES) then
630     Column := Column + SubTypes[subtype]
631     else
632     Column := Column + IntToStr(subtype);
633     column := Column + Format(' SEGMENT SIZE %d',
634     [qryTables.FieldByName('RDB$SEGMENT_LENGTH').AsInteger]);
635     end;
636    
637     { International character sets }
638     if ((FieldType in [blr_text, blr_varying]) or
639     (FieldType = blr_blob)) and
640     (not qryTables.FieldByName('RDB$CHARACTER_SET_ID').IsNull) and
641     (qryTables.FieldByName('RDB$CHARACTER_SET_ID').AsInteger <> 0) then
642     begin
643     { Override rdb$fields id with relation_fields if present }
644    
645     CharSetId := 0;
646     if not qryTables.FieldByName('RDB$CHARACTER_SET_ID').IsNull then
647     CharSetId := qryTables.FieldByName('RDB$CHARACTER_SET_ID').AsInteger;
648    
649     Column := Column + GetCharacterSets(CharSetId, 0, false);
650     intchar := 1;
651     end;
652     end;
653    
654     {Firebird 3 introduces IDENTITY columns. We need to check for them here}
655     if qryTables.HasField('RDB$GENERATOR_NAME') and not qryTables.FieldByName('RDB$GENERATOR_NAME').IsNull then
656     begin
657     qryGenerators.ParamByName('GENERATOR').AsString := qryTables.FieldByName('RDB$GENERATOR_NAME').AsString;
658     qryGenerators.ExecQuery;
659     if not qryGenerators.Eof then
660     begin
661     Column := Column + Format(' GENERATED BY DEFAULT AS IDENTITY (START WITH %d)',
662     [qryGenerators.FieldByName('RDB$INITIAL_VALUE').AsInteger]);
663     end;
664     qryGenerators.Close;
665     end;
666    
667     { Handle defaults for columns }
668     { Originally This called PrintMetadataTextBlob,
669     should no longer need }
670     if not qryTables.FieldByName('RDB$DEFAULT_SOURCE').IsNull then
671     Column := Column + ' ' + qryTables.FieldByName('RDB$DEFAULT_SOURCE').AsString;
672    
673    
674     { The null flag is either 1 or null (for nullable) . if there is
675     a constraint name, print that too. Domains cannot have named
676     constraints. The column name is in rdb$trigger_name in
677     rdb$check_constraints. We hope we get at most one row back. }
678    
679     if qryTables.FieldByName('RDB$NULL_FLAG').AsInteger = 1 then
680     begin
681     qryConstraints.Params.ByName('FIELDNAME').AsString := qryTables.FieldByName('RDB$FIELD_NAME').AsString;
682     qryConstraints.Params.ByName('RELATIONNAME').AsString := qryTables.FieldByName('RDB$RELATION_NAME').AsString;
683     qryConstraints.ExecQuery;
684    
685     while not qryConstraints.Eof do
686     begin
687     if Pos('INTEG', qryConstraints.FieldByName('RDB$CONSTRAINT_NAME').AsString) <> 1 then
688     Column := Column + Format(' CONSTRAINT %s',
689     [ QuoteIdentifier(
690     qryConstraints.FieldByName('RDB$CONSTRAINT_NAME').AsString)]);
691     qryConstraints.Next;
692     end;
693     qryConstraints.Close;
694     Column := Column + ' NOT NULL';
695     end;
696    
697     if ((FieldType in [blr_text, blr_varying]) or
698     (FieldType = blr_blob)) and
699     (not qryTables.FieldByName('RDB$CHARACTER_SET_ID').IsNull) and
700     (qryTables.FieldByName('RDB$CHARACTER_SET_ID').AsInteger <> 0) and
701     (intchar <> 0) then
702     begin
703     Collation := 0;
704     if not qryTables.FieldByName('RDB$COLLATION_ID1').IsNull then
705     Collation := qryTables.FieldByName('RDB$COLLATION_ID1').AsInteger
706     else
707     if not qryTables.FieldByName('RDB$COLLATION_ID').IsNull then
708     Collation := qryTables.FieldByName('RDB$COLLATION_ID').AsInteger;
709    
710     CharSetId := 0;
711     if not qryTables.FieldByName('RDB$CHARACTER_SET_ID').IsNull then
712     CharSetId := qryTables.FieldByName('RDB$CHARACTER_SET_ID').AsInteger;
713    
714     if Collation <> 0 then
715     Column := Column + GetCharacterSets(CharSetId, Collation, true);
716     end;
717     end;
718     qryTables.Next;
719     if not qryTables.Eof then
720     Column := Column + ',';
721     FMetaData.Add(Column);
722     end;
723    
724     { Do primary and unique keys only. references come later }
725    
726     qryRelConstraints.Params.ByName('relationname').AsString := RelationName;
727     qryRelConstraints.ExecQuery;
728     while not qryRelConstraints.Eof do
729     begin
730     Constraint := '';
731     FMetaData.Strings[FMetaData.Count - 1] := FMetaData.Strings[FMetaData.Count - 1] + ',';
732     { If the name of the constraint is not INTEG..., print it }
733     if Pos('INTEG', qryRelConstraints.FieldByName('RDB$CONSTRAINT_NAME').AsString) <> 1 then
734     Constraint := Constraint + 'CONSTRAINT ' +
735     QuoteIdentifier(
736     qryRelConstraints.FieldByName('RDB$CONSTRAINT_NAME').AsString);
737    
738    
739     if Pos('PRIMARY', qryRelConstraints.FieldByName('RDB$CONSTRAINT_TYPE').AsString) = 1 then
740     begin
741     FMetaData.Add(Constraint + Format(' PRIMARY KEY (%s)',
742     [GetIndexSegments(qryRelConstraints.FieldByName('RDB$INDEX_NAME').AsString)]));
743     end
744     else
745     if Pos('UNIQUE', qryRelConstraints.FieldByName('RDB$CONSTRAINT_TYPE').AsString) = 1 then
746     begin
747     FMetaData.Add(Constraint + Format(' UNIQUE (%s)',
748     [GetIndexSegments(qryRelConstraints.FieldByName('RDB$INDEX_NAME').AsString)]));
749     end;
750     qryRelConstraints.Next;
751     end;
752     if ValidRelation then
753     begin
754     if TableType = 4 then
755     FMetaData.Add(' ) ON COMMIT PRESERVE ROWS ' + TERM)
756     else
757     FMetaData.Add(')' + TERM);
758     end;
759     finally
760     qryTables.Free;
761     qryPrecision.Free;
762     qryConstraints.Free;
763     qryRelConstraints.Free;
764     qryGenerators.Free;
765     end;
766     end;
767    
768     { ExtractListView
769     Functional description
770     Show text of the specified view.
771     Use a SQL query to get the info and print it.
772     Note: This should also contain check option }
773    
774     procedure TIBExtract.ExtractListView(ViewName: String);
775     const
776     ViewsSQL = 'SELECT * FROM RDB$RELATIONS REL ' +
777     ' WHERE ' +
778     ' (REL.RDB$SYSTEM_FLAG <> 1 OR REL.RDB$SYSTEM_FLAG IS NULL) AND ' +
779     ' NOT REL.RDB$VIEW_BLR IS NULL AND ' +
780     ' REL.RDB$RELATION_NAME = :VIEWNAME AND ' +
781     ' REL.RDB$FLAGS = 1 ' +
782     'ORDER BY REL.RDB$RELATION_ID ';
783    
784     ColumnsSQL = 'SELECT * FROM RDB$RELATION_FIELDS RFR ' +
785     'WHERE ' +
786     ' RFR.RDB$RELATION_NAME = :RELATIONNAME ' +
787     'ORDER BY RFR.RDB$FIELD_POSITION ';
788    
789     var
790     qryViews, qryColumns : TIBSQL;
791     RelationName, ColList : String;
792     begin
793     ColList := '';
794     qryViews := TIBSQL.Create(FDatabase);
795     qryColumns := TIBSQL.Create(FDatabase);
796     try
797     qryViews.SQL.Add(ViewsSQL);
798     qryViews.Params.ByName('viewname').AsString := ViewName;
799     qryViews.ExecQuery;
800     while not qryViews.Eof do
801     begin
802     FMetaData.Add('');
803     RelationName := QuoteIdentifier(
804     qryViews.FieldByName('RDB$RELATION_NAME').AsString);
805     FMetaData.Add(Format('%s/* View: %s, Owner: %s */%s', [
806     RelationName,
807     Trim(qryViews.FieldByName('RDB$OWNER_NAME').AsString)]));
808     FMetaData.Add('');
809     FMetaData.Add(Format('CREATE VIEW %s (', [RelationName]));
810    
811     { Get Column List}
812     qryColumns.SQL.Add(ColumnsSQL);
813     qryColumns.Params.ByName('relationname').AsString := RelationName;
814     qryColumns.ExecQuery;
815     while not qryColumns.Eof do
816     begin
817     ColList := ColList + QuoteIdentifier(
818     qryColumns.FieldByName('RDB$FIELD_NAME').AsString);
819     qryColumns.Next;
820     if not qryColumns.Eof then
821     ColList := ColList + ', ';
822     end;
823     FMetaData.Add(ColList + ') AS');
824     FMetaData.Add(qryViews.FieldByName('RDB$VIEW_SOURCE').AsString + Term);
825     qryViews.Next;
826     end;
827     finally
828     qryViews.Free;
829     qryColumns.Free;
830     end;
831     end;
832    
833     function TIBExtract.GetCharacterSets(CharSetId, Collation: integer;
834     CollateOnly: Boolean): String;
835     var
836     CharSetSQL : TIBSQL;
837     DidActivate : Boolean;
838     begin
839     Result := '';
840     if not FTransaction.Active then
841     begin
842     FTransaction.StartTransaction;
843     DidActivate := true;
844     end
845     else
846     DidActivate := false;
847     CharSetSQL := TIBSQL.Create(FDatabase);
848     try
849     if Collation <> 0 then
850     begin
851     CharSetSQL.SQL.Add(CollationSQL);
852     CharSetSQL.Params.ByName('Char_Set_Id').AsInteger := CharSetId;
853     CharSetSQL.Params.ByName('Collation').AsInteger := Collation;
854     CharSetSQL.ExecQuery;
855    
856     { Is specified collation the default collation for character set? }
857     if (Trim(CharSetSQL.FieldByName('RDB$DEFAULT_COLLATE_NAME').AsString) =
858     Trim(CharSetSQL.FieldByName('RDB$COLLATION_NAME').AsString)) then
859     begin
860     if not CollateOnly then
861     Result := ' CHARACTER SET ' + Trim(CharSetSQL.FieldByName('RDB$CHARACTER_SET_NAME').AsString);
862     end
863     else
864     if CollateOnly then
865     Result := ' COLLATE ' + Trim(CharSetSQL.FieldByName('RDB$COLLATION_NAME').AsString)
866     else
867     Result := ' CHARACTER SET ' +
868     Trim(CharSetSQL.FieldByName('RDB$CHARACTER_SET_NAME').AsString) +
869     ' COLLATE ' +
870     Trim(CharSetSQL.FieldByName('RDB$COLLATION_NAME').AsString);
871     end
872     else
873     if CharSetId <> 0 then
874     begin
875     CharSetSQL.SQL.Add(NonCollationSQL);
876     CharSetSQL.Params.ByName('CharSetId').AsShort := CharSetId;
877     CharSetSQL.ExecQuery;
878     Result := ' CHARACTER SET ' + Trim(CharSetSQL.FieldByName('RDB$CHARACTER_SET_NAME').AsString);
879     end;
880     finally
881     CharSetSQL.Free;
882     end;
883     if DidActivate then
884     FTransaction.Commit;
885     end;
886    
887     procedure TIBExtract.Add2MetaData(const Msg: string; IsError: boolean);
888     begin
889     FMetaData.Add(Msg);
890     end;
891    
892     function TIBExtract.GetDatabase: TIBDatabase;
893     begin
894     result := FDatabase;
895     end;
896    
897     { GetIndexSegments
898     Functional description
899     returns the list of columns in an index. }
900    
901     function TIBExtract.GetIndexSegments(indexname: String): String;
902     const
903     IndexNamesSQL =
904     'SELECT * FROM RDB$INDEX_SEGMENTS SEG ' +
905     'WHERE SEG.RDB$INDEX_NAME = :INDEXNAME ' +
906     'ORDER BY SEG.RDB$FIELD_POSITION';
907    
908     var
909     qryColNames : TIBSQL;
910     begin
911     { Query to get column names }
912     Result := '';
913     qryColNames := TIBSQL.Create(FDatabase);
914     try
915     qryColNames.SQL.Add(IndexNamesSQL);
916     qryColNames.Params.ByName('IndexName').AsString := IndexName;
917     qryColNames.ExecQuery;
918     while not qryColNames.Eof do
919     begin
920     { Place a comma and a blank between each segment column name }
921    
922     Result := Result + QuoteIdentifier(
923     qryColNames.FieldByName('RDB$FIELD_NAME').AsString);
924     qryColNames.Next;
925     if not qryColNames.Eof then
926     Result := Result + ', ';
927     end;
928     finally
929     qryColNames.Free;
930     end;
931     end;
932    
933     function TIBExtract.GetTransaction: TIBTransaction;
934     begin
935     Result := FTransaction;
936     end;
937    
938     function TIBExtract.GetTriggerType(TypeID: Int64): string;
939     const
940     AllDDLTriggers = $7FFFFFFFFFFFDFFF shr 1;
941     var separator: string;
942     i: integer;
943    
944     function GetDDLEvent(Phase: TTriggerPhase; ObjectName: string): string;
945     begin
946     Result := '';
947     case Phase of
948     tpCreate:
949     Result := separator + 'CREATE ' + ObjectName;
950     tpAlter:
951     Result := separator + 'ALTER ' + ObjectName;
952     tpDrop:
953     Result := separator + 'Drop ' + ObjectName;
954     end;
955     if Result <> '' then
956     separator := ' OR ';
957     end;
958    
959     begin
960     if TypeID and $2000 <> 0 then
961     {database trigger}
962     begin
963     Result := 'ON ';
964     case TypeID of
965     $2000:
966     Result += 'CONNECT';
967     $2001:
968     Result += 'DISCONNECT';
969     $2002:
970     Result +='TRANSACTION START';
971     $2003:
972     Result += 'TRANSACTION COMMIT';
973     $2004:
974     Result += 'TRANSACTION ROLLBACK';
975     end;
976     end
977     else
978     if TypeID and $4000 <> 0 then
979     {DDL Trigger}
980     begin
981     if TypeID and $01 <> 0 then
982     Result := 'AFTER '
983     else
984     Result := 'BEFORE ';
985     TypeID := TypeID shr 1;
986     separator := '';
987     i := 0;
988     if TypeID = AllDDLTriggers then
989     Result += 'ANY DDL STATEMENT'
990     else
991     repeat
992     if (DDLTriggers[i].Bits > 0) and (TypeID and $01 <> 0) then
993     Result += GetDDLEvent(DDLTriggers[i].Bit1,DDLTriggers[i].ObjectName);
994    
995     if (DDLTriggers[i].Bits > 1) and (TypeID and $02 <> 0) then
996     Result += GetDDLEvent(DDLTriggers[i].Bit2,DDLTriggers[i].ObjectName);
997    
998     if (DDLTriggers[i].Bits > 2) and (TypeID and $04 <> 0) then
999     Result += GetDDLEvent(DDLTriggers[i].Bit3,DDLTriggers[i].ObjectName);
1000     TypeID := TypeID shr DDLTriggers[i].Bits;
1001     Inc(i);
1002     until TypeID = 0;
1003     end
1004     else
1005     {Normal Trigger}
1006     begin
1007     Inc(TypeID);
1008     if TypeID and $01 <> 0 then
1009     Result := 'AFTER '
1010     else
1011     Result := 'BEFORE ';
1012     TypeID := TypeID shr 1;
1013     separator := '';
1014     repeat
1015     Result += separator;
1016     separator := ' or ';
1017     case TypeID and $03 of
1018     1:
1019     Result += 'INSERT';
1020     2:
1021     Result += 'UPDATE';
1022     3:
1023     Result += 'DELETE';
1024     end;
1025     TypeID := TypeID shr 2;
1026     until TypeID = 0
1027     end
1028     end;
1029    
1030     { ListAllGrants
1031     Functional description
1032     Print the permissions on all user tables.
1033     Get separate permissions on table/views and then procedures }
1034    
1035     procedure TIBExtract.ListGrants(ExtractTypes: TExtractTypes);
1036     const
1037     SecuritySQL = 'SELECT * FROM RDB$RELATIONS ' +
1038     'WHERE ' +
1039     ' (RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL) AND ' +
1040     ' RDB$SECURITY_CLASS STARTING WITH ''SQL$'' ' +
1041     'ORDER BY RDB$RELATION_NAME';
1042    
1043     DomainSQL = 'select RDB$FIELD_NAME from RDB$FIELDS '+
1044     'where RDB$SYSTEM_FLAG <> 1 and RDB$FIELD_NAME not Similar to ''RDB$%|SEC$%|MON$%|SQL$%'' '+
1045     'order BY RDB$FIELD_NAME';
1046    
1047     CharacterSetSQL = 'Select * From RDB$CHARACTER_SETS '+
1048     'Where RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL ' +
1049     'Order by RDB$CHARACTER_SET_NAME';
1050    
1051     CollationsSQL = 'Select * From RDB$COLLATIONS '+
1052     'Where RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL ' +
1053     'Order by RDB$COLLATION_NAME';
1054    
1055     ProcedureSQL = 'select * from RDB$PROCEDURES '+
1056     'Where RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL ' +
1057     'Order BY RDB$PROCEDURE_NAME';
1058    
1059     ExceptionSQL = 'select * from RDB$EXCEPTIONS '+
1060     'Where RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL ' +
1061     'Order BY RDB$EXCEPTION_NAME';
1062    
1063     GeneratorSQL = 'select * from RDB$GENERATORS '+
1064     'Where RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL ' +
1065     'Order BY RDB$GENERATOR_NAME';
1066    
1067     MetadataGrantsSQL =
1068     'Select PR.RDB$USER, PR.RDB$USER_TYPE, '+
1069     'T.RDB$TYPE_NAME as USER_TYPE_NAME, '+
1070     'Case PR.RDB$PRIVILEGE '+
1071     ' When ''C'' then ''CREATE'' '+
1072     ' When ''O'' then ''DROP ANY'' '+
1073     ' When ''L'' then ''ALTER ANY'' End as Privilege, '+
1074     'Case PR.RDB$RELATION_NAME '+
1075     ' When ''SQL$COLLATIONS'' then ''COLLATION'' '+
1076     ' When ''SQL$CHARSETS'' then ''CHARACTER SET'' '+
1077     ' When ''SQL$DATABASE'' then ''DATABASE'' '+
1078     ' When ''SQL$DOMAINS'' then ''DOMAIN'' '+
1079     ' When ''SQL$EXCEPTIONS'' then ''EXCEPTION'' '+
1080     ' When ''SQL$FILTERS'' then ''FILTER'' '+
1081     ' When ''SQL$FUNCTIONS'' then ''FUNCTION'' '+
1082     ' When ''SQL$GENERATORS'' then ''GENERATOR'' '+
1083     ' When ''SQL$PACKAGES'' then ''PACKAGE'' '+
1084     ' When ''SQL$PROCEDURES'' then ''PROCEDURE'' '+
1085     ' When ''SQL$ROLES'' then ''ROLE'' '+
1086     ' When ''SQL$VIEWS'' then ''VIEW'' '+
1087     ' When ''SQL$TABLES'' then ''TABLE'' End as METAOBJECTNAME,'+
1088    
1089     'case when coalesce(RDB$GRANT_OPTION,0) <> 0 then '' WITH GRANT OPTION'' '+
1090     'ELSE '''' End as GRANTOPTION '+
1091     'FROM RDB$USER_PRIVILEGES PR '+
1092     'JOIN RDB$TYPES T On T.RDB$TYPE = PR.RDB$USER_TYPE and T.RDB$FIELD_NAME = ''RDB$OBJECT_TYPE'' '+
1093     'Where PR.RDB$RELATION_NAME like ''SQL$%'' and PR.RDB$PRIVILEGE in (''L'',''C'',''O'')';
1094    
1095     var
1096     qryRoles : TIBSQL;
1097     RelationName : String;
1098     begin
1099     ListRoles;
1100     qryRoles := TIBSQL.Create(FDatabase);
1101     try
1102     { This version of cursor gets only sql tables identified by security class
1103     and misses views, getting only null view_source }
1104    
1105     FMetaData.Add('');
1106     FMetaData.Add('/* Grant permissions for this database */');
1107     FMetaData.Add('');
1108    
1109     try
1110     qryRoles.SQL.Text := SecuritySQL;
1111     qryRoles.ExecQuery;
1112     while not qryRoles.Eof do
1113     begin
1114     RelationName := Trim(qryRoles.FieldByName('rdb$relation_Name').AsString);
1115     ShowGrants(RelationName, Term, not (etGrantsToUser in ExtractTypes));
1116     qryRoles.Next;
1117     end;
1118     finally
1119     qryRoles.Close;
1120     end;
1121    
1122     if etGrantsToUser in ExtractTypes then
1123     ShowGrantRoles(Term);
1124    
1125     if FDatabaseInfo.ODSMajorVersion >= ODS_VERSION12 then
1126     begin
1127     qryRoles.SQL.Text := DomainSQL;
1128     qryRoles.ExecQuery;
1129     try
1130     while not qryRoles.Eof do
1131     begin
1132     ShowGrants(Trim(qryRoles.FieldByName('RDB$FIELD_NAME').AsString), Term,
1133     not (etGrantsToUser in ExtractTypes));
1134     qryRoles.Next;
1135     end;
1136     finally
1137     qryRoles.Close;
1138     end;
1139    
1140     qryRoles.SQL.Text := CharacterSetSQL;
1141     qryRoles.ExecQuery;
1142     try
1143     while not qryRoles.Eof do
1144     begin
1145     ShowGrants(Trim(qryRoles.FieldByName('RDB$CHARACTER_SET_NAME').AsString), Term,
1146     not (etGrantsToUser in ExtractTypes));
1147     qryRoles.Next;
1148     end;
1149     finally
1150     qryRoles.Close;
1151     end;
1152    
1153     qryRoles.SQL.Text := CollationsSQL;
1154     qryRoles.ExecQuery;
1155     try
1156     while not qryRoles.Eof do
1157     begin
1158     ShowGrants(Trim(qryRoles.FieldByName('RDB$COLLATION_NAME').AsString), Term,
1159     not (etGrantsToUser in ExtractTypes));
1160     qryRoles.Next;
1161     end;
1162     finally
1163     qryRoles.Close;
1164     end;
1165    
1166     qryRoles.SQL.Text := ExceptionSQL;
1167     qryRoles.ExecQuery;
1168     try
1169     while not qryRoles.Eof do
1170     begin
1171     ShowGrants(Trim(qryRoles.FieldByName('RDB$EXCEPTION_NAME').AsString), Term,
1172     not (etGrantsToUser in ExtractTypes));
1173     qryRoles.Next;
1174     end;
1175     finally
1176     qryRoles.Close;
1177     end;
1178    
1179     qryRoles.SQL.Text := GeneratorSQL;
1180     qryRoles.ExecQuery;
1181     try
1182     while not qryRoles.Eof do
1183     begin
1184     ShowGrants(Trim(qryRoles.FieldByName('RDB$GENERATOR_NAME').AsString), Term,
1185     not (etGrantsToUser in ExtractTypes));
1186     qryRoles.Next;
1187     end;
1188     finally
1189     qryRoles.Close;
1190     end;
1191     end;
1192    
1193     qryRoles.SQL.Text := ProcedureSQL;
1194     qryRoles.ExecQuery;
1195     try
1196     while not qryRoles.Eof do
1197     begin
1198     ShowGrants(Trim(qryRoles.FieldByName('RDB$PROCEDURE_NAME').AsString), Term,
1199     not (etGrantsToUser in ExtractTypes));
1200     qryRoles.Next;
1201     end;
1202     finally
1203     qryRoles.Close;
1204     end;
1205    
1206     {Metadata Grants}
1207     if FDatabaseInfo.ODSMajorVersion >= ODS_VERSION12 then
1208     begin
1209     qryRoles.SQL.Text := MetadataGrantsSQL;
1210     qryRoles.ExecQuery;
1211     while not qryRoles.Eof do
1212     begin
1213     if (etGrantsToUser in ExtractTypes) or
1214     (qryRoles.FieldByName('RDB$USER_TYPE').AsInteger <> obj_user) or
1215     (qryRoles.FieldByName('RDB$USER').AsString = 'PUBLIC') then
1216     FMetaData.Add(Format('GRANT %s %s TO %s %s %s%s', [
1217     qryRoles.FieldByName('Privilege').AsString,
1218     qryRoles.FieldByName('METAOBJECTNAME').AsString,
1219     qryRoles.FieldByName('USER_TYPE_NAME').AsString,
1220     QuoteIdentifier(qryRoles.FieldByName('RDB$USER').AsString),
1221     qryRoles.FieldByName('GRANTOPTION').AsString,
1222     Term]));
1223     qryRoles.Next;
1224     end;
1225     qryRoles.Close;
1226     end;
1227     finally
1228     qryRoles.Free;
1229     end;
1230     end;
1231    
1232     { ListAllProcs
1233     Functional description
1234     Shows text of a stored procedure given a name.
1235     or lists procedures if no argument.
1236     Since procedures may reference each other, we will create all
1237     dummy procedures of the correct name, then alter these to their
1238     correct form.
1239     Add the parameter names when these procedures are created.
1240    
1241     procname -- Name of procedure to investigate }
1242    
1243     procedure TIBExtract.ListProcs(ProcDDLType: TProcDDLType;
1244     ProcedureName: String; IncludeGrants: boolean);
1245     const
1246     CreateProcedureStr1 = 'CREATE PROCEDURE %s ';
1247     CreateProcedureStr2 = 'BEGIN EXIT; END %s%s';
1248     ProcedureSQL = {Order procedures by dependency order and then procedure name}
1249     'with recursive Procs as ( ' +
1250     'Select RDB$PROCEDURE_NAME, 1 as ProcLevel from RDB$PROCEDURES ' +
1251     'UNION ALL ' +
1252     'Select D.RDB$DEPENDED_ON_NAME, ProcLevel + 1 From RDB$DEPENDENCIES D ' +
1253     'JOIN Procs on Procs.RDB$PROCEDURE_NAME = D.RDB$DEPENDENT_NAME ' +
1254     ' and Procs.RDB$PROCEDURE_NAME <> D.RDB$DEPENDED_ON_NAME ' +
1255     'JOIN RDB$PROCEDURES P On P.RDB$PROCEDURE_NAME = D.RDB$DEPENDED_ON_NAME ' +
1256     ' ) ' +
1257     'SELECT * FROM RDB$PROCEDURES P ' +
1258     'JOIN ( ' +
1259     'Select RDB$PROCEDURE_NAME, max(ProcLevel) as ProcLevel From Procs ' +
1260     'Group By RDB$PROCEDURE_NAME) A On A.RDB$PROCEDURE_NAME = P.RDB$PROCEDURE_NAME ' +
1261     'Order by A.ProcLevel desc, P.RDB$PROCEDURE_NAME asc';
1262    
1263     ProcedureNameSQL =
1264     'SELECT * FROM RDB$PROCEDURES ' +
1265     'WHERE RDB$PROCEDURE_NAME = :ProcedureName ' +
1266     'ORDER BY RDB$PROCEDURE_NAME';
1267    
1268     var
1269     qryProcedures : TIBSQL;
1270     ProcName : String;
1271     SList : TStrings;
1272     Header : Boolean;
1273    
1274     begin
1275    
1276     Header := true;
1277     qryProcedures := TIBSQL.Create(FDatabase);
1278     SList := TStringList.Create;
1279     try
1280     if ProcedureName = '' then
1281     qryProcedures.SQL.Text := ProcedureSQL
1282     else
1283     begin
1284     qryProcedures.SQL.Text := ProcedureNameSQL;
1285     qryProcedures.Params.ByName('ProcedureName').AsString := ProcedureName;
1286     end;
1287    
1288     qryProcedures.ExecQuery;
1289     while not qryProcedures.Eof do
1290     begin
1291     if Header then
1292     begin
1293     FMetaData.Add('COMMIT WORK;');
1294     FMetaData.Add('SET AUTODDL OFF;');
1295     FMetaData.Add(Format('SET TERM %s %s', [ProcTerm, Term]));
1296     FMetaData.Add(Format('%s/* Stored procedures */%s', [LineEnding, LineEnding]));
1297     Header := false;
1298     end;
1299     ProcName := Trim(qryProcedures.FieldByName('RDB$PROCEDURE_NAME').AsString);
1300    
1301     case ProcDDLType of
1302     pdCreateStub:
1303     begin
1304     FMetaData.Add(Format(CreateProcedureStr1, [QuoteIdentifier(
1305     ProcName)]));
1306     GetProcedureArgs(ProcName);
1307     FMetaData.Add(Format(CreateProcedureStr2, [ProcTerm, LineEnding]));
1308     end;
1309    
1310     pdCreateProc:
1311     begin
1312     FMetaData.Add(Format(CreateProcedureStr1, [QuoteIdentifier(
1313     ProcName)]));
1314     GetProcedureArgs(ProcName);
1315     if not qryProcedures.FieldByName('RDB$PROCEDURE_SOURCE').IsNull then
1316     begin
1317     SList.Text := qryProcedures.FieldByName('RDB$PROCEDURE_SOURCE').AsString;
1318     SList.Add(Format(' %s%s', [ProcTerm, LineEnding]));
1319     FMetaData.AddStrings(SList);
1320     end
1321     else
1322     FMetaData.Add(Format(CreateProcedureStr2, [ProcTerm, LineEnding]));
1323     end;
1324    
1325     pdAlterProc:
1326     begin
1327     FMetaData.Add(Format('%sALTER PROCEDURE %s ', [LineEnding,
1328     QuoteIdentifier( ProcName)]));
1329     GetProcedureArgs(ProcName);
1330    
1331     if not qryProcedures.FieldByName('RDB$PROCEDURE_SOURCE').IsNull then
1332     begin
1333     SList.Text := qryProcedures.FieldByName('RDB$PROCEDURE_SOURCE').AsString;
1334     SList.Add(Format(' %s%s', [ProcTerm, LineEnding]));
1335     FMetaData.AddStrings(SList);
1336     end
1337     else
1338     FMetaData.Add(Format(CreateProcedureStr2, [ProcTerm, LineEnding]));
1339     end;
1340     end;
1341     if IncludeGrants then
1342     ShowGrantsTo(ProcName,obj_procedure,ProcTerm);
1343     qryProcedures.Next;
1344     end;
1345     qryProcedures.Close;
1346    
1347     if not Header then
1348     begin
1349     FMetaData.Add(Format('SET TERM %s %s', [Term, ProcTerm]));
1350     FMetaData.Add('COMMIT WORK;');
1351     FMetaData.Add('SET AUTODDL ON;');
1352     end;
1353     finally
1354     qryProcedures.Free;
1355     SList.Free;
1356     end;
1357     end;
1358    
1359     { ListAllTables
1360     Functional description
1361     Extract the names of all user tables from
1362     rdb$relations. Filter SQL tables by
1363     security class after we fetch them
1364     Parameters: flag -- 0, get all tables }
1365    
1366     procedure TIBExtract.ListAllTables(flag: Boolean);
1367     const
1368     TableSQL =
1369     'SELECT * FROM RDB$RELATIONS ' +
1370     'WHERE ' +
1371     ' (RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL) AND ' +
1372     ' RDB$VIEW_BLR IS NULL ' +
1373     'ORDER BY RDB$RELATION_NAME';
1374    
1375     var
1376     qryTables : TIBSQL;
1377     begin
1378     { This version of cursor gets only sql tables identified by security class
1379     and misses views, getting only null view_source }
1380    
1381     qryTables := TIBSQL.Create(FDatabase);
1382     try
1383     qryTables.SQL.Text := TableSQL;
1384     qryTables.ExecQuery;
1385     while not qryTables.Eof do
1386     begin
1387     if ((qryTables.FieldByName('RDB$FLAGS').AsInteger <> 1) and
1388     (not Flag)) then
1389     continue;
1390     if flag or (Pos('SQL$', qryTables.FieldByName('RDB$SECURITY_CLASS').AsString) <> 1) then
1391     ExtractListTable(qryTables.FieldByName('RDB$RELATION_NAME').AsString,
1392     '', false);
1393    
1394     qryTables.Next;
1395     end;
1396     finally
1397     qryTables.Free;
1398     end;
1399     end;
1400    
1401     { ListAllTriggers
1402     Functional description
1403     Lists triggers in general on non-system
1404     tables with sql source only. }
1405    
1406     procedure TIBExtract.ListTriggers(ObjectName: String; ExtractTypes: TExtractTypes
1407     );
1408     const
1409     { Query gets the trigger info for non-system triggers with
1410     source that are not part of an SQL constraint }
1411    
1412     TriggerSQL =
1413     'SELECT * FROM RDB$TRIGGERS TRG Left Outer JOIN RDB$RELATIONS REL ON ' +
1414     ' TRG.RDB$RELATION_NAME = REL.RDB$RELATION_NAME ' +
1415     'WHERE ' +
1416     ' (REL.RDB$SYSTEM_FLAG <> 1 OR REL.RDB$SYSTEM_FLAG IS NULL) AND ' +
1417     ' NOT EXISTS (SELECT * FROM RDB$CHECK_CONSTRAINTS CHK WHERE ' +
1418     ' TRG.RDB$TRIGGER_NAME = CHK.RDB$TRIGGER_NAME) ' +
1419     'ORDER BY TRG.RDB$RELATION_NAME, TRG.RDB$TRIGGER_TYPE, ' +
1420     ' TRG.RDB$TRIGGER_SEQUENCE, TRG.RDB$TRIGGER_NAME';
1421    
1422     TriggerNameSQL =
1423     'SELECT * FROM RDB$TRIGGERS TRG JOIN RDB$RELATIONS REL ON ' +
1424     ' TRG.RDB$RELATION_NAME = REL.RDB$RELATION_NAME ' +
1425     'WHERE ' +
1426     ' REL.RDB$RELATION_NAME = :TableName AND ' +
1427     ' (REL.RDB$SYSTEM_FLAG <> 1 OR REL.RDB$SYSTEM_FLAG IS NULL) AND ' +
1428     ' NOT EXISTS (SELECT * FROM RDB$CHECK_CONSTRAINTS CHK WHERE ' +
1429     ' TRG.RDB$TRIGGER_NAME = CHK.RDB$TRIGGER_NAME) ' +
1430     'ORDER BY TRG.RDB$RELATION_NAME, TRG.RDB$TRIGGER_TYPE, ' +
1431     ' TRG.RDB$TRIGGER_SEQUENCE, TRG.RDB$TRIGGER_NAME';
1432    
1433     TriggerByNameSQL =
1434     'SELECT * FROM RDB$TRIGGERS TRG Left Outer JOIN RDB$RELATIONS REL ON ' +
1435     ' TRG.RDB$RELATION_NAME = REL.RDB$RELATION_NAME ' +
1436     'WHERE ' +
1437     ' TRG.RDB$TRIGGER_NAME = :TriggerName AND ' +
1438     ' (REL.RDB$SYSTEM_FLAG <> 1 OR REL.RDB$SYSTEM_FLAG IS NULL) AND ' +
1439     ' NOT EXISTS (SELECT * FROM RDB$CHECK_CONSTRAINTS CHK WHERE ' +
1440     ' TRG.RDB$TRIGGER_NAME = CHK.RDB$TRIGGER_NAME) ' +
1441     'ORDER BY TRG.RDB$RELATION_NAME, TRG.RDB$TRIGGER_TYPE, ' +
1442     ' TRG.RDB$TRIGGER_SEQUENCE, TRG.RDB$TRIGGER_NAME';
1443    
1444     var
1445     Header : Boolean;
1446     TriggerName, RelationName, InActive: String;
1447     qryTriggers : TIBSQL;
1448     SList : TStrings;
1449     begin
1450     Header := true;
1451     SList := TStringList.Create;
1452     qryTriggers := TIBSQL.Create(FDatabase);
1453     try
1454     if ObjectName = '' then
1455     qryTriggers.SQL.Text := TriggerSQL
1456     else
1457     begin
1458     if etTable in ExtractTypes then
1459     begin
1460     qryTriggers.SQL.Text := TriggerNameSQL;
1461     qryTriggers.Params.ByName('TableName').AsString := ObjectName;
1462     end
1463     else
1464     begin
1465     qryTriggers.SQL.Text := TriggerByNameSQL;
1466     qryTriggers.Params.ByName('TriggerName').AsString := ObjectName;
1467     end;
1468     end;
1469     qryTriggers.ExecQuery;
1470     while not qryTriggers.Eof do
1471     begin
1472     SList.Clear;
1473     if Header then
1474     begin
1475     FMetaData.Add(Format('SET TERM %s %s%s', [Procterm, Term, LineEnding]));
1476     FMetaData.Add(Format('%s/* Triggers only will work for SQL triggers */%s',
1477     [LineEnding, LineEnding]));
1478     Header := false;
1479     end;
1480     TriggerName := qryTriggers.FieldByName('RDB$TRIGGER_NAME').AsString;
1481     RelationName := qryTriggers.FieldByName('RDB$RELATION_NAME').AsString;
1482     if qryTriggers.FieldByName('RDB$TRIGGER_INACTIVE').IsNull then
1483     InActive := 'INACTIVE'
1484     else
1485     if qryTriggers.FieldByName('RDB$TRIGGER_INACTIVE').AsInteger = 1 then
1486     InActive := 'INACTIVE'
1487     else
1488     InActive := 'ACTIVE';
1489    
1490     if qryTriggers.FieldByName('RDB$FLAGS').AsInteger <> 1 then
1491     SList.Add('/* ');
1492    
1493     {Database or Transaction trigger}
1494     SList.Add(Format('CREATE TRIGGER %s%s%s %s POSITION %d',
1495     [QuoteIdentifier( TriggerName),
1496     LineEnding, InActive,
1497     GetTriggerType(qryTriggers.FieldByName('RDB$TRIGGER_TYPE').AsInt64),
1498     qryTriggers.FieldByName('RDB$TRIGGER_SEQUENCE').AsInteger]));
1499    
1500     if RelationName <> '' then
1501     SList.Add('ON ' + QuoteIdentifier( RelationName));
1502    
1503     if not qryTriggers.FieldByName('RDB$TRIGGER_SOURCE').IsNull then
1504     SList.Add(qryTriggers.FieldByName('RDB$TRIGGER_SOURCE').AsString)
1505     else
1506     SList.Add('AS BEGIN EXIT; END');
1507     SList.Add(' ' + ProcTerm);
1508     if qryTriggers.FieldByName('RDB$FLAGS').AsInteger <> 1 then
1509     SList.Add(' */');
1510     FMetaData.AddStrings(SList);
1511     if etGrant in ExtractTypes then
1512     ShowGrantsTo(TriggerName,obj_trigger,ProcTerm);
1513     qryTriggers.Next;
1514     end;
1515     if not Header then
1516     begin
1517     FMetaData.Add('COMMIT WORK ' + ProcTerm);
1518     FMetaData.Add('SET TERM ' + Term + ProcTerm);
1519     end;
1520     finally
1521     qryTriggers.Free;
1522     SList.Free;
1523     end;
1524     end;
1525    
1526     { ListCheck
1527     Functional description
1528     List check constraints for all objects to allow forward references }
1529    
1530     procedure TIBExtract.ListCheck(ObjectName : String; ExtractType : TExtractType);
1531     const
1532     { Query gets the check clauses for triggers stored for check constraints }
1533     CheckSQL =
1534     'SELECT * FROM RDB$TRIGGERS TRG JOIN RDB$CHECK_CONSTRAINTS CHK ON ' +
1535     ' TRG.RDB$TRIGGER_NAME = CHK.RDB$TRIGGER_NAME ' +
1536     'WHERE ' +
1537     ' TRG.RDB$TRIGGER_TYPE = 1 AND ' +
1538     ' EXISTS (SELECT RDB$CONSTRAINT_NAME FROM RDB$RELATION_CONSTRAINTS RELC WHERE ' +
1539     ' CHK.RDB$CONSTRAINT_NAME = RELC.RDB$CONSTRAINT_NAME) ' +
1540     'ORDER BY CHK.RDB$CONSTRAINT_NAME';
1541    
1542     CheckNameSQL =
1543     'SELECT * FROM RDB$TRIGGERS TRG JOIN RDB$CHECK_CONSTRAINTS CHK ON ' +
1544     ' TRG.RDB$TRIGGER_NAME = CHK.RDB$TRIGGER_NAME ' +
1545     'WHERE ' +
1546     ' TRG.RDB$RELATION_NAME = :TableName AND ' +
1547     ' TRG.RDB$TRIGGER_TYPE = 1 AND ' +
1548     ' EXISTS (SELECT RDB$CONSTRAINT_NAME FROM RDB$RELATION_CONSTRAINTS RELC WHERE ' +
1549     ' CHK.RDB$CONSTRAINT_NAME = RELC.RDB$CONSTRAINT_NAME) ' +
1550     'ORDER BY CHK.RDB$CONSTRAINT_NAME';
1551    
1552     CheckByNameSQL =
1553     'SELECT * FROM RDB$TRIGGERS TRG JOIN RDB$CHECK_CONSTRAINTS CHK ON ' +
1554     ' TRG.RDB$TRIGGER_NAME = CHK.RDB$TRIGGER_NAME ' +
1555     'WHERE ' +
1556     ' TRG.RDB$TRIGGER_NAME = :TriggerName AND ' +
1557     ' TRG.RDB$TRIGGER_TYPE = 1 AND ' +
1558     ' EXISTS (SELECT RDB$CONSTRAINT_NAME FROM RDB$RELATION_CONSTRAINTS RELC WHERE ' +
1559     ' CHK.RDB$CONSTRAINT_NAME = RELC.RDB$CONSTRAINT_NAME) ' +
1560     'ORDER BY CHK.RDB$CONSTRAINT_NAME';
1561    
1562     var
1563     qryChecks : TIBSQL;
1564     SList : TStrings;
1565     RelationName : String;
1566     begin
1567     qryChecks := TIBSQL.Create(FDatabase);
1568     SList := TStringList.Create;
1569     try
1570     if ObjectName = '' then
1571     qryChecks.SQL.Text := CheckSQL
1572     else
1573     if ExtractType = etTable then
1574     begin
1575     qryChecks.SQL.Text := CheckNameSQL;
1576     qryChecks.Params.ByName('TableName').AsString := ObjectName;
1577     end
1578     else
1579     begin
1580     qryChecks.SQL.Text := CheckByNameSQL;
1581     qryChecks.Params.ByName('TriggerName').AsString := ObjectName;
1582     end;
1583     qryChecks.ExecQuery;
1584     while not qryChecks.Eof do
1585     begin
1586     SList.Clear;
1587     RelationName := qryChecks.FieldByName('RDB$RELATION_NAME').AsString;
1588     SList.Add(Format('ALTER TABLE %s ADD',
1589     [QuoteIdentifier( RelationName)]));
1590     if Pos('INTEG', qryChecks.FieldByName('RDB$CONSTRAINT_NAME').AsString) <> 1 then
1591     SList.Add(Format('%sCONSTRAINT %s ', [TAB,
1592     QuoteIdentifier( qryChecks.FieldByName('RDB$CONSTRAINT_NAME').AsString)]));
1593    
1594     if not qryChecks.FieldByName('RDB$TRIGGER_SOURCE').IsNull then
1595     SList.Text := SList.Text + qryChecks.FieldByName('RDB$TRIGGER_SOURCE').AsString;
1596    
1597     SList.Strings[SList.Count - 1] := SList.Strings[SList.Count - 1] + (Term) + LineEnding;
1598     FMetaData.AddStrings(SList);
1599     qryChecks.Next;
1600     end;
1601     finally
1602     qryChecks.Free;
1603     SList.Free;
1604     end;
1605     end;
1606    
1607     { ListCreateDb
1608     Functional description
1609     Print the create database command if requested. At least put
1610     the page size in a comment with the extracted db name }
1611    
1612     procedure TIBExtract.ListCreateDb(TargetDb : String);
1613     const
1614     CharInfoSQL =
1615     'SELECT * FROM RDB$DATABASE DBP ' +
1616     'WHERE NOT DBP.RDB$CHARACTER_SET_NAME IS NULL ' +
1617     ' AND DBP.RDB$CHARACTER_SET_NAME <> '' ''';
1618    
1619     FilesSQL =
1620     'select * from RDB$FILES ' +
1621     'order BY RDB$SHADOW_NUMBER, RDB$FILE_SEQUENCE';
1622    
1623     LogsSQL =
1624     'SELECT * FROM RDB$LOG_FILES ' +
1625     'ORDER BY RDB$FILE_FLAGS, RDB$FILE_SEQUENCE';
1626    
1627     var
1628     NoDb, First, FirstFile, HasWal, SetUsed : Boolean;
1629     Buffer : String;
1630     qryDB : TIBSQL;
1631     FileFlags, FileLength, FileSequence, FileStart : Integer;
1632    
1633     function GetLongDatabaseInfo(DatabaseInfoCommand: Integer): LongInt;
1634     begin
1635     with Database.Attachment.GetDBInformation([DatabaseInfoCommand]) do
1636     if (Count > 0) and (Items[0].GetItemType = DatabaseInfoCommand) then
1637     Result := Items[0].AsInteger
1638     else
1639     IBError(ibxeUnexpectedDatabaseInfoResp,[nil]);
1640     end;
1641    
1642     begin
1643     NoDb := FALSE;
1644     First := TRUE;
1645     FirstFile := TRUE;
1646     HasWal := FALSE;
1647     SetUsed := FALSE;
1648     Buffer := '';
1649     if TargetDb = '' then
1650     begin
1651     Buffer := '/* ';
1652     TargetDb := FDatabase.DatabaseName;
1653     NoDb := true;
1654     end;
1655     Buffer := Buffer + 'CREATE DATABASE ' + QuotedStr(TargetDb) + ' PAGE_SIZE ' +
1656     IntToStr(FDatabaseInfo.PageSize) + LineEnding;
1657     FMetaData.Add(Buffer);
1658     Buffer := '';
1659    
1660     qryDB := TIBSQL.Create(FDatabase);
1661     try
1662     qryDB.SQL.Text := CharInfoSQL;
1663     qryDB.ExecQuery;
1664    
1665     if not qryDB.EOF then
1666     Buffer := Format(' DEFAULT CHARACTER SET %s',
1667     [trim(qryDB.FieldByName('RDB$CHARACTER_SET_NAME').AsString)]);
1668     if NoDB then
1669     Buffer := Buffer + Term + ' */'
1670     else
1671     Buffer := Buffer + Term;
1672     FMetaData.Add(Buffer);
1673     qryDB.Close;
1674     {List secondary files and shadows as
1675     alter db and create shadow in comment}
1676     qryDB.SQL.Text := FilesSQL;
1677     qryDB.ExecQuery;
1678     while not qryDB.Eof do
1679     begin
1680     if First then
1681     begin
1682     FMetaData.Add(LineEnding + '/* Add secondary files in comments ');
1683     First := false;
1684     end; //end_if
1685    
1686     if qryDB.FieldByName('RDB$FILE_FLAGS').IsNull then
1687     FileFlags := 0
1688     else
1689     FileFlags := qryDB.FieldByName('RDB$FILE_FLAGS').AsInteger;
1690     if qryDB.FieldByName('RDB$FILE_LENGTH').IsNull then
1691     FileLength := 0
1692     else
1693     FileLength := qryDB.FieldByName('RDB$FILE_LENGTH').AsInteger;
1694     if qryDB.FieldByName('RDB$FILE_SEQUENCE').IsNull then
1695     FileSequence := 0
1696     else
1697     FileSequence := qryDB.FieldByName('RDB$FILE_SEQUENCE').AsInteger;
1698     if qryDB.FieldByName('RDB$FILE_START').IsNull then
1699     FileStart := 0
1700     else
1701     FileStart := qryDB.FieldByName('RDB$FILE_START').AsInteger;
1702    
1703     { Pure secondary files }
1704     if FileFlags = 0 then
1705     begin
1706     Buffer := Format('%sALTER DATABASE ADD FILE ''%s''',
1707     [LineEnding, qryDB.FieldByName('RDB$FILE_NAME').AsString]);
1708     if FileStart <> 0 then
1709     Buffer := Buffer + Format(' STARTING %d', [FileStart]);
1710     if FileLength <> 0 then
1711     Buffer := Buffer + Format(' LENGTH %d', [FileLength]);
1712     FMetaData.Add(Buffer);
1713     end; //end_if
1714     if (FileFlags and FILE_cache) <> 0 then
1715     FMetaData.Add(Format('%sALTER DATABASE ADD CACHE ''%s'' LENGTH %d',
1716     [LineEnding, qryDB.FieldByName('RDB$FILE_NAME').AsString, FileLength]));
1717    
1718     Buffer := '';
1719     if (FileFlags and FILE_shadow) <> 0 then
1720     begin
1721     if FileSequence <> 0 then
1722     Buffer := Format('%sFILE ''%s''',
1723     [TAB, qryDB.FieldByName('RDB$FILE_NAME').AsString])
1724     else
1725     begin
1726     Buffer := Format('%sCREATE SHADOW %d ''%s'' ',
1727     [LineEnding, qryDB.FieldByName('RDB$SHADOW_NUMBER').AsInteger,
1728     qryDB.FieldByName('RDB$FILE_NAME').AsString]);
1729     if (FileFlags and FILE_inactive) <> 0 then
1730     Buffer := Buffer + 'INACTIVE ';
1731     if (FileFlags and FILE_manual) <> 0 then
1732     Buffer := Buffer + 'MANUAL '
1733     else
1734     Buffer := Buffer + 'AUTO ';
1735     if (FileFlags and FILE_conditional) <> 0 then
1736     Buffer := Buffer + 'CONDITIONAL ';
1737     end; //end_else
1738     if FileLength <> 0 then
1739     Buffer := Buffer + Format('LENGTH %d ', [FileLength]);
1740     if FileStart <> 0 then
1741     Buffer := Buffer + Format('STARTING %d ', [FileStart]);
1742     FMetaData.Add(Buffer);
1743     end; //end_if
1744     qryDB.Next;
1745     end;
1746     qryDB.Close;
1747    
1748     qryDB.SQL.Text := LogsSQL;
1749     qryDB.ExecQuery;
1750     while not qryDB.Eof do
1751     begin
1752    
1753     if qryDB.FieldByName('RDB$FILE_FLAGS').IsNull then
1754     FileFlags := 0
1755     else
1756     FileFlags := qryDB.FieldByName('RDB$FILE_FLAGS').AsInteger;
1757     if qryDB.FieldByName('RDB$FILE_LENGTH').IsNull then
1758     FileLength := 0
1759     else
1760     FileLength := qryDB.FieldByName('RDB$FILE_LENGTH').AsInteger;
1761    
1762     Buffer := '';
1763     HasWal := true;
1764     if First then
1765     begin
1766     if NoDB then
1767     Buffer := '/* ';
1768     Buffer := Buffer + LineEnding + 'ALTER DATABASE ADD ';
1769     First := false;
1770     end; //end_if
1771     if FirstFile then
1772     Buffer := Buffer + 'LOGFILE ';
1773     { Overflow files also have the serial bit set }
1774     if (FileFlags and LOG_default) = 0 then
1775     begin
1776     if (FileFlags and LOG_overflow) <> 0 then
1777     Buffer := Buffer + Format(')%s OVERFLOW ''%s''',
1778     [LineEnding, qryDB.FieldByName('RDB$FILE_NAME').AsString])
1779     else
1780     if (FileFlags and LOG_serial) <> 0 then
1781     Buffer := Buffer + Format('%s BASE_NAME ''%s''',
1782     [LineEnding, qryDB.FieldByName('RDB$FILE_NAME').AsString])
1783     { Since we are fetching order by FILE_FLAGS, the LOG_0verflow will
1784     be last. It will only appear if there were named round robin,
1785     so we must close the parens first }
1786    
1787     { We have round robin and overflow file specifications }
1788     else
1789     begin
1790     if FirstFile then
1791     Buffer := Buffer + '('
1792     else
1793     Buffer := Buffer + Format(',%s ', [LineEnding]);
1794     FirstFile := false;
1795    
1796     Buffer := Buffer + Format('''%s''', [qryDB.FieldByName('RDB$FILE_NAME').AsString]);
1797     end; //end_else
1798     end;
1799     { Any file can have a length }
1800     if FileLength <> 0 then
1801     Buffer := Buffer + Format(' SIZE %d ', [FileLength]);
1802     FMetaData.Add(Buffer);
1803     qryDB.Next;
1804     end;
1805     qryDB.Close;
1806     Buffer := '';
1807     if HasWal then
1808     begin
1809     Buffer := Buffer + PrintSet(SetUsed);
1810     Buffer := Buffer + Format('NUM_LOG_BUFFERS = %d',
1811     [GetLongDatabaseInfo(isc_info_num_wal_buffers)]);
1812     Buffer := Buffer + PrintSet(SetUsed);
1813     Buffer := Buffer + Format('LOG_BUFFER_SIZE = %d',
1814     [GetLongDatabaseInfo(isc_info_wal_buffer_size)]);
1815     Buffer := Buffer + PrintSet(SetUsed);
1816     Buffer := Buffer + Format('GROUP_COMMIT_WAIT_TIME = %d',
1817     [GetLongDatabaseInfo(isc_info_wal_grpc_wait_usecs)]);
1818     Buffer := Buffer + PrintSet(SetUsed);
1819     Buffer := Buffer + Format('CHECK_POINT_LENGTH = %d',
1820     [GetLongDatabaseInfo(isc_info_wal_ckpt_length)]);
1821     FMetaData.Add(Buffer);
1822    
1823     end;
1824     if not First then
1825     begin
1826     if NoDB then
1827     FMetaData.Add(Format('%s */%s', [LineEnding, LineEnding]))
1828     else
1829     FMetaData.Add(Format('%s%s%s', [Term, LineEnding, LineEnding]));
1830     end;
1831     finally
1832     qryDB.Free;
1833     end;
1834    
1835     (*
1836     *)
1837     end;
1838    
1839     { ListDomainTable
1840     Functional description
1841     List domains as identified by fields with any constraints on them
1842     for the named table
1843    
1844     Parameters: table_name == only extract domains for this table }
1845    
1846     procedure TIBExtract.ListDomains(ObjectName: String; ExtractType : TExtractType);
1847     const
1848     DomainSQL =
1849     'SELECT distinct fld.* FROM RDB$FIELDS FLD JOIN RDB$RELATION_FIELDS RFR ON ' +
1850     ' RFR.RDB$FIELD_SOURCE = FLD.RDB$FIELD_NAME ' +
1851     'WHERE RFR.RDB$RELATION_NAME = :TABLE_NAME ' +
1852     'ORDER BY FLD.RDB$FIELD_NAME';
1853    
1854     DomainByNameSQL =
1855     'SELECT * FROM RDB$FIELDS FLD ' +
1856     'WHERE FLD.RDB$FIELD_NAME = :DomainName ' +
1857     'ORDER BY FLD.RDB$FIELD_NAME';
1858    
1859     AllDomainSQL =
1860     'select * from RDB$FIELDS ' +
1861     'where RDB$SYSTEM_FLAG <> 1 ' +
1862     'order BY RDB$FIELD_NAME';
1863    
1864     var
1865     First : Boolean;
1866     qryDomains : TIBSQL;
1867     FieldName, Line : String;
1868    
1869     function FormatDomainStr : String;
1870     var
1871     i, SubType : Integer;
1872     PrecisionKnown : Boolean;
1873     begin
1874     Result := '';
1875     for i := Low(ColumnTypes) to High(ColumnTypes) do
1876     if qryDomains.FieldByName('RDB$FIELD_TYPE').AsInteger = ColumnTypes[i].SQLType then
1877     begin
1878     PrecisionKnown := FALSE;
1879     if FDatabaseInfo.ODSMajorVersion >= ODS_VERSION10 then
1880     begin
1881     if qryDomains.FieldByName('RDB$FIELD_TYPE').AsInteger in [blr_short, blr_long, blr_int64] then
1882     begin
1883     { We are ODS >= 10 and could be any Dialect }
1884     if (FDatabaseInfo.DBSQLDialect >= 3) and
1885     (not qryDomains.FieldByName('RDB$FIELD_PRECISION').IsNull) and
1886     (qryDomains.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger > 0) and
1887     (qryDomains.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger <= MAX_INTSUBTYPES) then
1888     begin
1889     Result := Result + Format('%s(%d, %d)', [
1890     IntegralSubtypes [qryDomains.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger],
1891     qryDomains.FieldByName('RDB$FIELD_PRECISION').AsInteger,
1892     -1 * qryDomains.FieldByName('RDB$FIELD_SCALE').AsInteger]);
1893     PrecisionKnown := true;
1894     end;
1895     end;
1896     end;
1897     if PrecisionKnown = false then
1898     begin
1899     { Take a stab at numerics and decimals }
1900     if (qryDomains.FieldByName('RDB$FIELD_TYPE').AsInteger = blr_short) and
1901     (qryDomains.FieldByName('RDB$FIELD_SCALE').AsInteger < 0) then
1902     Result := Result + Format('NUMERIC(4, %d)',
1903     [-qryDomains.FieldByName('RDB$FIELD_SCALE').AsInteger] )
1904     else
1905     if (qryDomains.FieldByName('RDB$FIELD_TYPE').AsInteger = blr_long) and
1906     (qryDomains.FieldByName('RDB$FIELD_SCALE').AsInteger < 0) then
1907     Result := Result + Format('NUMERIC(9, %d)',
1908     [-qryDomains.FieldByName('RDB$FIELD_SCALE').AsInteger] )
1909     else
1910     if (qryDomains.FieldByName('RDB$FIELD_TYPE').AsInteger = blr_double) and
1911     (qryDomains.FieldByName('RDB$FIELD_SCALE').AsInteger < 0) then
1912     Result := Result + Format('NUMERIC(15, %d)',
1913     [-qryDomains.FieldByName('RDB$FIELD_SCALE').AsInteger] )
1914     else
1915     Result := Result + ColumnTypes[i].TypeName;
1916     end;
1917     break;
1918     end;
1919    
1920     if qryDomains.FieldByName('RDB$FIELD_TYPE').AsInteger = blr_blob then
1921     begin
1922     subtype := qryDomains.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger;
1923     Result := Result + ' SUB_TYPE ';
1924     if (subtype > 0) and (subtype <= MAXSUBTYPES) then
1925     Result := Result + SubTypes[subtype]
1926     else
1927     Result := Result + Format('%d', [subtype]);
1928     Result := Result + Format(' SEGMENT SIZE %d', [qryDomains.FieldByName('RDB$SEGMENT_LENGTH').AsInteger]);
1929     end //end_if
1930     else
1931     if (qryDomains.FieldByName('RDB$FIELD_TYPE').AsInteger in [blr_text, blr_varying]) then
1932     begin
1933     if not qryDomains.FieldByName('RDB$CHARACTER_LENGTH').IsNull then
1934     Result := Result + Format('(%d)', [qryDomains.FieldByName('RDB$CHARACTER_LENGTH').AsInteger])
1935     else
1936     Result := Result + Format('(%d)', [qryDomains.FieldByName('RDB$FIELD_LENGTH').AsInteger]);
1937     end;
1938    
1939     { since the character set is part of the field type, display that
1940     information now. }
1941     if not qryDomains.FieldByName('RDB$CHARACTER_SET_ID').IsNull then
1942     Result := Result + GetCharacterSets(qryDomains.FieldByName('RDB$CHARACTER_SET_ID').AsInteger,
1943     0, FALSE);
1944     if not qryDomains.FieldByName('RDB$DIMENSIONS').IsNull then
1945     Result := GetArrayField(qryDomains.FieldByName('RDB$FIELD_SOURCE').AsString);
1946    
1947     if not qryDomains.FieldByName('RDB$DEFAULT_SOURCE').IsNull then
1948     Result := Result + Format('%s%s %s', [LineEnding, TAB,
1949     qryDomains.FieldByName('RDB$DEFAULT_SOURCE').AsString]);
1950    
1951     if not qryDomains.FieldByName('RDB$VALIDATION_SOURCE').IsNull then
1952     if Pos('CHECK', AnsiUpperCase(qryDomains.FieldByName('RDB$VALIDATION_SOURCE').AsString)) = 1 then
1953     Result := Result + Format('%s%s %s', [LineEnding, TAB,
1954     qryDomains.FieldByName('RDB$VALIDATION_SOURCE').AsString])
1955     else
1956     Result := Result + Format('%s%s /* %s */', [LineEnding, TAB,
1957     qryDomains.FieldByName('RDB$VALIDATION_SOURCE').AsString]);
1958    
1959     if qryDomains.FieldByName('RDB$NULL_FLAG').AsInteger = 1 then
1960     Result := Result + ' NOT NULL';
1961    
1962     { Show the collation order if one has been specified. If the collation
1963     order is the default for the character set being used, then no collation
1964     order will be shown ( because it isn't needed ).
1965    
1966     If the collation id is 0, then the default for the character set is
1967     being used so there is no need to retrieve the collation information.}
1968    
1969     if (not qryDomains.FieldByName('RDB$COLLATION_ID').IsNull) and
1970     (qryDomains.FieldByName('RDB$COLLATION_ID').AsInteger <> 0) then
1971     Result := Result + GetCharacterSets(qryDomains.FieldByName('RDB$CHARACTER_SET_ID').AsInteger,
1972     qryDomains.FieldByName('RDB$COLLATION_ID').AsInteger, true);
1973     end;
1974    
1975     begin
1976     First := true;
1977     qryDomains := TIBSQL.Create(FDatabase);
1978     try
1979     if ObjectName <> '' then
1980     begin
1981     if ExtractType = etTable then
1982     begin
1983     qryDomains.SQL.Text := DomainSQL;
1984     qryDomains.Params.ByName('table_name').AsString := ObjectName;
1985     end
1986     else
1987     begin
1988     qryDomains.SQL.Text := DomainByNameSQL;
1989     qryDomains.Params.ByName('DomainName').AsString := ObjectName;
1990     end;
1991     end
1992     else
1993     qryDomains.SQL.Text := AllDomainSQL;
1994    
1995     qryDomains.ExecQuery;
1996     while not qryDomains.Eof do
1997     begin
1998     FieldName := qryDomains.FieldByName('RDB$FIELD_NAME').AsString;
1999     { Skip over artifical domains }
2000     if (Pos('RDB$',FieldName) = 1) and
2001     (FieldName[5] in ['0'..'9']) and
2002     (qryDomains.FieldByName('RDB$SYSTEM_FLAG').AsInteger <> 1) then
2003     begin
2004     qryDomains.Next;
2005     continue;
2006     end;
2007    
2008     if First then
2009     begin
2010     FMetaData.Add('/* Domain definitions */');
2011     First := false;
2012     end;
2013    
2014     Line := Format('CREATE DOMAIN %s AS ', [FieldName]);
2015     Line := Line + FormatDomainStr + Term;
2016     FMetaData.Add(Line);
2017     qryDomains.Next;
2018     end;
2019     finally
2020     qryDomains.Free;
2021     end;
2022     end;
2023    
2024     { ListException
2025     Functional description
2026     List all exceptions defined in the database
2027    
2028     Parameters: none }
2029    
2030     procedure TIBExtract.ListException(ExceptionName : String = '');
2031     const
2032     ExceptionSQL =
2033     'select * from RDB$EXCEPTIONS ' +
2034     'ORDER BY RDB$EXCEPTION_NAME';
2035    
2036     ExceptionNameSQL =
2037     'select * from RDB$EXCEPTIONS ' +
2038     'WHERE RDB$EXCEPTION_NAME = :ExceptionName ' +
2039     'ORDER BY RDB$EXCEPTION_NAME';
2040    
2041     var
2042     First : Boolean;
2043     qryException : TIBSQL;
2044     begin
2045     First := true;
2046     qryException := TIBSQL.Create(FDatabase);
2047     try
2048     if ExceptionName = '' then
2049     qryException.SQL.Text := ExceptionSQL
2050     else
2051     begin
2052     qryException.SQL.Text := ExceptionNameSQL;
2053     qryException.Params.ByName('ExceptionName').AsString := ExceptionName;
2054     end;
2055    
2056     qryException.ExecQuery;
2057     while not qryException.Eof do
2058     begin
2059     if First then
2060     begin
2061     FMetaData.Add('');
2062     FMetaData.Add('/* Exceptions */');
2063     FMetaData.Add('');
2064     First := false;
2065     end; //end_if
2066    
2067     FMetaData.Add(Format('CREATE EXCEPTION %s %s%s',
2068     [QuoteIdentifier( qryException.FieldByName('RDB$EXCEPTION_NAME').AsString),
2069     QuotedStr(qryException.FieldByName('RDB$MESSAGE').AsString), Term]));
2070     qryException.Next;
2071     end;
2072     finally
2073     qryException.Free;
2074     end;
2075     end;
2076    
2077     { ListFilters
2078    
2079     Functional description
2080     List all blob filters
2081    
2082     Parameters: none
2083     Results in
2084     DECLARE FILTER <fname> INPUT_TYPE <blob_sub_type> OUTPUT_TYPE <blob_subtype>
2085     ENTRY_POINT <string> MODULE_NAME <string> }
2086    
2087     procedure TIBExtract.ListFilters(FilterName : String = '');
2088     const
2089     FiltersSQL =
2090     'SELECT * FROM RDB$FILTERS ' +
2091     'ORDER BY RDB$FUNCTION_NAME';
2092     FilterNameSQL =
2093     'SELECT * FROM RDB$FILTERS ' +
2094     'WHERE RDB$FUNCTION_NAME = :FunctionName ' +
2095     'ORDER BY RDB$FUNCTION_NAME';
2096    
2097     var
2098     First : Boolean;
2099     qryFilters : TIBSQL;
2100     begin
2101     First := true;
2102     qryFilters := TIBSQL.Create(FDatabase);
2103     try
2104     if FilterName = '' then
2105     qryFilters.SQL.Text := FiltersSQL
2106     else
2107     begin
2108     qryFilters.SQL.Text := FilterNameSQL;
2109     qryFilters.Params.ByName('FunctionName').AsString := FilterName;
2110     end;
2111     qryFilters.ExecQuery;
2112     while not qryFilters.Eof do
2113     begin
2114     if First then
2115     begin
2116     FMetaData.Add('');
2117     FMetaData.Add('/* BLOB Filter declarations */');
2118     FMetaData.Add('');
2119     First := false;
2120     end; //end_if
2121    
2122     FMetaData.Add(Format('DECLARE FILTER %s INPUT_TYPE %d OUTPUT_TYPE %d',
2123     [qryFilters.FieldByName('RDB$FUNCTION_NAME').AsString,
2124     qryFilters.FieldByName('RDB$INPUT_SUB_TYPE').AsInteger,
2125     qryFilters.FieldByName('RDB$OUTPUT_SUB_TYPE').AsInteger]));
2126     FMetaData.Add(Format('%sENTRY_POINT ''%s'' MODULE_NAME ''%s''%s%',
2127     [TAB, qryFilters.FieldByName('RDB$ENTRYPOINT').AsString,
2128     qryFilters.FieldByName('RDB$MODULE_NAME').AsString, Term]));
2129     FMetaData.Add('');
2130    
2131     qryFilters.Next;
2132     end;
2133    
2134     finally
2135     qryFilters.Free;
2136     end;
2137     end;
2138    
2139     { ListForeign
2140     Functional description
2141     List all foreign key constraints and alter the tables }
2142    
2143     procedure TIBExtract.ListForeign(ObjectName : String; ExtractType : TExtractType);
2144     const
2145     { Static queries for obtaining foreign constraints, where RELC1 is the
2146     foreign key constraints, RELC2 is the primary key lookup and REFC
2147     is the join table }
2148     ForeignSQL =
2149     'SELECT REFC.RDB$UPDATE_RULE REFC_UPDATE_RULE, REFC.RDB$DELETE_RULE REFC_DELETE_RULE, ' +
2150     ' RELC1.RDB$RELATION_NAME RELC1_RELATION_NAME, RELC2.RDB$RELATION_NAME RELC2_RELATION_NAME, ' +
2151     ' RELC1.RDB$INDEX_NAME RELC1_INDEX_NAME, RELC1.RDB$CONSTRAINT_NAME RELC1_CONSTRAINT_NAME, ' +
2152     ' RELC2.RDB$INDEX_NAME RELC2_INDEX_NAME ' +
2153     'FROM RDB$REF_CONSTRAINTS REFC, RDB$RELATION_CONSTRAINTS RELC1, ' +
2154     ' RDB$RELATION_CONSTRAINTS RELC2 ' +
2155     'WHERE ' +
2156     ' RELC1.RDB$CONSTRAINT_TYPE = ''FOREIGN KEY'' AND ' +
2157     ' REFC.RDB$CONST_NAME_UQ = RELC2.RDB$CONSTRAINT_NAME AND ' +
2158     ' REFC.RDB$CONSTRAINT_NAME = RELC1.RDB$CONSTRAINT_NAME AND ' +
2159     ' (RELC2.RDB$CONSTRAINT_TYPE = ''UNIQUE'' OR ' +
2160     ' RELC2.RDB$CONSTRAINT_TYPE = ''PRIMARY KEY'') ' +
2161     'ORDER BY RELC1.RDB$RELATION_NAME, RELC1.RDB$CONSTRAINT_NAME';
2162    
2163     ForeignNameSQL =
2164     'SELECT REFC.RDB$UPDATE_RULE REFC_UPDATE_RULE, REFC.RDB$DELETE_RULE REFC_DELETE_RULE, ' +
2165     ' RELC1.RDB$RELATION_NAME RELC1_RELATION_NAME, RELC2.RDB$RELATION_NAME RELC2_RELATION_NAME, ' +
2166     ' RELC1.RDB$INDEX_NAME RELC1_INDEX_NAME, RELC1.RDB$CONSTRAINT_NAME RELC1_CONSTRAINT_NAME, ' +
2167     ' RELC2.RDB$INDEX_NAME RELC2_INDEX_NAME ' +
2168     'FROM RDB$REF_CONSTRAINTS REFC, RDB$RELATION_CONSTRAINTS RELC1, ' +
2169     ' RDB$RELATION_CONSTRAINTS RELC2 ' +
2170     'WHERE ' +
2171     ' RELC1.RDB$RELATION_NAME = :TableName AND ' +
2172     ' RELC1.RDB$CONSTRAINT_TYPE = ''FOREIGN KEY'' AND ' +
2173     ' REFC.RDB$CONST_NAME_UQ = RELC2.RDB$CONSTRAINT_NAME AND ' +
2174     ' REFC.RDB$CONSTRAINT_NAME = RELC1.RDB$CONSTRAINT_NAME AND ' +
2175     ' (RELC2.RDB$CONSTRAINT_TYPE = ''UNIQUE'' OR ' +
2176     ' RELC2.RDB$CONSTRAINT_TYPE = ''PRIMARY KEY'') ' +
2177     'ORDER BY RELC1.RDB$RELATION_NAME, RELC1.RDB$CONSTRAINT_NAME';
2178    
2179     ForeignByNameSQL =
2180     'SELECT REFC.RDB$UPDATE_RULE REFC_UPDATE_RULE, REFC.RDB$DELETE_RULE REFC_DELETE_RULE, ' +
2181     ' RELC1.RDB$RELATION_NAME RELC1_RELATION_NAME, RELC2.RDB$RELATION_NAME RELC2_RELATION_NAME, ' +
2182     ' RELC1.RDB$INDEX_NAME RELC1_INDEX_NAME, RELC1.RDB$CONSTRAINT_NAME RELC1_CONSTRAINT_NAME, ' +
2183     ' RELC2.RDB$INDEX_NAME RELC2_INDEX_NAME ' +
2184     'FROM RDB$REF_CONSTRAINTS REFC, RDB$RELATION_CONSTRAINTS RELC1, ' +
2185     ' RDB$RELATION_CONSTRAINTS RELC2 ' +
2186     'WHERE ' +
2187     ' RELC1.RDB$CONSTRAINT_NAME = :ConstraintName AND ' +
2188     ' RELC1.RDB$CONSTRAINT_TYPE = ''FOREIGN KEY'' AND ' +
2189     ' REFC.RDB$CONST_NAME_UQ = RELC2.RDB$CONSTRAINT_NAME AND ' +
2190     ' REFC.RDB$CONSTRAINT_NAME = RELC1.RDB$CONSTRAINT_NAME AND ' +
2191     ' (RELC2.RDB$CONSTRAINT_TYPE = ''UNIQUE'' OR ' +
2192     ' RELC2.RDB$CONSTRAINT_TYPE = ''PRIMARY KEY'') ' +
2193     'ORDER BY RELC1.RDB$RELATION_NAME, RELC1.RDB$CONSTRAINT_NAME';
2194    
2195     var
2196     qryForeign : TIBSQL;
2197     Line : String;
2198    
2199     begin
2200     qryForeign := TIBSQL.Create(FDatabase);
2201     try
2202     if ObjectName = '' then
2203     qryForeign.SQL.Text := ForeignSQL
2204     else
2205     begin
2206     if ExtractType = etTable then
2207     begin
2208     qryForeign.SQL.Text := ForeignNameSQL;
2209     qryForeign.Params.ByName('TableName').AsString := ObjectName;
2210     end
2211     else
2212     begin
2213     qryForeign.SQL.Text := ForeignByNameSQL;
2214     qryForeign.Params.ByName('ConstraintName').AsString := ObjectName;
2215     end;
2216     end;
2217     qryForeign.ExecQuery;
2218     while not qryForeign.Eof do
2219     begin
2220     Line := Format('ALTER TABLE %s ADD ', [QuoteIdentifier(
2221     qryForeign.FieldByName('RELC1_RELATION_NAME').AsString)]);
2222    
2223     { If the name of the constraint is not INTEG..., print it.
2224     INTEG... are internally generated names. }
2225     if (not qryForeign.FieldByName('RELC1_CONSTRAINT_NAME').IsNull) and
2226     ( Pos('INTEG', qryForeign.FieldByName('RELC1_CONSTRAINT_NAME').AsString) <> 1) then
2227     Line := Line + Format('CONSTRAINT %s ', [QuoteIdentifier(
2228     Trim(qryForeign.FieldByName('RELC1_CONSTRAINT_NAME').AsString))]);
2229    
2230     Line := Line + Format('FOREIGN KEY (%s) REFERENCES %s ', [
2231     GetIndexSegments(qryForeign.FieldByName('RELC1_INDEX_NAME').AsString),
2232     Trim(qryForeign.FieldByName('RELC2_RELATION_NAME').AsString)]);
2233    
2234     Line := Line + Format('(%s)',
2235     [GetIndexSegments(qryForeign.FieldByName('RELC2_INDEX_NAME').AsString)]);
2236    
2237     { Add the referential actions, if any }
2238     if (not qryForeign.FieldByName('REFC_UPDATE_RULE').IsNull) and
2239     (Trim(qryForeign.FieldByName('REFC_UPDATE_RULE').AsString) <> 'RESTRICT') then
2240     Line := Line + Format(' ON UPDATE %s',
2241     [Trim(qryForeign.FieldByName('REFC_UPDATE_RULE').AsString)]);
2242    
2243     if (not qryForeign.FieldByName('REFC_DELETE_RULE').IsNull) and
2244     (Trim(qryForeign.FieldByName('REFC_DELETE_RULE').AsString) <> 'RESTRICT') then
2245     Line := Line + Format(' ON DELETE %s',
2246     [Trim(qryForeign.FieldByName('REFC_DELETE_RULE').AsString)]);
2247    
2248     Line := Line + Term;
2249     FMetaData.Add(Line);
2250     qryForeign.Next;
2251     end;
2252     finally
2253     qryForeign.Free;
2254     end;
2255     end;
2256    
2257     { ListFunctions
2258    
2259     Functional description
2260     List all external functions
2261    
2262     Parameters: none
2263     Results in
2264     DECLARE EXTERNAL FUNCTION function_name
2265     CHAR [256] , INTEGER, ....
2266     RETURNS INTEGER BY VALUE
2267     ENTRY_POINT entrypoint MODULE_NAME module; }
2268    
2269     procedure TIBExtract.ListFunctions(FunctionName : String = '');
2270     const
2271     FunctionSQL =
2272     'SELECT * FROM RDB$FUNCTIONS WHERE RDB$SYSTEM_FLAG = 0 ' +
2273     'ORDER BY RDB$FUNCTION_NAME';
2274    
2275     FunctionNameSQL =
2276     'SELECT * FROM RDB$FUNCTIONS ' +
2277     'WHERE RDB$FUNCTION_NAME = :FunctionName ' +
2278     'ORDER BY RDB$FUNCTION_NAME';
2279    
2280     FunctionArgsSQL =
2281     'SELECT * FROM RDB$FUNCTION_ARGUMENTS ' +
2282     'WHERE ' +
2283     ' :FUNCTION_NAME = RDB$FUNCTION_NAME ' +
2284     'ORDER BY RDB$ARGUMENT_POSITION';
2285    
2286     FuncArgsPosSQL =
2287     'SELECT * FROM RDB$FUNCTION_ARGUMENTS ' +
2288     'WHERE ' +
2289     ' RDB$FUNCTION_NAME = :RDB$FUNCTION_NAME AND ' +
2290     ' RDB$ARGUMENT_POSITION = :RDB$ARGUMENT_POSITION';
2291    
2292     CharSetSQL =
2293     'SELECT * FROM RDB$CHARACTER_SETS ' +
2294     'WHERE ' +
2295     ' RDB$CHARACTER_SET_ID = :CHARACTER_SET_ID';
2296    
2297     var
2298     qryFunctions, qryFuncArgs, qryCharSets, qryFuncPos : TIBSQL;
2299     First, FirstArg, DidCharset, PrecisionKnown : Boolean;
2300     ReturnBuffer, TypeBuffer, Line : String;
2301     i, FieldType : Integer;
2302     begin
2303     First := true;
2304     qryFunctions := TIBSQL.Create(FDatabase);
2305     qryFuncArgs := TIBSQL.Create(FDatabase);
2306     qryFuncPos := TIBSQL.Create(FDatabase);
2307     qryCharSets := TIBSQL.Create(FDatabase);
2308     try
2309     if FunctionName = '' then
2310     qryFunctions.SQL.Text := FunctionSQL
2311     else
2312     begin
2313     qryFunctions.SQL.Text := FunctionNameSQL;
2314     qryFunctions.Params.ByName('FunctionName').AsString := FunctionName;
2315     end;
2316     qryFuncArgs.SQL.Text := FunctionArgsSQL;
2317     qryFuncPos.SQL.Text := FuncArgsPosSQL;
2318     qryCharSets.SQL.Text := CharSetSQL;
2319     qryFunctions.ExecQuery;
2320     while not qryFunctions.Eof do
2321     begin
2322     if First then
2323     begin
2324     FMEtaData.Add(Format('%s/* External Function declarations */%s',
2325     [LineEnding, LineEnding]));
2326     First := false;
2327     end; //end_if
2328     { Start new function declaration }
2329     FMetaData.Add(Format('DECLARE EXTERNAL FUNCTION %s',
2330     [qryFunctions.FieldByName('RDB$FUNCTION_NAME').AsString]));
2331     Line := '';
2332    
2333     FirstArg := true;
2334     qryFuncArgs.Params.ByName('FUNCTION_NAME').AsString :=
2335     qryFunctions.FieldByName('RDB$FUNCTION_NAME').AsString;
2336    
2337     qryFuncArgs.ExecQuery;
2338     while not qryFuncArgs.Eof do
2339     begin
2340     { Find parameter type }
2341     i := 0;
2342     FieldType := qryFuncArgs.FieldByName('RDB$FIELD_TYPE').AsInteger;
2343     while FieldType <> ColumnTypes[i].SQLType do
2344     Inc(i);
2345    
2346     { Print length where appropriate }
2347     if FieldType in [ blr_text, blr_varying, blr_cstring] then
2348     begin
2349     DidCharset := false;
2350    
2351     qryCharSets.Params.ByName('CHARACTER_SET_ID').AsString :=
2352     qryFuncArgs.FieldByName('RDB$CHARACTER_SET_ID').AsString;
2353     qryCharSets.ExecQuery;
2354     while not qryCharSets.Eof do
2355     begin
2356     DidCharset := true;
2357     TypeBuffer := Format('%s(%d) CHARACTER SET %s',
2358     [ColumnTypes[i].TypeName,
2359     qryFuncArgs.FieldByName('RDB$FIELD_LENGTH').AsInteger div
2360     Max(1,qryCharSets.FieldByName('RDB$BYTES_PER_CHARACTER').AsInteger),
2361     qryCharSets.FieldByName('RDB$CHARACTER_SET_NAME').AsString]);
2362     qryCharSets.Next;
2363     end;
2364     qryCharSets.Close;
2365     if not DidCharset then
2366     TypeBuffer := Format('%s(%d)', [ColumnTypes[i].TypeName,
2367     qryFuncArgs.FieldByName('RDB$FIELD_LENGTH').AsInteger]);
2368     end //end_if
2369     else
2370     begin
2371     PrecisionKnown := false;
2372     if (FDatabaseInfo.ODSMajorVersion >= ODS_VERSION10) and
2373     (FieldType in [blr_short, blr_long, blr_int64]) then
2374     begin
2375     qryFuncPos.Params.ByName('RDB$FUNCTION_NAME').AsString :=
2376     qryFuncArgs.FieldByName('RDB$FUNCTION_NAME').AsString;
2377     qryFuncPos.Params.ByName('RDB$ARGUMENT_POSITION').AsInteger :=
2378     qryFuncArgs.FieldByName('RDB$ARGUMENT_POSITION').AsInteger;
2379    
2380     qryFuncPos.ExecQuery;
2381     while not qryFuncPos.Eof do
2382     begin
2383     { We are ODS >= 10 and could be any Dialect }
2384     if not qryFuncPos.FieldByName('RDB$FIELD_PRECISION').IsNull then
2385     begin
2386     { We are Dialect >=3 since FIELD_PRECISION is non-NULL }
2387     if (qryFuncPos.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger > 0) and
2388     (qryFuncPos.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger <= MAX_INTSUBTYPES) then
2389     begin
2390     TypeBuffer := Format('%s(%d, %d)',
2391     [IntegralSubtypes[qryFuncPos.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger],
2392     qryFuncPos.FieldByName('RDB$FIELD_PRECISION').AsInteger,
2393     -qryFuncPos.FieldByName('RDB$FIELD_SCALE').AsInteger] );
2394     PrecisionKnown := true;
2395     end; //end_if
2396     end; { if field_precision is not null }
2397     qryFuncPos.Next;
2398     end;
2399     qryFuncPos.Close;
2400     end; { if major_ods >= ods_version10 && }
2401     if not PrecisionKnown then
2402     begin
2403     { Take a stab at numerics and decimals }
2404     if (FieldType = blr_short) and
2405     (qryFuncArgs.FieldByName('RDB$FIELD_SCALE').AsInteger < 0) then
2406     TypeBuffer := Format('NUMERIC(4, %d)',
2407     [-qryFuncArgs.FieldByName('RDB$FIELD_SCALE').AsInteger])
2408     else
2409     if (FieldType = blr_long) and
2410     (qryFuncArgs.FieldByName('RDB$FIELD_SCALE').AsInteger < 0) then
2411     TypeBuffer := Format('NUMERIC(9, %d)',
2412     [-qryFuncArgs.FieldByName('RDB$FIELD_SCALE').AsInteger])
2413     else
2414     if (FieldType = blr_double) and
2415     (qryFuncArgs.FieldByName('RDB$FIELD_SCALE').AsInteger < 0) then
2416     TypeBuffer := Format('NUMERIC(15, %d)',
2417     [-qryFuncArgs.FieldByName('RDB$FIELD_SCALE').AsInteger])
2418     else
2419     TypeBuffer := ColumnTypes[i].TypeName;
2420     end; { if not PrecisionKnown }
2421     end; { if FCHAR or VARCHAR or CSTRING ... else }
2422    
2423     if qryFunctions.FieldByName('RDB$RETURN_ARGUMENT').AsInteger =
2424     qryFuncArgs.FieldByName('RDB$ARGUMENT_POSITION').AsInteger then
2425     begin
2426     ReturnBuffer := 'RETURNS ' + TypeBuffer;
2427     if qryFuncArgs.FieldByName('RDB$MECHANISM').AsInteger = 0 then
2428     ReturnBuffer := ReturnBuffer + ' BY VALUE ';
2429     if qryFuncArgs.FieldByName('RDB$MECHANISM').AsInteger < 0 then
2430     ReturnBuffer := ReturnBuffer + ' FREE_IT';
2431     end
2432     else
2433     begin
2434     { First arg needs no comma }
2435     if FirstArg then
2436     begin
2437     Line := Line + TypeBuffer;
2438     FirstArg := false;
2439     end
2440     else
2441     Line := Line + ', ' + TypeBuffer;
2442     end; //end_else
2443     qryFuncArgs.Next;
2444     end;
2445     qryFuncArgs.Close;
2446    
2447     FMetaData.Add(Line);
2448     FMetaData.Add(ReturnBuffer);
2449     FMetaData.Add(Format('ENTRY_POINT ''%s'' MODULE_NAME ''%s''%s%s%s',
2450     [qryFunctions.FieldByName('RDB$ENTRYPOINT').AsString,
2451     qryFunctions.FieldByName('RDB$MODULE_NAME').AsString,
2452     Term, LineEnding, LineEnding]));
2453    
2454     qryFunctions.Next;
2455     end;
2456     finally
2457     qryFunctions.Free;
2458     qryFuncArgs.Free;
2459     qryCharSets.Free;
2460     qryFuncPos.Free;
2461     end;
2462     end;
2463    
2464     { ListGenerators
2465     Functional description
2466     Re create all non-system generators }
2467    
2468     procedure TIBExtract.ListGenerators(GeneratorName: String;
2469     ExtractTypes: TExtractTypes);
2470     const
2471     GeneratorSQL =
2472     'SELECT RDB$GENERATOR_NAME ' +
2473     'FROM RDB$GENERATORS ' +
2474     'WHERE ' +
2475     ' (RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG <> 1) ' +
2476     'ORDER BY RDB$GENERATOR_NAME';
2477    
2478     GeneratorNameSQL =
2479     'SELECT RDB$GENERATOR_NAME ' +
2480     'FROM RDB$GENERATORS ' +
2481     'WHERE RDB$GENERATOR_NAME = :GeneratorName AND ' +
2482     ' (RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG <> 1) ' +
2483     'ORDER BY RDB$GENERATOR_NAME';
2484    
2485     GeneratorValueSQL =
2486     'SELECT GEN_ID(%s,0) as GENERATORVALUE From RDB$Database';
2487    
2488     var
2489     qryGenerator : TIBSQL;
2490     qryValue: TIBSQL;
2491     GenName : String;
2492     begin
2493     qryGenerator := TIBSQL.Create(FDatabase);
2494     qryValue := TIBSQL.Create(FDatabase);
2495     try
2496     if GeneratorName = '' then
2497     qryGenerator.SQL.Text := GeneratorSQL
2498     else
2499     begin
2500     qryGenerator.SQL.Text := GeneratorNameSQL;
2501     qryGenerator.Params.ByName('GeneratorName').AsString := GeneratorName;
2502     end;
2503     qryGenerator.ExecQuery;
2504     FMetaData.Add('');
2505     while not qryGenerator.Eof do
2506     begin
2507     GenName := qryGenerator.FieldByName('RDB$GENERATOR_NAME').AsString;
2508     if ((Pos('RDB$',GenName) = 1) and
2509     (GenName[5] in ['0'..'9'])) or
2510     ((Pos('SQL$',GenName) = 1) and
2511     (GenName[5] in ['0'..'9'])) then
2512     begin
2513     qryGenerator.Next;
2514     continue;
2515     end;
2516     FMetaData.Add(Format('CREATE SEQUENCE %s%s',
2517     [QuoteIdentifier( GenName),
2518     Term]));
2519     if etData in ExtractTypes then
2520     begin
2521     qryValue.SQL.Text := Format(GeneratorValueSQL,[GenName]);
2522     qryValue.ExecQuery;
2523     try
2524     if not qryValue.EOF then
2525     FMetaData.Add(Format('ALTER SEQUENCE %s RESTART WITH %d;',
2526     [QuoteIdentifier( GenName),
2527     qryValue.FieldByName('GENERATORVALUE').AsInteger]));
2528     finally
2529     qryValue.Close;
2530     end;
2531     end;
2532     qryGenerator.Next;
2533     end;
2534     finally
2535     qryGenerator.Free;
2536     qryValue.Free;
2537     end;
2538     end;
2539    
2540     { ListIndex
2541     Functional description
2542     Define all non-constraint indices
2543     Use a static SQL query to get the info and print it.
2544    
2545     Uses get_index_segment to provide a key list for each index }
2546    
2547     procedure TIBExtract.ListIndex(ObjectName : String; ExtractType : TExtractType);
2548     const
2549     IndexSQL =
2550     'SELECT IDX.RDB$RELATION_NAME, IDX.RDB$INDEX_NAME, IDX.RDB$UNIQUE_FLAG, ' +
2551     ' IDX.RDB$INDEX_TYPE ' +
2552     'FROM RDB$INDICES IDX JOIN RDB$RELATIONS RELC ON ' +
2553     ' IDX.RDB$RELATION_NAME = RELC.RDB$RELATION_NAME ' +
2554     'WHERE (RELC.RDB$SYSTEM_FLAG <> 1 OR RELC.RDB$SYSTEM_FLAG IS NULL) AND ' +
2555     ' NOT EXISTS (SELECT * FROM RDB$RELATION_CONSTRAINTS RC ' +
2556     ' WHERE RC.RDB$INDEX_NAME = IDX.RDB$INDEX_NAME) ' +
2557     'ORDER BY IDX.RDB$RELATION_NAME, IDX.RDB$INDEX_NAME';
2558    
2559     IndexNameSQL =
2560     'SELECT IDX.RDB$RELATION_NAME, IDX.RDB$INDEX_NAME, IDX.RDB$UNIQUE_FLAG, ' +
2561     ' IDX.RDB$INDEX_TYPE ' +
2562     'FROM RDB$INDICES IDX JOIN RDB$RELATIONS RELC ON ' +
2563     ' IDX.RDB$RELATION_NAME = RELC.RDB$RELATION_NAME ' +
2564     'WHERE (RELC.RDB$SYSTEM_FLAG <> 1 OR RELC.RDB$SYSTEM_FLAG IS NULL) AND ' +
2565     ' RELC.RDB$RELATION_NAME = :RelationName AND ' +
2566     ' NOT EXISTS (SELECT * FROM RDB$RELATION_CONSTRAINTS RC ' +
2567     ' WHERE RC.RDB$INDEX_NAME = IDX.RDB$INDEX_NAME) ' +
2568     'ORDER BY IDX.RDB$RELATION_NAME, IDX.RDB$INDEX_NAME';
2569    
2570     IndexByNameSQL =
2571     'SELECT IDX.RDB$RELATION_NAME, IDX.RDB$INDEX_NAME, IDX.RDB$UNIQUE_FLAG, ' +
2572     ' IDX.RDB$INDEX_TYPE ' +
2573     'FROM RDB$INDICES IDX JOIN RDB$RELATIONS RELC ON ' +
2574     ' IDX.RDB$RELATION_NAME = RELC.RDB$RELATION_NAME ' +
2575     'WHERE (RELC.RDB$SYSTEM_FLAG <> 1 OR RELC.RDB$SYSTEM_FLAG IS NULL) AND ' +
2576     ' IDX.RDB$INDEX_NAME = :IndexName AND ' +
2577     ' NOT EXISTS (SELECT * FROM RDB$RELATION_CONSTRAINTS RC ' +
2578     ' WHERE RC.RDB$INDEX_NAME = IDX.RDB$INDEX_NAME) ' +
2579     'ORDER BY IDX.RDB$RELATION_NAME, IDX.RDB$INDEX_NAME';
2580    
2581     var
2582     qryIndex : TIBSQL;
2583     First : Boolean;
2584     Unique, IdxType, Line : String;
2585     begin
2586     First := true;
2587     qryIndex := TIBSQL.Create(FDatabase);
2588     try
2589     if ObjectName = '' then
2590     qryIndex.SQL.Text := IndexSQL
2591     else
2592     begin
2593     if ExtractType = etTable then
2594     begin
2595     qryIndex.SQL.Text := IndexNameSQL;
2596     qryIndex.Params.ByName('RelationName').AsString := ObjectName;
2597     end
2598     else
2599     begin
2600     qryIndex.SQL.Text := IndexByNameSQL;
2601     qryIndex.Params.ByName('IndexName').AsString := ObjectName;
2602     end;
2603     end;
2604     qryIndex.ExecQuery;
2605     while not qryIndex.Eof do
2606     begin
2607     if First then
2608     begin
2609     if ObjectName = '' then
2610     FMetaData.Add(LineEnding + '/* Index definitions for all user tables */' + LineEnding)
2611     else
2612     FMetaData.Add(LineEnding + '/* Index definitions for ' + ObjectName + ' */' + LineEnding);
2613     First := false;
2614     end; //end_if
2615    
2616     if qryIndex.FieldByName('RDB$UNIQUE_FLAG').AsInteger = 1 then
2617     Unique := ' UNIQUE'
2618     else
2619     Unique := '';
2620    
2621     if qryIndex.FieldByName('RDB$INDEX_TYPE').AsInteger = 1 then
2622     IdxType := ' DESCENDING'
2623     else
2624     IdxType := '';
2625    
2626     Line := Format('CREATE%s%s INDEX %s ON %s(', [Unique, IdxType,
2627     QuoteIdentifier(
2628     qryIndex.FieldByName('RDB$INDEX_NAME').AsString),
2629     QuoteIdentifier(
2630     qryIndex.FieldByName('RDB$RELATION_NAME').AsString)]);
2631    
2632     Line := Line + GetIndexSegments(qryIndex.FieldByName('RDB$INDEX_NAME').AsString) +
2633     ')' + Term;
2634    
2635     FMetaData.Add(Line);
2636     qryIndex.Next;
2637     end;
2638     finally
2639     qryIndex.Free;
2640     end;
2641     end;
2642    
2643     { ListViews
2644     Functional description
2645     Show text of views.
2646     Use a SQL query to get the info and print it.
2647     Note: This should also contain check option }
2648    
2649     procedure TIBExtract.ListViews(ViewName : String);
2650     const
2651     ViewSQL =
2652     'with recursive Views as ( ' +
2653     ' Select RDB$RELATION_NAME, 1 as ViewLevel from RDB$RELATIONS ' +
2654     ' Where RDB$RELATION_TYPE = 1 and RDB$SYSTEM_FLAG = 0 '+
2655     ' UNION ALL ' +
2656     ' Select D.RDB$DEPENDED_ON_NAME, ViewLevel + 1 From RDB$DEPENDENCIES D ' +
2657     ' JOIN Views on Views.RDB$RELATION_NAME = D.RDB$DEPENDENT_NAME ' +
2658     ' and Views.RDB$RELATION_NAME <> D.RDB$DEPENDED_ON_NAME ' +
2659     ' JOIN RDB$RELATIONS R On R.RDB$RELATION_NAME = D.RDB$DEPENDED_ON_NAME ' +
2660     ')' +
2661     'SELECT R.RDB$RELATION_NAME, R.RDB$OWNER_NAME, R.RDB$VIEW_SOURCE FROM RDB$RELATIONS R ' +
2662     'JOIN ( ' +
2663     'Select RDB$RELATION_NAME, max(ViewLevel) as ViewLevel From Views ' +
2664     'Group By RDB$RELATION_NAME) A On A.RDB$RELATION_NAME = R.RDB$RELATION_NAME ' +
2665     'Where R.RDB$RELATION_TYPE = 1 and R.RDB$SYSTEM_FLAG = 0 '+
2666     'Order by A.ViewLevel desc, R.RDB$RELATION_NAME asc';
2667    
2668     {
2669     'SELECT RDB$RELATION_NAME, RDB$OWNER_NAME, RDB$VIEW_SOURCE ' +
2670     'FROM RDB$RELATIONS ' +
2671     'WHERE ' +
2672     ' (RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL) AND ' +
2673     ' NOT RDB$VIEW_BLR IS NULL AND ' +
2674     ' RDB$FLAGS = 1 ' +
2675     'ORDER BY RDB$RELATION_ID'; }
2676    
2677     ViewNameSQL =
2678     'SELECT RDB$RELATION_NAME, RDB$OWNER_NAME, RDB$VIEW_SOURCE ' +
2679     'FROM RDB$RELATIONS ' +
2680     'WHERE ' +
2681     ' (RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL) AND ' +
2682     ' NOT RDB$VIEW_BLR IS NULL AND ' +
2683     ' RDB$FLAGS = 1 AND ' +
2684     ' RDB$RELATION_NAME = :ViewName ' +
2685     'ORDER BY RDB$RELATION_ID';
2686    
2687     ColumnSQL =
2688     'SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS ' +
2689     'WHERE ' +
2690     ' RDB$RELATION_NAME = :RELATION_NAME ' +
2691     'ORDER BY RDB$FIELD_POSITION';
2692    
2693     var
2694     qryView, qryColumns : TIBSQL;
2695     SList : TStrings;
2696     begin
2697     qryView := TIBSQL.Create(FDatabase);
2698     qryColumns := TIBSQL.Create(FDatabase);
2699     SList := TStringList.Create;
2700     try
2701     if ViewName = '' then
2702     qryView.SQL.Text := ViewSQL
2703     else
2704     begin
2705     qryView.SQL.Text := ViewNameSQL;
2706     qryView.Params.ByName('ViewName').AsString := ViewName;
2707     end;
2708     qryColumns.SQL.Text := ColumnSQL;
2709     qryView.ExecQuery;
2710     while not qryView.Eof do
2711     begin
2712     SList.Add(Format('%s/* View: %s, Owner: %s */%s',
2713     [LineEnding, qryView.FieldByName('RDB$RELATION_NAME').AsString,
2714     qryView.FieldByName('RDB$OWNER_NAME').AsString, LineEnding]));
2715    
2716     SList.Add(Format('CREATE VIEW %s (', [QuoteIdentifier(
2717     qryView.FieldByName('RDB$RELATION_NAME').AsString)]));
2718    
2719     qryColumns.Params.ByName('RELATION_NAME').AsString :=
2720     qryView.FieldByName('RDB$RELATION_NAME').AsString;
2721     qryColumns.ExecQuery;
2722     while not qryColumns.Eof do
2723     begin
2724     SList.Add(' ' + QuoteIdentifier(
2725     qryColumns.FieldByName('RDB$FIELD_NAME').AsString));
2726     qryColumns.Next;
2727     if not qryColumns.Eof then
2728     SList.Strings[SList.Count - 1] := SList.Strings[SList.Count - 1] + ', ';
2729     end;
2730     qryColumns.Close;
2731     SList.Text := SList.Text + Format(') AS%s', [LineEnding]);
2732     if not qryView.FieldByName('RDB$VIEW_SOURCE').IsNull then
2733     SList.Text := SList.Text + qryView.FieldByName('RDB$VIEW_SOURCE').AsString;
2734     SList.Text := SList.Text + Format('%s%s', [Term, LineEnding]);
2735     FMetaData.AddStrings(SList);
2736     SList.Clear;
2737     qryView.Next;
2738     end;
2739     finally
2740     qryView.Free;
2741     qryColumns.Free;
2742     SList.Free;
2743     end;
2744     end;
2745    
2746     { PrintSet
2747     Functional description
2748     print (using ISQL_printf) the word "SET"
2749     if the first line of the ALTER DATABASE
2750     settings options. Also, add trailing
2751     comma for end of prior line if needed.
2752    
2753     uses Print_buffer, a global }
2754    
2755     function TIBExtract.PrintSet(var Used: Boolean) : String;
2756     begin
2757     if not Used then
2758     begin
2759     Result := ' SET ';
2760     Used := true;
2761     end
2762     else
2763     Result := Format(', %s ', [LineEnding]);
2764     end;
2765    
2766     {
2767     PrintValidation
2768     Functional description
2769     This does some minor syntax adjustmet for extracting
2770     validation blobs and computed fields.
2771     if it does not start with the word CHECK
2772     if this is a computed field blob,look for () or insert them.
2773     if flag = false, this is a validation clause,
2774     if flag = true, this is a computed field }
2775    
2776     function TIBExtract.PrintValidation(ToValidate: String;
2777     flag: Boolean): String;
2778     var
2779     IsSQL : Boolean;
2780     begin
2781     IsSql := false;
2782    
2783     Result := '';
2784     ToValidate := Trim(ToValidate);
2785    
2786     if flag then
2787     begin
2788     if ToValidate[1] = '(' then
2789     IsSQL := true;
2790     end
2791     else
2792     if (Pos(ToValidate, 'check') = 1) or (Pos(ToValidate, 'CHECK') = 1) then
2793     IsSQL := TRUE;
2794    
2795     if not IsSQL then
2796     begin
2797     if Flag then
2798     Result := Result + '/* ' + ToValidate + ' */'
2799     else
2800     Result := Result + '(' + ToValidate + ')';
2801     end
2802     else
2803     Result := ToValidate;
2804     end;
2805    
2806     procedure TIBExtract.SetDatabase(const Value: TIBDatabase);
2807     begin
2808     if (csLoading in ComponentState) or (FDatabase <> Value) then
2809     begin
2810     FDatabase := Value;
2811     if (not Assigned(FTransaction)) and (FDatabase <> nil) then
2812     Transaction := FDatabase.DefaultTransaction;
2813     FDatabaseInfo.Database := FDatabase;
2814     end;
2815     end;
2816    
2817     procedure TIBExtract.SetTransaction(const Value: TIBTransaction);
2818     begin
2819     if FTransaction <> Value then
2820     begin
2821     FTransaction := Value;
2822     if (not Assigned(FDatabase)) and (FTransaction <> nil) then
2823     Database := FTransaction.DefaultDatabase;
2824     end;
2825     end;
2826    
2827     procedure TIBExtract.ExtractObject(ObjectType : TExtractObjectTypes;
2828     ObjectName : String = ''; ExtractTypes : TExtractTypes = []);
2829     var
2830     DidActivate : Boolean;
2831     begin
2832     DidActivate := false;
2833     if not FTransaction.Active then
2834     begin
2835     FTransaction.StartTransaction;
2836     DidActivate := true;
2837     end;
2838     FMetaData.Clear;
2839     case ObjectType of
2840     eoDatabase : ExtractDDL(true, '', ExtractTypes);
2841     eoDomain :
2842     if etTable in ExtractTypes then
2843     ListDomains(ObjectName, etTable)
2844     else
2845     ListDomains(ObjectName);
2846     eoTable :
2847     begin
2848     if ObjectName <> '' then
2849     begin
2850     if etDomain in ExtractTypes then
2851     ListDomains(ObjectName, etTable);
2852     ExtractListTable(ObjectName, '', false);
2853     if etIndex in ExtractTypes then
2854     ListIndex(ObjectName, etTable);
2855     if etForeign in ExtractTypes then
2856     ListForeign(ObjectName, etTable);
2857     if etCheck in ExtractTypes then
2858     ListCheck(ObjectName, etTable);
2859     if etTrigger in ExtractTypes then
2860     begin
2861     if etGrant in ExtractTypes then
2862     ListTriggers(ObjectName, [etTable,etGrant])
2863     else
2864     ListTriggers(ObjectName, [etTable]);
2865     end;
2866     if etGrant in ExtractTypes then
2867     ShowGrants(ObjectName, Term);
2868     if etData in ExtractTypes then
2869     ListData(ObjectName);
2870     end
2871     else
2872     ListAllTables(true);
2873     end;
2874     eoView :
2875     begin
2876     ListViews(ObjectName);
2877     if ObjectName <> '' then
2878     begin
2879     if etTrigger in ExtractTypes then
2880     begin
2881     if etGrant in ExtractTypes then
2882     ListTriggers(ObjectName, [etTable,etGrant])
2883     else
2884     ListTriggers(ObjectName, [etTable]);
2885     end;
2886     if etGrant in ExtractTypes then
2887     ShowGrants(ObjectName, Term);
2888     end;
2889     end;
2890     eoProcedure :
2891     begin
2892     ListProcs(pdCreateProc,ObjectName,etGrant in ExtractTypes);
2893     if (ObjectName <> '' ) and (etGrant in ExtractTypes) then
2894     ShowGrants(ObjectName, Term);
2895     end;
2896     eoFunction : ListFunctions(ObjectName);
2897     eoGenerator : ListGenerators(ObjectName,ExtractTypes);
2898     eoException : ListException(ObjectName);
2899     eoBLOBFilter : ListFilters(ObjectName);
2900     eoRole : ListRoles(ObjectName,etGrant in ExtractTypes);
2901     eoTrigger :
2902     if etTable in ExtractTypes then
2903     begin
2904     if etGrant in ExtractTypes then
2905     ListTriggers(ObjectName, [etTable,etGrant])
2906     else
2907     ListTriggers(ObjectName, [etTable])
2908     end
2909     else
2910     if etGrant in ExtractTypes then
2911     ListTriggers(ObjectName,[etTrigger,etGrant])
2912     else
2913     ListTriggers(ObjectName);
2914     eoForeign :
2915     if etTable in ExtractTypes then
2916     ListForeign(ObjectName, etTable)
2917     else
2918     ListForeign(ObjectName);
2919     eoIndexes :
2920     if etTable in ExtractTypes then
2921     ListIndex(ObjectName, etTable)
2922     else
2923     ListIndex(ObjectName);
2924     eoChecks :
2925     if etTable in ExtractTypes then
2926     ListCheck(ObjectName, etTable)
2927     else
2928     ListCheck(ObjectName);
2929     eoData : ListData(ObjectName);
2930     end;
2931     if DidActivate then
2932     FTransaction.Commit;
2933     end;
2934    
2935     function TIBExtract.GetFieldType(FieldType, FieldSubType, FieldScale,
2936     FieldSize, FieldPrec, FieldLen: Integer): String;
2937     var
2938     i : Integer;
2939     PrecisionKnown : Boolean;
2940     begin
2941     Result := '';
2942     for i := Low(ColumnTypes) to High(ColumnTypes) do
2943     if FieldType = ColumnTypes[i].SQLType then
2944     begin
2945     PrecisionKnown := FALSE;
2946     if FDatabaseInfo.ODSMajorVersion >= ODS_VERSION10 then
2947     begin
2948     if FieldType in [blr_short, blr_long, blr_int64] then
2949     begin
2950     { We are ODS >= 10 and could be any Dialect }
2951     if (FDatabaseInfo.DBSQLDialect >= 3) and
2952     (FieldPrec <> 0) and
2953     (FieldSubType > 0) and
2954     (FieldSubType <= MAX_INTSUBTYPES) then
2955     begin
2956     Result := Result + Format('%s(%d, %d)', [
2957     IntegralSubtypes [FieldSubType],
2958     FieldPrec,
2959     -1 * FieldScale]);
2960     PrecisionKnown := true;
2961     end;
2962     end;
2963     end;
2964     if PrecisionKnown = false then
2965     begin
2966     { Take a stab at numerics and decimals }
2967     if (FieldType = blr_short) and
2968     (FieldScale < 0) then
2969     Result := Result + Format('NUMERIC(4, %d)',
2970     [-FieldScale] )
2971     else
2972     if (FieldType = blr_long) and
2973     (FieldScale < 0) then
2974     Result := Result + Format('NUMERIC(9, %d)',
2975     [-FieldScale] )
2976     else
2977     if (FieldType = blr_double) and
2978     (FieldScale < 0) then
2979     Result := Result + Format('NUMERIC(15, %d)',
2980     [-FieldScale] )
2981     else
2982     Result := Result + ColumnTypes[i].TypeName;
2983     end;
2984     break;
2985     end;
2986     if (FieldType in [blr_text, blr_varying]) and
2987     (FieldSize <> 0) then
2988     Result := Result + Format('(%d)', [FieldSize]);
2989     end;
2990    
2991     { S H O W _ g r a n t s
2992     Functional description
2993     Show grants for given object name
2994     This function is also called by extract for privileges.
2995     It must extract granted privileges on tables/views to users,
2996     - these may be compound, so put them on the same line.
2997     Grant execute privilege on procedures to users
2998     Grant various privileges to procedures.
2999     All privileges may have the with_grant option set. }
3000    
3001     procedure TIBExtract.ShowGrants(MetaObject: String; Terminator: String;
3002     NoUserGrants: boolean);
3003     const
3004     GrantsBaseSelect =
3005     'Select Trim(RDB$USER) as RDB$USER,List("Privileges") as Privileges, '+
3006     'coalesce(RDB$GRANT_OPTION,0) as RDB$GRANT_OPTION,METAOBJECTNAME, '+
3007     'RDB$USER_TYPE, RDB$OBJECT_TYPE, '+
3008     'case T2.RDB$TYPE_NAME '+
3009     ' When ''RELATION'' then ''TABLE'' '+
3010     ' When ''FIELD'' then ''DOMAIN'' '+
3011     'Else T2.RDB$TYPE_NAME End as OBJECT_TYPE_NAME, '+
3012     'T1.RDB$TYPE_NAME as USER_TYPE_NAME, '+
3013     'case '+
3014     'When coalesce(RDB$GRANT_OPTION,0) <> 0 and RDB$USER_TYPE = 13 then '' WITH ADMIN OPTION'' '+
3015     'When coalesce(RDB$GRANT_OPTION,0) <> 0 and RDB$USER_TYPE <> 13 then '' WITH GRANT OPTION'' '+
3016     'ELSE '''' End as GRANTOPTION, '+
3017     'case When RDB$OWNER_NAME = RDB$GRANTOR then NULL '+
3018     'else Trim(RDB$GRANTOR) End as GRANTEDBY '+
3019     'From ( '+
3020     'Select PR.RDB$USER,PR.RDB$RELATION_NAME as METAOBJECTNAME, LIST(DISTINCT Trim(Case PR.RDB$PRIVILEGE '+
3021     'When ''X'' then ''EXECUTE'' '+
3022     'When ''S'' then ''SELECT'' '+
3023     'When ''U'' then ''UPDATE'' '+
3024     'When ''D'' then ''DELETE'' '+
3025     'When ''R'' then ''REFERENCES'' '+
3026     'When ''G'' then ''USAGE'' '+
3027     'When ''I'' then ''INSERT'' '+
3028     'end )) as "Privileges", '+
3029     'PR.RDB$GRANT_OPTION, PR.RDB$USER_TYPE, PR.RDB$OBJECT_TYPE,OW.RDB$OWNER_NAME,PR.RDB$GRANTOR '+
3030     'FROM RDB$USER_PRIVILEGES PR '+
3031     'JOIN ObjectOwners OW On OW.METAOBJECTNAME = PR.RDB$RELATION_NAME and OW.ObjectType = PR.RDB$OBJECT_TYPE '+
3032     'Where PR.RDB$PRIVILEGE <> ''M'' and (PR.RDB$PRIVILEGE <> ''U'' or PR.RDB$FIELD_NAME is null) '+
3033     'Group By PR.RDB$USER,PR.RDB$RELATION_NAME,PR.RDB$GRANT_OPTION, PR.RDB$USER_TYPE, PR.RDB$OBJECT_TYPE,OW.RDB$OWNER_NAME,PR.RDB$GRANTOR '+
3034     'UNION '+
3035     'Select PR.RDB$USER,PR.RDB$RELATION_NAME, ''Update('' || List(Trim(PR.RDB$FIELD_NAME)) || '')'', '+
3036     'PR.RDB$GRANT_OPTION, PR.RDB$USER_TYPE, PR.RDB$OBJECT_TYPE, OW.RDB$OWNER_NAME,PR.RDB$GRANTOR '+
3037     'FROM RDB$USER_PRIVILEGES PR '+
3038     'JOIN ObjectOwners OW On OW.METAOBJECTNAME = PR.RDB$RELATION_NAME and OW.ObjectType = PR.RDB$OBJECT_TYPE '+
3039     'Where PR.RDB$PRIVILEGE = ''U'' and PR.RDB$FIELD_NAME is not null '+
3040     'Group By PR.RDB$USER,PR.RDB$RELATION_NAME,PR.RDB$GRANT_OPTION, PR.RDB$USER_TYPE, PR.RDB$OBJECT_TYPE, OW.RDB$OWNER_NAME,PR.RDB$GRANTOR) A '+
3041     'JOIN RDB$TYPES T1 On T1.RDB$TYPE = RDB$USER_TYPE and T1.RDB$FIELD_NAME = ''RDB$OBJECT_TYPE'' '+
3042     'JOIN RDB$TYPES T2 On T2.RDB$TYPE = RDB$OBJECT_TYPE and T2.RDB$FIELD_NAME = ''RDB$OBJECT_TYPE'' '+
3043     'Where METAOBJECTNAME = :METAOBJECTNAME and RDB$USER <> RDB$OWNER_NAME '+
3044     'Group By RDB$USER,RDB$GRANT_OPTION, RDB$USER_TYPE, RDB$OBJECT_TYPE,METAOBJECTNAME,RDB$GRANTOR,T2.RDB$TYPE_NAME,T1.RDB$TYPE_NAME,RDB$OWNER_NAME '+
3045     'ORDER BY RDB$USER, RDB$OBJECT_TYPE';
3046    
3047     GrantsSQL12 =
3048     'with ObjectOwners As ( '+
3049     ' Select RDB$RELATION_NAME as METAOBJECTNAME, RDB$OWNER_NAME, 0 as ObjectType '+
3050     ' From RDB$RELATIONS '+
3051     ' UNION '+
3052     ' Select RDB$PROCEDURE_NAME as METAOBJECTNAME, RDB$OWNER_NAME, 5 as ObjectType '+
3053     ' From RDB$PROCEDURES '+
3054     ' UNION '+
3055     ' Select RDB$EXCEPTION_NAME as METAOBJECTNAME, RDB$OWNER_NAME, 7 as ObjectType '+
3056     ' From RDB$EXCEPTIONS '+
3057     ' UNION '+
3058     ' Select RDB$FIELD_NAME as METAOBJECTNAME, RDB$OWNER_NAME, 9 as ObjectType '+
3059     ' From RDB$FIELDS Where RDB$FIELD_NAME not Similar to ''RDB$%|SEC$%|MON$%|SQL$%'' '+
3060     ' UNION '+
3061     ' Select RDB$GENERATOR_NAME as METAOBJECTNAME, RDB$OWNER_NAME, 14 as ObjectType '+
3062     ' From RDB$GENERATORS '+
3063     ' UNION '+
3064     ' Select RDB$CHARACTER_SET_NAME as METAOBJECTNAME, RDB$OWNER_NAME, 11 as ObjectType '+
3065     ' From RDB$CHARACTER_SETS '+
3066     ' UNION '+
3067     ' Select RDB$COLLATION_NAME as METAOBJECTNAME, RDB$OWNER_NAME, 17 as ObjectType '+
3068     ' From RDB$COLLATIONS '+
3069     ') '+ GrantsBaseSelect;
3070    
3071     GrantsSQL =
3072     'with ObjectOwners As ( '+
3073     'Select RDB$RELATION_NAME as METAOBJECTNAME, RDB$OWNER_NAME, 0 as ObjectType '+
3074     'From RDB$RELATIONS '+
3075     'UNION '+
3076     'Select RDB$PROCEDURE_NAME as METAOBJECTNAME, RDB$OWNER_NAME, 5 as ObjectType '+
3077     'From RDB$PROCEDURES '+
3078     'UNION '+
3079     'Select RDB$EXCEPTION_NAME as METAOBJECTNAME, ''SYSDBA'', 7 as ObjectType '+
3080     'From RDB$EXCEPTIONS '+
3081     'UNION '+
3082     'Select RDB$GENERATOR_NAME as METAOBJECTNAME, ''SYSDBA'', 14 as ObjectType '+
3083     'From RDB$GENERATORS '+
3084     'UNION '+
3085     'Select RDB$CHARACTER_SET_NAME as METAOBJECTNAME, ''SYSDBA'', 11 as ObjectType '+
3086     'From RDB$CHARACTER_SETS '+
3087     ') '+ GrantsBaseSelect;
3088    
3089     var qryOwnerPriv : TIBSQL;
3090    
3091     begin
3092     if MetaObject = '' then
3093     exit;
3094    
3095     qryOwnerPriv := TIBSQL.Create(FDatabase);
3096     try
3097     if FDatabaseInfo.ODSMajorVersion >= ODS_VERSION12 then
3098     qryOwnerPriv.SQL.Text := GrantsSQL12
3099     else
3100     qryOwnerPriv.SQL.Text := GrantsSQL;
3101     qryOwnerPriv.Params.ByName('METAOBJECTNAME').AsString := MetaObject;
3102     qryOwnerPriv.ExecQuery;
3103     while not qryOwnerPriv.Eof do
3104     begin
3105     if not NoUserGrants or (qryOwnerPriv.FieldByName('RDB$USER_TYPE').AsInteger <> obj_user)
3106     or (qryOwnerPriv.FieldByName('RDB$USER').AsString = 'PUBLIC') then
3107     begin
3108     if qryOwnerPriv.FieldByName('GRANTEDBY').IsNull then
3109     FMetaData.Add(Format('GRANT %s ON %s %s TO %s %s %s %s', [
3110     qryOwnerPriv.FieldByName('Privileges').AsString,
3111     qryOwnerPriv.FieldByName('OBJECT_TYPE_NAME').AsString,
3112     QuoteIdentifier(qryOwnerPriv.FieldByName('METAOBJECTNAME').AsString),
3113     qryOwnerPriv.FieldByName('USER_TYPE_NAME').AsString,
3114     QuoteIdentifier(qryOwnerPriv.FieldByName('RDB$USER').AsString),
3115     qryOwnerPriv.FieldByName('GRANTOPTION').AsString,
3116     Terminator]))
3117     else
3118     FMetaData.Add(Format('GRANT %s ON %s %s TO %s %s %s GRANTED BY %s %s', [
3119     qryOwnerPriv.FieldByName('Privileges').AsString,
3120     qryOwnerPriv.FieldByName('OBJECT_TYPE_NAME').AsString,
3121     QuoteIdentifier(qryOwnerPriv.FieldByName('METAOBJECTNAME').AsString),
3122     qryOwnerPriv.FieldByName('USER_TYPE_NAME').AsString,
3123     QuoteIdentifier(qryOwnerPriv.FieldByName('RDB$USER').AsString),
3124     qryOwnerPriv.FieldByName('GRANTOPTION').AsString,
3125     QuoteIdentifier(qryOwnerPriv.FieldByName('GRANTEDBY').AsString),
3126     Terminator]));
3127     end;
3128     qryOwnerPriv.Next;
3129     end;
3130     qryOwnerPriv.Close;
3131     finally
3132     qryOwnerPriv.Free;
3133     end;
3134     end;
3135    
3136     procedure TIBExtract.ShowGrantsTo(MetaObject: String; ObjectType: integer; Terminator: String);
3137     const
3138     GrantsSQL =
3139     'Select Trim(RDB$USER) as RDB$USER,List("Privileges") as Privileges, '+
3140     'coalesce(RDB$GRANT_OPTION,0) as RDB$GRANT_OPTION,METAOBJECTNAME, '+
3141     'RDB$USER_TYPE, RDB$OBJECT_TYPE, '+
3142     'case RDB$OBJECT_TYPE '+
3143     'When 0 then ''TABLE'' '+
3144     'When 5 then ''PROCEDURE'' '+
3145     'When 7 then ''EXCEPTION'' '+
3146     'When 11 then ''CHARACTER SET'' '+
3147     'ELSE NULL END as OBJECT_TYPE_NAME, '+
3148     'case RDB$USER_TYPE '+
3149     'When 5 then ''PROCEDURE'' '+
3150     'When 2 then ''TRIGGER'' '+
3151     'When 8 then ''USER'' '+
3152     'When 13 then ''ROLE'' '+
3153     'ELSE NULL END as USER_TYPE_NAME, '+
3154     'case '+
3155     'When coalesce(RDB$GRANT_OPTION,0) <> 0 and RDB$USER_TYPE = 13 then '' WITH ADMIN OPTION'' '+
3156     'When coalesce(RDB$GRANT_OPTION,0) <> 0 and RDB$USER_TYPE <> 13 then '' WITH GRANT OPTION'' '+
3157     'ELSE '''' End as GRANTOPTION '+
3158     'From ( '+
3159     'Select PR.RDB$USER,PR.RDB$RELATION_NAME as METAOBJECTNAME, LIST(DISTINCT Trim(Case PR.RDB$PRIVILEGE '+
3160     'When ''X'' then ''EXECUTE'' '+
3161     'When ''S'' then ''SELECT'' '+
3162     'When ''U'' then ''UPDATE'' '+
3163     'When ''D'' then ''DELETE'' '+
3164     'When ''R'' then ''REFERENCES'' '+
3165     'When ''G'' then ''USAGE'' '+
3166     'When ''I'' then ''INSERT'' end )) as "Privileges", '+
3167     'PR.RDB$GRANT_OPTION, PR.RDB$USER_TYPE, PR.RDB$OBJECT_TYPE '+
3168     'FROM RDB$USER_PRIVILEGES PR '+
3169     'Where PR.RDB$PRIVILEGE <> ''M'' and (PR.RDB$PRIVILEGE <> ''U'' or PR.RDB$FIELD_NAME is null) '+
3170     'Group By PR.RDB$USER,PR.RDB$RELATION_NAME,PR.RDB$GRANT_OPTION, PR.RDB$USER_TYPE, PR.RDB$OBJECT_TYPE '+
3171     'UNION '+
3172     'Select PR.RDB$USER,PR.RDB$RELATION_NAME, ''Update('' || List(Trim(PR.RDB$FIELD_NAME)) || '')'', '+
3173     'PR.RDB$GRANT_OPTION, PR.RDB$USER_TYPE, PR.RDB$OBJECT_TYPE '+
3174     'FROM RDB$USER_PRIVILEGES PR '+
3175     'Where PR.RDB$PRIVILEGE = ''U'' and PR.RDB$FIELD_NAME is not null '+
3176     'Group By PR.RDB$USER,PR.RDB$RELATION_NAME,PR.RDB$GRANT_OPTION, PR.RDB$USER_TYPE, PR.RDB$OBJECT_TYPE) '+
3177     'Where RDB$USER = :METAOBJECTNAME and RDB$USER_TYPE = :USERTYPE '+
3178     'Group By RDB$USER,RDB$GRANT_OPTION, RDB$USER_TYPE, RDB$OBJECT_TYPE, METAOBJECTNAME '+
3179     'ORDER BY METAOBJECTNAME';
3180    
3181     var qryOwnerPriv : TIBSQL;
3182    
3183     begin
3184     if MetaObject = '' then
3185     exit;
3186    
3187     qryOwnerPriv := TIBSQL.Create(FDatabase);
3188     try
3189     qryOwnerPriv.SQL.Text := GrantsSQL;
3190     qryOwnerPriv.Params.ByName('METAOBJECTNAME').AsString := MetaObject;
3191     qryOwnerPriv.Params.ByName('USERTYPE').AsInteger := ObjectType;
3192     qryOwnerPriv.ExecQuery;
3193     while not qryOwnerPriv.Eof do
3194     begin
3195     FMetaData.Add(Format('GRANT %s ON %s %s TO %s %s %s%s', [
3196     qryOwnerPriv.FieldByName('Privileges').AsString,
3197     qryOwnerPriv.FieldByName('OBJECT_TYPE_NAME').AsString,
3198     QuoteIdentifier(qryOwnerPriv.FieldByName('METAOBJECTNAME').AsString),
3199     qryOwnerPriv.FieldByName('USER_TYPE_NAME').AsString,
3200     QuoteIdentifier(qryOwnerPriv.FieldByName('RDB$USER').AsString),
3201     qryOwnerPriv.FieldByName('GRANTOPTION').AsString,
3202     Terminator]));
3203     qryOwnerPriv.Next;
3204     end;
3205     qryOwnerPriv.Close;
3206     finally
3207     qryOwnerPriv.Free;
3208     end;
3209     FMetaData.Add('');
3210     end;
3211    
3212     { ShowGrantRoles
3213     Functional description
3214     Show grants for given role name
3215     This function is also called by extract for privileges.
3216     All membership privilege may have the with_admin option set. }
3217    
3218     procedure TIBExtract.ShowGrantRoles(Terminator: String);
3219     const
3220     RoleSQL =
3221     'SELECT RDB$USER, RDB$GRANT_OPTION, RDB$RELATION_NAME ' +
3222     'FROM RDB$USER_PRIVILEGES ' +
3223     'WHERE ' +
3224     ' RDB$OBJECT_TYPE = %d AND ' +
3225     ' RDB$USER_TYPE = %d AND ' +
3226     ' RDB$PRIVILEGE = ''M'' ' +
3227     'ORDER BY RDB$RELATION_NAME, RDB$USER';
3228    
3229     var
3230     WithOption, UserString : String;
3231     qryRole : TIBSQL;
3232    
3233     begin
3234     qryRole := TIBSQL.Create(FDatabase);
3235     try
3236     qryRole.SQL.Text := Format(RoleSQL, [obj_sql_role, obj_user]);
3237     qryRole.ExecQuery;
3238     while not qryRole.Eof do
3239     begin
3240     UserString := Trim(qryRole.FieldByName('RDB$USER').AsString);
3241    
3242     if (not qryRole.FieldByName('RDB$GRANT_OPTION').IsNull) and
3243     (qryRole.FieldByName('RDB$GRANT_OPTION').AsInteger = 1) then
3244     WithOption := ' WITH ADMIN OPTION'
3245     else
3246     WithOption := '';
3247     FMetaData.Add(Format('GRANT %s TO %s%s%s%s',
3248     [ QuoteIdentifier( qryRole.FieldByName('RDB$RELATION_NAME').AsString),
3249     UserString, WithOption, Terminator, LineEnding]));
3250    
3251     qryRole.Next;
3252     end;
3253     finally
3254     qryRole.Free;
3255     end;
3256     end;
3257    
3258     { GetProcedureArgs
3259     Functional description
3260     This function extract the procedure parameters and adds it to the
3261     extract file }
3262    
3263     procedure TIBExtract.GetProcedureArgs(Proc: String);
3264     const
3265     { query to retrieve the input parameters. }
3266     ProcHeaderSQL =
3267     'SELECT * ' +
3268     ' FROM RDB$PROCEDURE_PARAMETERS PRM JOIN RDB$FIELDS FLD ON ' +
3269     ' PRM.RDB$FIELD_SOURCE = FLD.RDB$FIELD_NAME ' +
3270     'WHERE ' +
3271     ' PRM.RDB$PROCEDURE_NAME = :PROCNAME AND ' +
3272     ' PRM.RDB$PARAMETER_TYPE = :Input ' +
3273     'ORDER BY PRM.RDB$PARAMETER_NUMBER';
3274    
3275     var
3276     FirstTime, PrecisionKnown : Boolean;
3277     Line : String;
3278     qryHeader : TIBSQL;
3279    
3280     function FormatParamStr : String;
3281     var
3282     i, CollationID, CharSetID : Integer;
3283     begin
3284     Result := Format(' %s ', [qryHeader.FieldByName('RDB$PARAMETER_NAME').AsString]);
3285     for i := Low(ColumnTypes) to High(ColumnTypes) do
3286     if qryHeader.FieldByName('RDB$FIELD_TYPE').AsInteger = ColumnTypes[i].SQLType then
3287     begin
3288     PrecisionKnown := FALSE;
3289     if FDatabaseInfo.ODSMajorVersion >= ODS_VERSION10 then
3290     begin
3291     if qryHeader.FieldByName('RDB$FIELD_TYPE').AsInteger in [blr_short, blr_long, blr_int64] then
3292     begin
3293     { We are ODS >= 10 and could be any Dialect }
3294     if (FDatabaseInfo.DBSQLDialect >= 3) and
3295     (not qryHeader.FieldByName('RDB$FIELD_PRECISION').IsNull) and
3296     (qryHeader.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger > 0) and
3297     (qryHeader.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger <= MAX_INTSUBTYPES) then
3298     begin
3299     Result := Result + Format('%s(%d, %d)', [
3300     IntegralSubtypes [qryHeader.FieldByName('RDB$FIELD_SUB_TYPE').AsInteger],
3301     qryHeader.FieldByName('RDB$FIELD_PRECISION').AsInteger,
3302     -1 * qryHeader.FieldByName('RDB$FIELD_SCALE').AsInteger]);
3303     PrecisionKnown := true;
3304     end;
3305     end;
3306     end;
3307     if PrecisionKnown = false then
3308     begin
3309     { Take a stab at numerics and decimals }
3310     if (qryHeader.FieldByName('RDB$FIELD_TYPE').AsInteger = blr_short) and
3311     (qryHeader.FieldByName('RDB$FIELD_SCALE').AsInteger < 0) then
3312     Result := Result + Format('NUMERIC(4, %d)',
3313     [-qryHeader.FieldByName('RDB$FIELD_SCALE').AsInteger] )
3314     else
3315     if (qryHeader.FieldByName('RDB$FIELD_TYPE').AsInteger = blr_long) and
3316     (qryHeader.FieldByName('RDB$FIELD_SCALE').AsInteger < 0) then
3317     Result := Result + Format('NUMERIC(9, %d)',
3318     [-qryHeader.FieldByName('RDB$FIELD_SCALE').AsInteger] )
3319     else
3320     if (qryHeader.FieldByName('RDB$FIELD_TYPE').AsInteger = blr_double) and
3321     (qryHeader.FieldByName('RDB$FIELD_SCALE').AsInteger < 0) then
3322     Result := Result + Format('NUMERIC(15, %d)',
3323     [-qryHeader.FieldByName('RDB$FIELD_SCALE').AsInteger] )
3324     else
3325     Result := Result + ColumnTypes[i].TypeName;
3326     end;
3327     break;
3328     end;
3329     if (qryHeader.FieldByName('RDB$FIELD_TYPE').AsInteger in [blr_text, blr_varying]) then
3330     begin
3331     if not qryHeader.FieldByName('RDB$CHARACTER_LENGTH').IsNull then
3332     Result := Result + Format('(%d)', [qryHeader.FieldByName('RDB$CHARACTER_LENGTH').AsInteger])
3333     else
3334     Result := Result + Format('(%d)', [qryHeader.FieldByName('RDB$FIELD_LENGTH').AsInteger]);
3335     end;
3336    
3337     { Show international character sets and collations }
3338    
3339     if (not qryHeader.FieldByName('RDB$COLLATION_ID').IsNull) or
3340     (not qryHeader.FieldByName('RDB$CHARACTER_SET_ID').IsNull) then
3341     begin
3342     if qryHeader.FieldByName('RDB$COLLATION_ID').IsNull then
3343     CollationId := 0
3344     else
3345     CollationId := qryHeader.FieldByName('RDB$COLLATION_ID').AsInteger;
3346    
3347     if qryHeader.FieldByName('RDB$CHARACTER_SET_ID').IsNull then
3348     CharSetId := 0
3349     else
3350     CharSetId := qryHeader.FieldByName('RDB$CHARACTER_SET_ID').AsInteger;
3351    
3352     Result := Result + GetCharacterSets(CharSetId, CollationId, false);
3353     end;
3354     end;
3355    
3356     begin
3357     FirstTime := true;
3358     qryHeader := TIBSQL.Create(FDatabase);
3359     try
3360     qryHeader.SQL.Text := ProcHeaderSQL;
3361     qryHeader.Params.ByName('procname').AsString := Proc;
3362     qryHeader.Params.ByName('Input').AsInteger := 0;
3363     qryHeader.ExecQuery;
3364     while not qryHeader.Eof do
3365     begin
3366     if FirstTime then
3367     begin
3368     FirstTime := false;
3369     FMetaData.Add('(');
3370     end;
3371    
3372     Line := FormatParamStr;
3373    
3374     qryHeader.Next;
3375     if not qryHeader.Eof then
3376     Line := Line + ',';
3377     FMetaData.Add(Line);
3378     end;
3379    
3380     { If there was at least one param, close parens }
3381     if not FirstTime then
3382     begin
3383     FMetaData.Add( ')');
3384     end;
3385    
3386     FirstTime := true;
3387     qryHeader.Close;
3388     qryHeader.Params.ByName('Input').AsInteger := 1;
3389     qryHeader.ExecQuery;
3390    
3391     while not qryHeader.Eof do
3392     begin
3393     if FirstTime then
3394     begin
3395     FirstTime := false;
3396     FMetaData.Add('RETURNS' + LineEnding + '(');
3397     end;
3398    
3399     Line := FormatParamStr;
3400    
3401     qryHeader.Next;
3402     if not qryHeader.Eof then
3403     Line := Line + ',';
3404     FMetaData.Add(Line);
3405     end;
3406    
3407     { If there was at least one param, close parens }
3408     if not FirstTime then
3409     begin
3410     FMetaData.Add( ')');
3411     end;
3412    
3413     FMetaData.Add('AS');
3414     finally
3415     qryHeader.Free;
3416     end;
3417     end;
3418    
3419     procedure TIBExtract.Notification(AComponent: TComponent;
3420     Operation: TOperation);
3421     begin
3422     inherited;
3423     if (AComponent = FDatabase) and (Operation = opRemove) then
3424     FDatabase := nil;
3425     if (AComponent = FTransaction) and (Operation = opRemove) then
3426     FTransaction := nil;
3427     end;
3428    
3429     function TIBExtract.QuoteIdentifier(Value: String): String;
3430     begin
3431     if AlwaysQuoteIdentifiers then
3432     Result := IBUtils.QuoteIdentifier(FDatabase.DBSQLDialect,Value)
3433     else
3434     Result := IBUtils.QuoteIdentifierIfNeeded(FDatabase.DBSQLDialect,Value)
3435     end;
3436    
3437     procedure TIBExtract.ListData(ObjectName: String);
3438     const
3439     SelectFieldListSQL = 'Select List(RDB$FIELD_NAME) From ( '+
3440     'Select RF.RDB$FIELD_NAME From RDB$RELATION_FIELDS RF '+
3441     'JOIN RDB$FIELDS F On F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE '+
3442     'Where F.RDB$COMPUTED_BLR is NULL and RF.RDB$RELATION_NAME = Upper(:Relation) '+
3443     'Order by RF.RDB$FIELD_POSITION asc)';
3444    
3445     TableSQL =
3446     'SELECT * FROM RDB$RELATIONS ' +
3447     'WHERE ' +
3448     ' (RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL) AND ' +
3449     ' RDB$VIEW_BLR IS NULL ' +
3450     'ORDER BY RDB$RELATION_NAME';
3451    
3452     var FieldList: string;
3453    
3454     begin
3455     if ObjectName = '' then {List all}
3456     begin
3457     with TIBSQL.Create(self) do
3458     try
3459     Database := FDatabase;
3460     SQL.Text := TableSQL;
3461     ExecQuery;
3462     FMetaData.Add('/* Data Starts */');
3463     while not EOF do
3464     begin
3465     ListData(Trim(FieldByName('RDB$RELATION_NAME').AsString));
3466     Next;
3467     end;
3468     FMetaData.Add('/* Data Ends */');
3469     finally
3470     Free;
3471     end;
3472     end
3473     else
3474     begin
3475     FieldList := '*';
3476     with TIBSQL.Create(self) do
3477     try
3478     Database := FDatabase;
3479     SQL.Text := SelectFieldListSQL;
3480     Params[0].AsString := ObjectName;
3481     ExecQuery;
3482     try
3483     if not EOF then
3484     FieldList := Fields[0].AsString;
3485     finally
3486     Close;
3487     end;
3488     finally
3489     Free
3490     end;
3491    
3492     with TIBInsertStmtsOut.Create(self) do
3493     try
3494     Database := FDatabase;
3495     if DataOut(Format('Select %s From %s',[FieldList,QuoteIdentifier( ObjectName)]),
3496     Add2MetaData) then
3497     FMetaData.Add('COMMIT;');
3498     finally
3499     Free
3500     end;
3501     end;
3502     end;
3503    
3504     procedure TIBExtract.ListRoles(ObjectName: String; IncludeGrants: boolean);
3505     const
3506     RolesSQL =
3507     'select * from RDB$ROLES WHERE RDB$SYSTEM_FLAG = 0 ' +
3508     'order by RDB$ROLE_NAME';
3509    
3510     RolesByNameSQL =
3511     'select * from RDB$ROLES ' +
3512     'WHERE RDB$ROLE_NAME = :RoleName ' +
3513     'order by RDB$ROLE_NAME';
3514    
3515     var
3516     qryRoles : TIBSQL;
3517     PrevOwner, RoleName, OwnerName : String;
3518     begin
3519     {Process GRANT roles}
3520     qryRoles := TIBSQL.Create(FDatabase);
3521     try
3522     if FDatabaseInfo.ODSMajorVersion >= ODS_VERSION9 then
3523     begin
3524     PrevOwner := '';
3525     FMetaData.Add('');
3526     FMetaData.Add('/* Grant Roles for this database */');
3527     FMetaData.Add('');
3528    
3529     if ObjectName = '' then
3530     qryRoles.SQL.Text := RolesSQL
3531     else
3532     begin
3533     qryRoles.SQL.Text := RolesByNameSQL;
3534     qryRoles.Params.ByName('RoleName').AsString := ObjectName;
3535     end;
3536     qryRoles.ExecQuery;
3537     try
3538     while not qryRoles.Eof do
3539     begin
3540     RoleName := QuoteIdentifier(
3541     qryRoles.FieldByName('rdb$Role_Name').AsString);
3542     OwnerName := Trim(qryRoles.FieldByName('rdb$Owner_Name').AsString);
3543     if PrevOwner <> OwnerName then
3544     begin
3545     FMetaData.Add('');
3546     FMetaData.Add(Format('/* Role: %s, Owner: %s */', [RoleName, OwnerName]));
3547     FMetaData.Add('');
3548     PrevOwner := OwnerName;
3549     end;
3550     FMetaData.Add('CREATE ROLE ' + RoleName + Term);
3551     if IncludeGrants then
3552     ShowGrantsTo(qryRoles.FieldByName('rdb$Role_Name').AsString,obj_sql_role,Term);
3553     qryRoles.Next;
3554     end;
3555     finally
3556     qryRoles.Close;
3557     end;
3558     end;
3559     finally
3560     qryRoles.Free;
3561     end;
3562     end;
3563    
3564     end.
3565    
3566