--- ibx/trunk/design/IBSystemTables.pas 2011/02/18 16:26:16 6 +++ ibx/trunk/design/IBSystemTables.pas 2012/08/05 18:28:19 7 @@ -1,11 +1,37 @@ -unit IBSystemTables; +(* + * IBX For Lazarus (Firebird Express) + * + * The contents of this file are subject to the Initial Developer's + * Public License Version 1.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy + * of the License here: + * + * http://www.firebirdsql.org/index.php?op=doc&id=idpl + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing rights + * and limitations under the License. + * + * The Initial Developer of the Original Code is Tony Whyman. + * + * The Original Code is (C) 2011 Tony Whyman, MWA Software + * (http://www.mwasoftware.co.uk). + * + * All Rights Reserved. + * + * Contributor(s): ______________________________________. + * +*) + +unit IBSystemTables; {$mode objfpc}{$H+} interface uses - Classes, SysUtils, IBSQL, IBDatabase; + Classes, SysUtils, IBSQL, IBDatabase, StdCtrls; type @@ -19,24 +45,37 @@ type FTestSQL: TIBSQL; FTableAndColumnSQL: TIBSQL; FGetGeneratorsSQL: TIBSQL; + FGetProcedures: TIBSQL; + FGetProcedureParams: TIBSQL; + FGetProcedureInfo: TIBSQL; function GetSQLType(SQLType: TIBSQLTypes): string; - procedure AddWhereClause(TableName: string; QuotedStrings: boolean; SQL: TStrings); + procedure AddWhereClause(TableName: string; QuotedStrings: boolean; SQL: TStrings; + UseOldValues: boolean = false); + procedure GetProcParams(ProcName: string; ParamList: TStrings; InputParams: boolean); overload; + function GetWord(S: string; WordNo: integer): string; public constructor Create; destructor Destroy; override; procedure SelectDatabase(Database: TIBDatabase; Transaction: TIBTransaction); procedure GetTableNames(TableNames: TStrings); procedure GetFieldNames(TableName: string; FieldNames: TStrings; - IncludePrimaryKeys:boolean=true); + IncludePrimaryKeys:boolean=true; IncludeReadOnlyFields: boolean = true); procedure GetPrimaryKeys(TableName: string; PrimaryKeys: TStrings); procedure GetTableAndColumns(SelectSQL: string; var FirstTableName: string; Columns: TStrings); + procedure GetProcedureNames(ProcNames: TStrings; WithOutputParams: boolean=false); + procedure GetProcParams(ProcName: string; var ExecuteOnly: boolean; + InputParams, OutputParams: TStrings); overload; procedure GetGenerators(GeneratorNames: TStrings); - procedure GenerateSelectSQL(TableName: string; QuotedStrings: boolean; SQL: TStrings); - procedure GenerateRefreshSQL(TableName: string; QuotedStrings: boolean; SQL: TStrings); + procedure GenerateSelectSQL(TableName: string; QuotedStrings: boolean; FieldNames,SQL: TStrings); + procedure GenerateRefreshSQL(TableName: string; QuotedStrings: boolean; FieldNames,SQL: TStrings); procedure GenerateInsertSQL(TableName: string; QuotedStrings: boolean; FieldNames, SQL: TStrings); procedure GenerateModifySQL(TableName: string; QuotedStrings: boolean; FieldNames,SQL: TStrings); procedure GenerateDeleteSQL(TableName: string; QuotedStrings: boolean; SQL: TStrings); + procedure GenerateExecuteSQL(ProcName: string; QuotedStrings: boolean; ExecuteOnly: boolean; + InputParams, OutputParams, ExecuteSQL: TStrings); + function GetStatementType(SQL: string; var IsStoredProcedure: boolean): TIBSQLTypes; + function GetFieldNames(FieldList: TListBox): TStrings; procedure TestSQL(SQL: string); end; @@ -51,16 +90,28 @@ const 'Where RDB$SYSTEM_FLAG = 0 ' + 'Order by 1'; - sqlGETFIELDS = 'Select Trim(RDB$FIELD_NAME) as ColumnName FROM RDB$RELATION_FIELDS ' + + sqlGETALLFIELDS = 'Select Trim(RDB$FIELD_NAME) as ColumnName FROM RDB$RELATION_FIELDS ' + 'Where RDB$RELATION_NAME = :TableName ' + 'order by RDB$FIELD_POSITION asc '; + sqlGETFIELDS = 'Select Trim(RF.RDB$FIELD_NAME) as ColumnName FROM RDB$RELATION_FIELDS RF ' + + 'JOIN RDB$FIELDS B On B.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE '+ + 'Where RF.RDB$RELATION_NAME = :TableName and B.RDB$COMPUTED_SOURCE is NULL ' + + 'order by RF.RDB$FIELD_POSITION asc '; + sqlGETPRIMARYKEYS = 'Select Trim(S.RDB$FIELD_NAME) as ColumnName From '+ '(Select RDB$INDEX_NAME,RDB$FIELD_NAME FROM RDB$INDEX_SEGMENTS Order by RDB$FIELD_POSITION ASC) S ' + 'JOIN RDB$RELATION_CONSTRAINTS C On C.RDB$INDEX_NAME = S.RDB$INDEX_NAME ' + 'Where C.RDB$CONSTRAINT_TYPE = ''PRIMARY KEY'' and RDB$RELATION_NAME = :TableName'; - sqlUPDATEFIELDS = 'Select Trim(RDB$FIELD_NAME) as ColumnName FROM RDB$RELATION_FIELDS RF ' + + sqlUPDATEFIELDS = 'Select Trim(RF.RDB$FIELD_NAME) as ColumnName FROM RDB$RELATION_FIELDS RF ' + + 'JOIN RDB$FIELDS B On B.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE '+ + 'Where RF.RDB$RELATION_NAME = :TableName and RF.RDB$FIELD_NAME not in ' + + '(Select RDB$FIELD_NAME FROM RDB$INDEX_SEGMENTS S JOIN RDB$RELATION_CONSTRAINTS C On C.RDB$INDEX_NAME = S.RDB$INDEX_NAME '+ + 'Where C.RDB$CONSTRAINT_TYPE = ''PRIMARY KEY'' and C.RDB$RELATION_NAME = RF.RDB$RELATION_NAME) and B.RDB$COMPUTED_SOURCE is NULL ' + + 'order by 1 asc '; + + sqlALLUPDATEFIELDS = 'Select Trim(RDB$FIELD_NAME) as ColumnName FROM RDB$RELATION_FIELDS RF ' + 'Where RF.RDB$RELATION_NAME = :TableName and RDB$FIELD_NAME not in ' + '(Select RDB$FIELD_NAME FROM RDB$INDEX_SEGMENTS S JOIN RDB$RELATION_CONSTRAINTS C On C.RDB$INDEX_NAME = S.RDB$INDEX_NAME '+ 'Where C.RDB$CONSTRAINT_TYPE = ''PRIMARY KEY'' and C.RDB$RELATION_NAME = RF.RDB$RELATION_NAME)' + @@ -70,6 +121,20 @@ const 'Where RDB$SYSTEM_FLAG = 0 '+ 'Order by 1 asc'; + sqlGETPROCEDURES = 'Select Trim(RDB$PROCEDURE_NAME) as ProcName, RDB$PROCEDURE_INPUTS, '+ + 'RDB$PROCEDURE_OUTPUTS From RDB$PROCEDURES '+ + 'Where RDB$SYSTEM_FLAG = 0 and RDB$PROCEDURE_TYPE <= :ProcType Order by 1 asc'; + + sqlGETPROCPARAM = 'Select Trim(P.RDB$PARAMETER_NAME) as ParamName '+ + 'From RDB$PROCEDURE_PARAMETERS P '+ + 'JOIN RDB$FIELDS F On F.RDB$FIELD_NAME = P.RDB$FIELD_SOURCE '+ + 'Where P.RDB$SYSTEM_FLAG = 0 and P.RDB$PROCEDURE_NAME = :ProcName and P.RDB$PARAMETER_TYPE = :type '+ + 'Order by P.RDB$PARAMETER_NUMBER asc'; + + sqlCheckProcedureNames = 'Select * From RDB$PROCEDURES Where Upper(Trim(RDB$PROCEDURE_NAME)) = Upper(:ProcName)'; + + sqlGETPROCEDUREINFO = 'Select RDB$PROCEDURE_TYPE From RDB$PROCEDURES Where Upper(Trim(RDB$PROCEDURE_NAME)) = Upper(:ProcName)'; + function TIBSystemTables.GetSQLType(SQLType: TIBSQLTypes): string; begin case SQLType of @@ -91,10 +156,11 @@ begin end; procedure TIBSystemTables.AddWhereClause(TableName: string; - QuotedStrings: boolean; SQL: TStrings); + QuotedStrings: boolean; SQL: TStrings; UseOldValues: boolean); var WhereClause: string; Separator: string; Count: integer; + Prefix: string; begin if not assigned(FGetPrimaryKeys.Database) or not FGetPrimaryKeys.Database.Connected or not assigned(FGetPrimaryKeys.Transaction) then @@ -102,6 +168,10 @@ begin Count := 0; WhereClause := 'Where'; Separator := ' A.'; + if UseOldValues then + Prefix := ':OLD_' + else + Prefix := ':'; FGetPrimaryKeys.Prepare; FGetPrimaryKeys.ParamByName('TableName').AsString := TableName; FGetPrimaryKeys.ExecQuery; @@ -110,9 +180,11 @@ begin begin Inc(Count); if QuotedStrings then - WhereClause := WhereClause + Separator + '"' + FGetPrimaryKeys.FieldByName('ColumnName').AsString + '" = :' + FGetPrimaryKeys.FieldByName('ColumnName').AsString + WhereClause := WhereClause + Separator + '"' + FGetPrimaryKeys.FieldByName('ColumnName').AsString + + '" = ' + Prefix+ FGetPrimaryKeys.FieldByName('ColumnName').AsString else - WhereClause := WhereClause + Separator + FGetPrimaryKeys.FieldByName('ColumnName').AsString + ' = :' + FGetPrimaryKeys.FieldByName('ColumnName').AsString; + WhereClause := WhereClause + Separator + FGetPrimaryKeys.FieldByName('ColumnName').AsString + + ' = ' + Prefix + FGetPrimaryKeys.FieldByName('ColumnName').AsString; Separator := ' AND A.'; FGetPrimaryKeys.Next end; @@ -123,14 +195,83 @@ begin SQL.Add(WhereClause) end; +procedure TIBSystemTables.GetProcParams(ProcName: string; ParamList: TStrings; + InputParams: boolean); +begin + if not assigned(FGetProcedureParams.Database) or not FGetProcedureParams.Database.Connected or + not assigned(FGetProcedureParams.Transaction) then + Exit; + ParamList.Clear; + with FGetProcedureParams do + begin + with Transaction do + if not InTransaction then StartTransaction; + Prepare; + ParamByName('ProcName').AsString := ProcName; + if InputParams then + ParamByName('type').AsInteger := 0 + else + ParamByName('type').AsInteger := 1; + ExecQuery; + try + while not EOF do + begin + ParamList.Add(FieldByName('ParamName').AsString); + Next; + end; + finally + Close + end; + end; +end; + +function TIBSystemTables.GetWord(S: string; WordNo: integer): string; +const + SpaceChars = [' ',#$0a,#$0d,#$09,'(']; +var I: integer; + StartIdx: integer; + InWhiteSpace: boolean; +begin + Result := ''; + StartIdx := 1; + InWhiteSpace := true; + for I := 1 to Length(S) do + begin + if InWhiteSpace then + begin + if not (S[I] in SpaceChars) then + begin + StartIdx := I; + InWhiteSpace := false + end + end + else + begin + if S[I] in SpaceChars then + begin + Dec(WordNo); + if WordNo = 0 then + begin + Result := System.copy(S,StartIdx,I - StartIdx); + Exit + end; + InWhiteSpace := true + end + end + end; +end; + constructor TIBSystemTables.Create; begin FGetTableNames := TIBSQL.Create(nil); FGetFieldNames := TIBSQL.Create(nil); FGetPrimaryKeys := TIBSQL.Create(nil); + FGetProcedures := TIBSQL.Create(nil); FTestSQL := TIBSQL.Create(nil); FTableAndColumnSQL := TIBSQL.Create(nil); FGetGeneratorsSQL := TIBSQL.Create(nil); + FGetProcedureParams := TIBSQL.Create(nil); + FGetProcedureInfo := TIBSQL.Create(nil); end; destructor TIBSystemTables.Destroy; @@ -141,6 +282,9 @@ begin if assigned(FGetPrimaryKeys) then FGetPrimaryKeys.Free; if assigned(FTableAndColumnSQL) then FTableAndColumnSQL.Free; if assigned(FGetGeneratorsSQL) then FGetGeneratorsSQL.Free; + if assigned(FGetProcedures) then FGetProcedures.Free; + if assigned(FGetProcedureParams) then FGetProcedureParams.Free; + if assigned(FGetProcedureInfo) then FGetProcedureInfo.Free; inherited Destroy; end; @@ -163,6 +307,15 @@ begin FGetGeneratorsSQL.Database := Database; FGetGeneratorsSQL.Transaction := Transaction; FGetGeneratorsSQL.SQL.Text := sqlGETGENERATORNAMES; + FGetProcedureParams.Database := Database; + FGetProcedureParams.Transaction := Transaction; + FGetProcedureParams.SQL.Text := sqlGETPROCPARAM; + FGetProcedureInfo.Database := Database; + FGetProcedureInfo.Transaction := Transaction; + FGetProcedureInfo.SQL.Text := sqlGETPROCEDUREINFO; + FGetProcedures.Database := Database; + FGetProcedures.Transaction := Transaction; + FGetProcedures.SQL.Text := sqlGETPROCEDURES; end; procedure TIBSystemTables.GetTableNames(TableNames: TStrings); @@ -185,8 +338,9 @@ begin end; end; -procedure TIBSystemTables.GetFieldNames(TableName: string; FieldNames: TStrings; - IncludePrimaryKeys:boolean=true); +procedure TIBSystemTables.GetFieldNames(TableName: string; + FieldNames: TStrings; IncludePrimaryKeys: boolean; + IncludeReadOnlyFields: boolean); begin if not assigned(FGetFieldNames.Database) or not FGetFieldNames.Database.Connected or not assigned(FGetFieldNames.Transaction) then @@ -195,7 +349,15 @@ begin if not InTransaction then StartTransaction; FieldNames.Clear; if IncludePrimaryKeys then + begin + if IncludeReadOnlyFields then + FGetFieldNames.SQL.Text := sqlGETALLFIELDS + else FGetFieldNames.SQL.Text := sqlGETFIELDS + end + else + if IncludeReadOnlyFields then + FGetFieldNames.SQL.Text := sqlALLUPDATEFIELDS else FGetFieldNames.SQL.Text := sqlUPDATEFIELDS; FGetFieldNames.Prepare; @@ -238,23 +400,90 @@ procedure TIBSystemTables.GetTableAndCol var FirstTableName: string; Columns: TStrings); var I: integer; begin + FirstTableName := ''; if not assigned(FTableAndColumnSQL.Database) or not FTableAndColumnSQL.Database.Connected or - not assigned(FTableAndColumnSQL.Transaction) then + not assigned(FTableAndColumnSQL.Transaction) or (Trim(SelectSQL) = '') then Exit; with FTableAndColumnSQL.Transaction do if not InTransaction then StartTransaction; FTableAndColumnSQL.SQL.Text := SelectSQL; try FTableAndColumnSQL.Prepare; - FirstTableName := strpas(FTableAndColumnSQL.Current.Vars[0].Data^.relname); - if assigned(Columns) then - begin - Columns.Clear; - for I := 0 to FTableAndColumnSQL.Current.Count - 1 do - Columns.Add(FTableAndColumnSQL.Current.Vars[I].Name) - end; + case FTableAndColumnSQL.SQLType of + SQLSelect: + begin + if FTableAndColumnSQL.Current.Count > 0 then + FirstTableName := strpas(FTableAndColumnSQL.Current.Vars[0].Data^.relname) + else + FirstTableName := ''; + if assigned(Columns) then + begin + Columns.Clear; + for I := 0 to FTableAndColumnSQL.Current.Count - 1 do + Columns.Add(FTableAndColumnSQL.Current.Vars[I].Name) + end; + end; + { If not a select statement then return table or procedure name + as First Table Name } + SQLUpdate: + FirstTableName := GetWord(SelectSQL,2); + + else + FirstTableName := GetWord(SelectSQL,3); + end except on E:EIBError do - ShowMessage(E.Message); +// ShowMessage(E.Message); + end; +end; + +procedure TIBSystemTables.GetProcedureNames(ProcNames: TStrings; WithOutputParams: boolean); +begin + if not assigned(FGetProcedures.Database) or not FGetProcedures.Database.Connected or + not assigned(FGetProcedures.Transaction) then + Exit; + ProcNames.Clear; + with FGetProcedures do + begin + with Transaction do + if not InTransaction then StartTransaction; + Prepare; + if WithOutputParams then + ParamByName('ProcType').AsInteger := 1 + else + ParamByName('ProcType').AsInteger := 2; + ExecQuery; + try + while not EOF do + begin + ProcNames.Add(FieldByName('ProcName').AsString); + Next; + end; + finally + Close + end; + end; +end; + +procedure TIBSystemTables.GetProcParams(ProcName: string; + var ExecuteOnly: boolean; InputParams, OutputParams: TStrings); +begin + GetProcParams(ProcName,InputParams,true); + GetProcParams(ProcName,OutputParams,false); + ExecuteOnly := OutputParams.Count = 0; + if not ExecuteOnly then + with FGetProcedureInfo do + begin + with Transaction do + if not InTransaction then StartTransaction; + Prepare; + ParamByName('ProcName').AsString := ProcName; + ExecQuery; + try + if not EOF then + ExecuteOnly := FieldByName('RDB$PROCEDURE_TYPE').AsInteger = 2 + finally + Close + end; end; end; @@ -282,39 +511,35 @@ begin end; -procedure TIBSystemTables.GenerateSelectSQL(TableName: string; QuotedStrings: boolean; SQL: TStrings); +procedure TIBSystemTables.GenerateSelectSQL(TableName: string; QuotedStrings: boolean; FieldNames,SQL: TStrings); var SelectSQL: string; Separator : string; + I: integer; begin + SQL.Clear; if not assigned(FGetFieldNames.Database) or not FGetFieldNames.Database.Connected or not assigned(FGetFieldNames.Transaction) then + begin + Messagedlg('No Database Connected',mtError,[mbOK],0); Exit; + end; SelectSQL := 'Select'; Separator := ' A.'; - FGetFieldNames.SQL.Text := sqlGETFIELDS; - FGetFieldNames.Prepare; - FGetFieldNames.ParamByName('TableName').AsString := TableName; - FGetFieldNames.ExecQuery; - try - while not FGetFieldNames.EOF do - begin - if QuotedStrings then - SelectSQL := SelectSQL + Separator + '"' + FGetFieldNames.FieldByName('ColumnName').AsString + '"' - else - SelectSQL := SelectSQL + Separator + FGetFieldNames.FieldByName('ColumnName').AsString; - Separator := ', A.'; - FGetFieldNames.Next - end; - finally - FGetFieldNames.Close + for I := 0 to FieldNames.Count - 1 do + begin + if QuotedStrings then + SelectSQL := SelectSQL + Separator + '"' + FieldNames[I] + '"' + else + SelectSQL := SelectSQL + Separator + FieldNames[I]; + Separator := ', A.'; end; SelectSQL := SelectSQL + ' From ' + TableName + ' A'; SQL.Add(SelectSQL); end; -procedure TIBSystemTables.GenerateRefreshSQL(TableName: string; QuotedStrings: boolean; SQL: TStrings); +procedure TIBSystemTables.GenerateRefreshSQL(TableName: string; QuotedStrings: boolean; FieldNames,SQL: TStrings); begin - GenerateSelectSQL(TableName,QuotedStrings,SQL); + GenerateSelectSQL(TableName,QuotedStrings,FieldNames,SQL); AddWhereClause(TableName,QuotedStrings,SQL) end; @@ -333,7 +558,7 @@ begin InsertSQL := InsertSQL + Separator + '"' + FieldNames[I] + '"' else InsertSQL := InsertSQL + Separator + FieldNames[I] ; - Separator := ','; + Separator := ', '; end; InsertSQL := InsertSQL + ')'; SQL.Add(InsertSQL); @@ -342,7 +567,7 @@ begin for I := 0 to FieldNames.Count - 1 do begin InsertSQL := InsertSQL + Separator + FieldNames[I] ; - Separator := ',:'; + Separator := ', :'; end; InsertSQL := InsertSQL + ')'; SQL.Add(InsertSQL); @@ -366,7 +591,7 @@ begin Separator := ','#$0d#$0a' A.'; end; SQL.Add(UpdateSQL); - AddWhereClause(TableName,QuotedStrings,SQL) + AddWhereClause(TableName,QuotedStrings,SQL,true) end; procedure TIBSystemTables.GenerateDeleteSQL(TableName: string; QuotedStrings: boolean; SQL: TStrings); @@ -376,11 +601,114 @@ begin AddWhereClause(TableName,QuotedStrings,SQL) end; +procedure TIBSystemTables.GenerateExecuteSQL(ProcName: string; + QuotedStrings: boolean; ExecuteOnly: boolean; InputParams, OutputParams, + ExecuteSQL: TStrings); +var SQL: string; + I: integer; + Separator: string; +begin + Separator := ''; + if not ExecuteOnly and (OutputParams.Count > 0) then //Select Query + begin + SQL := 'Select '; + for I := 0 to OutputParams.Count - 1 do + begin + if QuotedStrings then + SQL := SQL + Separator + '"' + OutputParams[I] + '"' + else + SQL := SQL + Separator + OutputParams[I]; + Separator := ', '; + end; + SQL := SQL + ' From ' + ProcName; + if InputParams.Count > 0 then + begin + Separator := '(:'; + for I := 0 to InputParams.Count - 1 do + begin + SQL := SQL + Separator + InputParams[I]; + Separator := ', :'; + end; + SQL := SQL + ')' + end + end + else // Execute Procedure + begin + if QuotedStrings then + SQL := 'Execute Procedure "' + ProcName + '"' + else + SQL := 'Execute Procedure ' + ProcName; + if InputParams.Count > 0 then + begin + Separator := ' :'; + for I := 0 to InputParams.Count - 1 do + begin + if QuotedStrings then + SQL := SQL + Separator + '"' + InputParams[I] + '"' + else + SQL := SQL + Separator + InputParams[I]; + Separator := ', :'; + end; + end + end; + ExecuteSQL.Text := SQL +end; + +function TIBSystemTables.GetStatementType(SQL: string; var IsStoredProcedure: boolean): TIBSQLTypes; +var TableName: string; +begin + Result := sqlUnknown; + if not assigned(FTestSQL.Database) or not FTestSQL.Database.Connected or + not assigned(FTestSQL.Transaction) or (Trim(SQL) = '') then + Exit; + IsStoredProcedure := false; + FTestSQL.SQL.Text := SQL; + try + FTestSQL.Prepare; + Result := FTestSQL.SQLType + except on E:EIBError do +// ShowMessage(E.Message); + end; + if (Result = SQLSelect) and (FTestSQL.Current.Count > 0) then + begin + TableName := strpas(FTestSQL.Current.Vars[0].Data^.relname); + FTestSQL.SQL.Text := sqlCheckProcedureNames; + FTestSQL.Prepare; + FTestSQL.ParamByName('ProcName').AsString := TableName; + FTestSQL.ExecQuery; + try + IsStoredProcedure := not FTestSQL.EOF; + finally + FTestSQL.Close + end; + end; +end; + +function TIBSystemTables.GetFieldNames(FieldList: TListBox): TStrings; +var I: integer; +begin + Result := TStringList.Create; + try + if FieldList.SelCount = 0 then + Result.Assign(FieldList.Items) + else + for I := 0 to FieldList.Items.Count - 1 do + if FieldList.Selected[I] then + Result.Add(FieldList.Items[I]); + except + Result.Free; + raise + end; +end; + procedure TIBSystemTables.TestSQL(SQL: string); begin if not assigned(FTestSQL.Database) or not FTestSQL.Database.Connected or not assigned(FTestSQL.Transaction) then + begin + Messagedlg('No Database Connected',mtError,[mbOK],0); Exit; + end; FTestSQL.SQL.Text := SQL; try FTestSQL.Prepare;