{ This file is part of the Free Pascal run time library. Copyright (c) 2007 by Michael Van Canneyt, member of the Free Pascal development team Firebird/Interbase Data Dictionary Engine Implementation. See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} unit fpddfb; {$mode objfpc}{$H+} interface uses Classes, SysUtils, sqldb, fpdatadict, fpddsqldb, db; Type { TSQLDBFBDDEngine } { TFPDDFBSQLEngine } TFPDDFBSQLEngine = Class(TFPDDSQLEngine) Public Function CreateSequenceSQL(Sequence : TDDSequenceDef) : String; override; end; TSQLDBFBDDEngine = Class(TSQLDBDDEngine) private function ConvertFBFieldType(FDfieldtype, FBsubtype: integer): TFieldType; Protected Function CreateConnection(AConnectString : String) : TSQLConnection; override; Public function ImportFields(Table: TDDTableDef): Integer; override; Function ImportIndexes(Table : TDDTableDef) : Integer; override; Function ImportSequences(Sequences : TDDSequenceDefs; List : TStrings; UpdateExisting : boolean) : Integer; override; Function ImportDomains(Domains : TDDDomainDefs; List : TStrings; UpdateExisting : boolean) : Integer; override; Function CreateSQLEngine : TFPDDSQLEngine; override; Class function EngineCapabilities : TFPDDEngineCapabilities; override; Class function Description : string; override; Class function DBType : String; override; end; // Backwards compatibility TSQLDBIBDDEngine = TSQLDBFBDDEngine; Procedure RegisterFBDDEngine; Procedure UnRegisterFBDDEngine; implementation uses ibconnection; Procedure RegisterFBDDEngine; begin RegisterDictionaryEngine(TSQLDBFBDDEngine); end; Procedure UnRegisterFBDDEngine; begin UnRegisterDictionaryEngine(TSQLDBFBDDEngine); end; { TSQLDBFBDDEngine } function TSQLDBFBDDEngine.CreateConnection(AConnectString: String ): TSQLConnection; begin Result:=TIBConnection.Create(Self); end; class function TSQLDBFBDDEngine.EngineCapabilities: TFPDDEngineCapabilities; begin Result:=[ecImport,ecCreateTable,ecViewTable, ecTableIndexes, ecRunQuery, ecRowsAffected, ecSequences, ecDomains]; end; class function TSQLDBFBDDEngine.Description: string; begin Result:='Firebird/Interbase connection using SQLDB'; end; class function TSQLDBFBDDEngine.DBType: String; begin Result:='Firebird/Interbase'; end; function TSQLDBFBDDEngine.ConvertFBFieldType (FDfieldtype, FBsubtype : integer) : TFieldType; var b : byte; begin if FDFieldType > 255 then begin if FDFieldType = 261 then result := ftBlob {BLOB} else result := ftUnknown; end else begin b := byte(FDFieldType and $FF); if (b in [7,8,16]) and (FBsubtype <> 0) then // BCD types: 1= Numeric, 2 := Decimal result := ftBCD else case b of 14 : result := ftFixedChar; {CHAR} 37 : result := ftString; {VARCHAR} 40 : result := ftString; {CSTRING ?} 11 : result := ftFloat; {D-FLOAT ?} 27 : result := ftFloat; {DOUBLE} 10 : result := ftFloat; {FLOAT} 16 : result := ftLargeint; {INT64} 8 : result := ftInteger; {INTEGER} 9 : result := ftlargeint; {QUAD ?} 7 : result := ftSmallint; {SMALLINT} 12 : result := ftDate; {DATE dialect 3} 13 : result := ftTime; {TIME} 35 : result := ftDateTime; {TIMESTAMP dialect 3, DATE in dialect 1,2} else result := ftUnknown; end; end; end; function TSQLDBFBDDEngine.ImportIndexes(Table: TDDTableDef): Integer; const SQLindexes = 'SELECT '+ 'I.RDB$INDEX_NAME as IndexName, '+ 'I.RDB$INDEX_TYPE as IndexType, '+ 'I.RDB$UNIQUE_FLAG as IsUnique, '+ 'R.RDB$CONSTRAINT_TYPE as ConstraintType, '+ 'R.RDB$CONSTRAINT_NAME as ConstraintName '+ 'FROM '+ 'RDB$INDICES I '+ 'LEFT JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME = R.RDB$INDEX_NAME '+ 'WHERE '+ 'I.RDB$RELATION_NAME=''%s'' '+ 'AND I.RDB$FOREIGN_KEY is null '+ 'ORDER BY I.RDB$INDEX_NAME'; { SQLchecks = 'SELECT '+ 'R.RDB$CONSTRAINT_NAME as ConstraintName, '+ 'R.RDB$CONSTRAINT_TYPE as ConstraintType, '+ 'T.RDB$TRIGGER_SOURCE as CheckSource, '+ 'FROM '+ 'RDB$RELATION_CONSTRAINTS R '+ 'LEFT JOIN RDB$CHECK_CONSTRAINTS C ON R.RDB$CONSTRAINT_NAME = C.RDB$CONSTRAINT_NAME '+ 'LEFT JOIN RDB$TRIGGERS T ON T.RDB$TRIGGER_NAME = C.RDB$TRIGGER_NAME '+ 'WHERE '+ 'R.RDB$RELATION_NAME=''%s'' '+ 'ORDER BY R.RDB$CONSTRAINT_NAME'; SQLforeign = 'SELECT '+ 'R.RDB$CONSTRAINT_NAME as ConstraintName, '+ 'R.RDB$INDEX_NAME as IndexName, '+ 'E.RDB$CONST_NAME_UQ as RefUnique, '+ 'E.RDB$UPDATE_RULE as OnUpdate, '+ 'E.RDB$DELETE_RULE as OnDelete, '+ 'I.RDB$INDEX_TYPE as IndexType '+ 'FROM '+ 'RDB$RELATION_CONSTRAINTS R '+ 'LEFT JOIN RDB$REF_CONSTRAINTS E ON E.RDB$CONSTRAINT_NAME = R.RDB$CONSTRAINT_NAME '+ 'LEFT JOIN RDB$INDICES I ON I.RDB$INDEX_NAME = R.RDB$INDEX_NAME '+ 'WHERE '+ 'R.RDB$RELATION_NAME=''%s'' '+ 'ORDER BY R.RDB$CONSTRAINT_NAME'; } SQLFields = 'SELECT RDB$FIELD_NAME as IndexField '+ 'FROM RDB$INDEX_SEGMENTS '+ 'WHERE RDB$INDEX_NAME = :IndexName '+ 'ORDER BY RDB$FIELD_POSITION'; Var Q, QF : TSQLQuery; PIndexName : TParam; FConstraintName, FConstraintType, FIndexType, FIndexName, FUnique : TField; //FCheckSource, FRefUnique, //FOnUpdate, FOnDelete : TField; Index : TDDIndexDef; procedure BindIndexFields; begin PIndexName := QF.params.parambyname ('IndexName'); FConstraintName := Q.Fieldbyname('ConstraintName'); FConstraintType := Q.Fieldbyname('ConstraintType'); FIndexType := Q.Fieldbyname('IndexType'); FIndexName := Q.Fieldbyname('IndexName'); FUnique := Q.Fieldbyname('IsUnique'); end; { procedure BindCheckFields; begin FCheckSource := Q.Fieldbyname('CheckSource'); end; procedure BindForeignFields; begin FRefUnique := Q.Fieldbyname('RefUnique'); FOnUpdate := Q.Fieldbyname('OnUpdate'); FOnDelete := Q.Fieldbyname('OnDelete'); end; } function CreateIndex (AName, indexname: string) : TDDIndexDef; var n, s : string; begin n := trim(AName); if n = '' then n := trim(indexname); if trim (indexName) = '' then indexname := AName; result := Table.Indexes.AddIndex(n); PIndexName.asstring := indexname; QF.Open; try s := trim(QF.Fields[0].asstring); QF.Next; while not QF.eof do begin s := s + ';' + trim(QF.Fields[0].asstring); QF.Next; end; finally QF.Close; end; result.Fields := s; end; function ImportIndices : integer; begin result := 0; Q.SQL.text := format (SQLindexes, [Table.TableName]); Q.Open; try result := 0; Q.First; BindIndexFields; while not Q.eof do begin with CreateIndex (FConstraintName.asstring, FIndexName.asstring) do begin inc (result); if trim(FConstraintType.asstring) = 'PRIMARY KEY' then options := options + [ixPrimary] else if FUnique.asinteger = 1 then options := options + [ixUnique]; if FIndextype.asinteger = 1 then options := options + [ixDescending]; end; Q.Next; end; finally Q.Close; end; end; begin Q:=CreateSQLQuery(Nil); try QF:=CreateSQLQuery(Nil); try QF.SQl.Text := SQLFields; QF.Prepare; try ImportIndices; //ImportChecks; //ImportForeignKeys; finally QF.Unprepare; end; finally QF.Free; end; finally Q.Free; end; end; function TSQLDBFBDDEngine.ImportSequences(Sequences: TDDSequenceDefs; List: TStrings; UpdateExisting: boolean): Integer; const SQL = 'SELECT RDB$GENERATOR_Name FROM RDB$Generators WHERE RDB$System_Flag = 0'; Var Q : TSQLQuery; Seq : TDDSequenceDef; n : string; begin result := 0; Q:=CreateSQLQuery(Nil); try Q.Sql.Text := SQL; Q.Open; try while not Q.eof do begin n := trim(Q.Fields[0].asstring); seq := Sequences.FindSequence(n); if not assigned (Seq) then Seq := Sequences.AddSequence(n) else if not UpdateExisting then Seq := nil; if assigned (Seq) then begin Seq.Increment := 0; Seq.StartValue := 0; inc (result); end; Q.Next; end; finally Q.CLose; end; finally Q.Free; end; end; function TSQLDBFBDDEngine.ImportDomains(Domains: TDDDomainDefs; List: TStrings; UpdateExisting: boolean): Integer; const SQL = 'SELECT ' + ' RDB$FIELD_NAME as Name,' + ' RDB$DEFAULT_SOURCE as DomainDefault,' + ' RDB$FIELD_LENGTH as CharLength,' + ' RDB$FIELD_PRECISION as FieldPrecision,' + ' RDB$FIELD_SCALE as Scale,' + ' RDB$FIELD_TYPE as FieldType,' + ' RDB$FIELD_SUB_TYPE as Subtype,' + ' RDB$NULL_FLAG as DomainNull' + ' FROM '+ ' RDB$FIELDS'+ ' WHERE RDB$System_Flag = 0 and not (RDB$Field_Name like ''RDB$%'')'; Var Q : TSQLQuery; FName, FDomainName, FDomainDefault, FCharLength, FPrecision, FScale, FFieldType, FSubType, FDomainnull : TField; procedure BindFields; begin FName := q.fieldbyname('Name'); FDomainDefault := q.fieldbyname('DomainDefault'); FCharLength := q.fieldbyname('CharLength'); FPrecision := q.fieldbyname('FieldPrecision'); FScale := q.fieldbyname('Scale'); FFieldType := q.fieldbyname('FieldType'); FSubType := q.fieldbyname('SubType'); FDomainnull := q.fieldbyname('Domainnull'); end; function ImportDomain : boolean; var Dom : TDDDomainDef; n : string; begin n := trim(FName.asstring); Dom := Domains.FindDomain(n); if not assigned (Dom) then Dom := Domains.AddDomain(n) else if not UpdateExisting then Dom := nil; if assigned (Dom) then begin result := true; Dom.FieldType := ConvertFBFieldType (FFieldType.asinteger, FSubType.asinteger); Dom.Precision := FPrecision.asinteger; if FScale.asinteger < 0 then Dom.Size := -FScale.asinteger else if Dom.Fieldtype in [ftString, ftFixedChar] then Dom.Size := FCharLength.asinteger else Dom.Size := 0; //Dom.DefaultExpression := copy(trim(FDomainDefault.asstring), 9, maxint); Dom.Required := FDomainnull.asinteger = 1; end else result := false; end; begin result := 0; Q:=CreateSQLQuery(Nil); try Q.Sql.Text := SQL; Q.Open; BindFields; try while not Q.eof do begin if ImportDomain then inc (result); Q.Next; end; finally Q.CLose; end; finally Q.Free; end; end; function TSQLDBFBDDEngine.ImportFields(Table: TDDTableDef): Integer; Const SQL = 'SELECT ' + ' F.RDB$FIELD_POSITION as FieldPosition,' + ' F.RDB$FIELD_NAME as Name,' + ' F.RDB$NULL_FLAG as FieldNull,' + ' F.RDB$Description as Description,' + ' F.RDB$DEFAULT_SOURCE as FieldDefault,' + ' D.RDB$DEFAULT_SOURCE as DomainDefault,' + ' D.RDB$FIELD_LENGTH as CharLength,' + ' D.RDB$FIELD_PRECISION as FieldPrecision,' + ' D.RDB$FIELD_SCALE as Scale,' + ' D.RDB$FIELD_TYPE as FieldType,' + ' D.RDB$FIELD_SUB_TYPE as Subtype,' + ' D.RDB$NULL_FLAG as DomainNull,' + ' D.RDB$FIELD_NAME as DName ' + ' FROM '+ ' RDB$RELATION_FIELDS F left join RDB$FIELDS D on F.RDB$FIELD_Source = D.RDB$FIELD_NAME'+ ' WHERE (RDB$RELATION_NAME = ''%s'')' + ' ORDER BY RDB$FIELD_POSITION'; Var Q : TSQLQuery; FName, FPosition, FFieldnull, FDescription, FFieldDefault, FDomainDefault, FDomainnull, FDomainName, FCharLength, FPrecision, FScale, FFieldType, FSubType : TField; procedure BindFields; begin FName := q.fieldbyname('Name'); FPosition := q.fieldbyname('FieldPosition'); FFieldnull := q.fieldbyname('FieldNull'); FDescription := q.fieldbyname('Description'); FFieldDefault := q.fieldbyname('FieldDefault'); FDomainDefault := q.fieldbyname('DomainDefault'); FCharLength := q.fieldbyname('CharLength'); FPrecision := q.fieldbyname('FieldPrecision'); FScale := q.fieldbyname('Scale'); FFieldType := q.fieldbyname('FieldType'); FSubType := q.fieldbyname('SubType'); FDomainnull := q.fieldbyname('Domainnull'); FDomainName := q.fieldbyname('DName'); end; {Opmerking: bestaande fielddefs die niet meer in de tabel zitten worden niet verwijderd !? } function ImportFieldDef(APosition : Integer) : boolean; var FD : TDDFieldDef; n, s : string; begin n := trim(FName.asstring); FD := Table.Fields.FindField(n); if not assigned (FD) then FD := Table.AddField(n); FD.FieldName := n; FD.FieldType := ConvertFBFieldType (FFieldType.asinteger, FSubType.asinteger); FD.Precision := FPrecision.asinteger; if FScale.asinteger < 0 then FD.Size := -FScale.asinteger else if FD.Fieldtype in [ftString, ftFixedChar] then FD.Size := FCharLength.asinteger else FD.Size := 0; { // Fixed length types don't have a size in the dictionary case byte(FFieldType.asinteger and $FF) of 7 : FD.Size := 2; 10,8 : FD.Size := 4; 35,11,27,9,16,12 : FD.Size := 8; end; } if not fDescription.IsNull then FD.Hint := FDescription.asstring; s := trim(FFieldDefault.asstring); n := trim(FDomainDefault.asstring); if s <> '' then FD.DefaultExpression := copy(s, 9, maxint) else if n <> '' then FD.DefaultExpression := copy(n, 9, maxint); if FDomainnull.asinteger = 0 then if FFieldnull.asinteger = 1 then FD.Required:=true else FD.Required:=false else FD.Required:=false; FD.index := APosition; s := trim(FDomainName.asstring); if copy(s, 1, 4) <> 'RDB$' then FD.DomainName := s else FD.DomainName := ''; result := true; end; function ImportFromSQLDef : integer; begin result := 0; Q.First; BindFields; while not Q.eof do begin if ImportFieldDef(Result) then inc (result); Q.Next; end; end; begin Q:=CreateSQLQuery(Nil); try Q.Sql.Text:=Format(SQL,[Table.TableName]); Q.Open; try result := ImportFromSQLDef; finally Q.CLose; end; finally Q.Free; end; end; function TSQLDBFBDDEngine.CreateSQLEngine: TFPDDSQLEngine; begin Result:=TFPDDFBSQLEngine.Create; end; { TFPDDFBSQLEngine } function TFPDDFBSQLEngine.CreateSequenceSQL(Sequence: TDDSequenceDef): String; begin Result:='CREATE GENERATOR '+Sequence.SequenceName; end; end.