| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 | unit ScAutoUpdateUnit;interfaceuses  DB, ADODB, ScFileArchiver, ScTablesUnit, Classes;const  MaxFieldCount = 512;  PrimaryKey = 'PrimaryKey';  type  PFieldDefs = ^TFieldDefs;  TFieldDefs = array [0..MaxFieldCount - 1] of TScFieldDef;  PTableDef = ^TTableDef;  // 表定义结构  TTableDef = record    // 表名    TableName: string;    // 字段数    FieldCount: Integer;    // 字段结构数组    FieldDefs: PFieldDefs;    // 是否需要重新创建    Recreate: Boolean;    // 重新创建主键    RecreatePrimaryKey: Boolean;  end;  TSQLType = (stAlter, stCreate, stReCreate);  TUpdateEventType = (uetAddFields, uetKeys, uetAfterUpdate);  TUpdateEvent = procedure (ATableName: string; AEventType: TUpdateEventType;    ASQLType: TSQLType; AConnection: TADOConnection);  TScUpdater = class(TObject)  private    FTableDefList: TList;    FFileName: string;    FConnection: TADOConnection;    FFileVer: string;    FQuery: TADOQuery;    FForceUpdate: Boolean;    FForceCheck: Boolean;    FCurFileVersion: string;    FOnUpdateData: TUpdateEvent;    function GetCurFileVersion: string;    // 返回True:表存在,返回False: 表不存在    function CheckTable(ATableName: string): Boolean;    procedure GenerateSQL(ATableDef: PTableDef; ASQLType: TSQLType; ASQLList: TStrings);    procedure InternalExcuteSQL(ASQL: string; AHideException: Boolean = False; AOpen: Boolean = False);    function ExcuteUpdateSQL(ASQLList: TStrings): Boolean;    procedure SetForceUpdate(const Value: Boolean);    procedure SetForceCheck(const Value: Boolean);    procedure SetCurFileVersion(const Value: string);    // 字符串是否是事件    // 事件字符串格式:"事件名 表名 操作类型"    function CheckEvent(AText: string): Boolean;  public    constructor Create;    destructor Destroy; override;    // 打开    procedure Open(AFileName: string; AConnection: TADOConnection;        AFileVer: string);    // 关闭    procedure Close;    // 执行升级操作    function ExcuteUpdate: Boolean;    // 执行其他SQL语句(如建立、删除索引)    procedure ExcuteSQL(ASQL: string);    // 添加表定义    function AddTableDef(ATableName: string; AFieldDefs: PFieldDefs; AFieldCount: Integer;        AReCreate, ARecreatePK: Boolean): Integer;    // 文件是否需要升级(根据版本号判断)    function FileNeedUpdate: Boolean;    // 强制进行表和字段的升级检查    property ForceCheck: Boolean read FForceCheck write SetForceCheck;    // 强制升级所有表和字段    property ForceUpdate: Boolean read FForceUpdate write SetForceUpdate;    // 最新文件版本    property CurrentFileVer: string read GetCurFileVersion write SetCurFileVersion;    // 事件    // 重建关键字之前    property OnUpdateData: TUpdateEvent read FOnUpdateData      write FOnUpdateData;  end;const  SQLTypeStrs: array [TSQLType] of string = ('Modify', 'Create', 'ReCreate');  SOnUpdateData = 'OnUpdateData';implementationuses  ConstMethodUnit, SysUtils;function StrToSQLType(ASQLType: string): TSQLType;var  I: TSQLType;begin  Result := stAlter;  for I := Low(SQLTypeStrs) to High(SQLTypeStrs) do  begin    if SameText(ASQLType, SQLTypeStrs[I]) then    begin      Result := I;      Break;    end;  end;end;{ TScUpdater }function TScUpdater.AddTableDef(ATableName: string; AFieldDefs: PFieldDefs;  AFieldCount: Integer; AReCreate, ARecreatePK: Boolean): Integer;var  pRec: PTableDef;begin  New(pRec);  pRec^.TableName := ATableName;  pRec^.FieldCount := AFieldCount;  pRec^.FieldDefs := AFieldDefs;  pRec^.Recreate := AReCreate;  pRec^.RecreatePrimaryKey := ARecreatePK;  Result := FTableDefList.Add(pRec);end;function TScUpdater.CheckEvent(AText: string): Boolean;var  strText, strEvent, strTableName, strEventType, strSQLType: string;  SQLType: TSQLType;  iPos: Integer;begin  Result := False;  strText := AText;  // 检查事件名称  iPos := Pos(' ', strText);  if iPos > 0 then  begin    strEvent := Copy(strText, 1, iPos - 1);    Delete(strText, 1, iPos);    if SameText(strEvent, SOnUpdateData) then    begin      // 事件类型      iPos := Pos(' ', strText);      strEventType := Copy(strText, 1, iPos - 1);      Delete(strText, 1, iPos);      // 表名      iPos := Pos(' ', strText);      strTableName := Copy(strText, 1, iPos - 1);      Delete(strText, 1, iPos);      // 操作类型      strSQLType := strText;      SQLType := StrToSQLType(strSQLType);      Result := True;      if Assigned(FOnUpdateData) then        FOnUpdateData(strTableName, TUpdateEventType(StrToInt(strEventType)), SQLType, FConnection);    end;  end;end;function TScUpdater.CheckTable(ATableName: string): Boolean;var  I: Integer;  Names: TStringList;begin  Names := TStringList.Create;  try    FConnection.GetTableNames(Names);    if Names.IndexOf(ATableName) < 0 then      Result := False    else      Result := True;  finally    Names.Free;  end;end;procedure TScUpdater.Close;begin  FQuery.Close;end;constructor TScUpdater.Create;begin  FForceUpdate := False;  FForceCheck := False;  FTableDefList := TList.Create;  FQuery := TADOQuery.Create(nil);end;destructor TScUpdater.Destroy;begin  Close;  FQuery.Free;  ClearPointerList(FTableDefList);  FTableDefList.Free;  inherited;end;procedure TScUpdater.ExcuteSQL(ASQL: string);begin  InternalExcuteSQL(ASQL);end;function TScUpdater.ExcuteUpdate: Boolean;var  I: Integer;  pRec: PTableDef;  SQLs: TStringList;  SQLType: TSQLType;  bHasError: Boolean;  sError: string;begin  Result := False;  bHasError := False;  sError := '';  if FileNeedUpdate then  begin    SQLs := TStringList.Create;    try      for I := 0 to FTableDefList.Count - 1 do      begin        pRec := PTableDef(FTableDefList[I]);        if CheckTable(pRec^.TableName) then        begin          if pRec^.Recreate then            SQLType := stReCreate          else            SQLType := stAlter;        end        else          SQLType := stCreate;        GenerateSQL(pRec, SQLType, SQLs);        if SQLs.Count > 0 then          if not ExcuteUpdateSQL(SQLs) then          begin            bHasError := True;            sError := sError + #13#10 + Format('Update operation [%s] on table [%s] can not excute!', [SQLTypeStrs[SQLType], pRec^.TableName]);          end;      end;    finally      SQLs.Free;    end;    if bHasError then      MessageWarning(0, '升级文件时发生错误,无法完成升级。'#13#10'错误信息:' + sError)    else      Result := True;  end;end;function TScUpdater.ExcuteUpdateSQL(ASQLList: TStrings): Boolean;var  I: Integer;  HideExcption: Boolean;begin  Result := False;  try    for I := 0 to ASQLList.Count - 1 do    begin      if not CheckEvent(ASQLList[I]) then      begin        HideExcption := ASQLList.Objects[I] <> nil;        if HideExcption then          HideExcption := Boolean(Integer(ASQLList.Objects[I]));        InternalExcuteSQL(ASQLList[I], HideExcption);      end;    end;    Result := True;  except  end;end;function TScUpdater.FileNeedUpdate: Boolean;begin  Result := (ScCompareFileVer(FFileVer, GetCurFileVersion) <> 0) or FForceCheck;end;function SameFieldType(AFieldType: TFieldType; AScFieldType: TScMDBFieldType): Boolean;begin  Result := False;  case AScFieldType of    ftString:      Result := (AFieldType = DB.ftWideString) or (AFieldType = DB.ftString);    ftByte:      Result := AFieldType = DB.ftWord;    ftSmallint:      Result := AFieldType = DB.ftSmallint;    ftInteger:      Result := AFieldType = DB.ftInteger;    ftBoolean:      Result := AFieldType = DB.ftBoolean;    ftSingle:      Result := AFieldType = DB.ftFloat;    ftDouble:      Result := AFieldType = DB.ftFloat;    ftCurrency:      Result := (AFieldType = DB.ftCurrency) or (AFieldType = DB.ftBCD);    ftDateTime:      Result := AFieldType = DB.ftDateTime;    ftMemo:      Result := AFieldType = DB.ftMemo;    ftOLEObject:      Result := AFieldType = DB.ftBlob;  end;end;procedure TScUpdater.GenerateSQL(ATableDef: PTableDef; ASQLType: TSQLType; ASQLList: TStrings);  function GenerateCreateSQL: string;  var    I: Integer;    Def: TScFieldDef;    strField, strFields, strKeyFields: string;  begin    Result := '';    if ATableDef^.FieldCount > 0 then    begin      // CREATE TABLE table1      Result := Format('CREATE TABLE %s ', [ATableDef^.TableName]);      strFields := '';      strKeyFields := '';      for I := 0 to ATableDef^.FieldCount - 1 do      begin        Def := ATableDef^.FieldDefs[I];        // field1 type        strField := Def.FieldName + ' ' + ScMDBFieldTypeName[Def.FieldType];        if Def.FieldType in [ftString] then          // field1 type (size)          strField := strField + ' ' + Format('(%d)', [Def.Size]);        if Def.NotNull then          // field1 type (size) NOT NULL          strField := strField + ' ' + 'NOT NULL';        if Def.PrimaryKey then          strKeyFields := strKeyFields + Def.FieldName + ', ';        strFields := strFields + strField + ', ';      end;      if strKeyFields <> '' then      begin        Delete(strKeyFields, Length(strKeyFields) - 1, 2);        // CONSTRAINT PrimaryKey PRIMARY KEY (field1, field2...)        strKeyFields := Format('CONSTRAINT %s PRIMARY KEY (%s)', [PrimaryKey, strKeyFields]);      end      else        Delete(strFields, Length(strFields) - 1, 2);      // (field1 type (size) NOT NULL, field2 type (size) NOT NULL..., CONSTRAINT PrimaryKey PRIMARY KEY (field1, field2...))      strFields := Format('(%s)', [strFields + strKeyFields]);      // CREATE TABLE table1 (field1 type (size) NOT NULL, field2 type (size) NOT NULL..., CONSTRAINT PrimaryKey PRIMARY KEY (field1, field2...))      Result := Result + strFields;    end;  end;type  TAlterType = (atAddField, atAlterField, atDropField, atAddIndex, atDropIndex);  function GenerateSingleFieldAlterSQL(ATableName: string; AFieldDef: TScFieldDef;    AOp: TAlterType; var ANeedDefault: Boolean): string;  begin    Result := '';    ANeedDefault := False;    case AOp of      atAddField:      begin        Result := Format('ALTER TABLE %s ADD COLUMN %s %s',            [ATableName, AFieldDef.FieldName, ScMDBFieldTypeName[AFieldDef.FieldType]]);        if AFieldDef.FieldType in [ftString] then          Result := Result + Format(' (%d)', [AFieldDef.Size]);        if AFieldDef.NotNull then        begin          Result := Result + ' NOT NULL';          case AFieldDef.FieldType of            ftByte, ftSmallint, ftInteger, ftBoolean, ftSingle, ftDouble,                 ftCurrency, ftDateTime:            ANeedDefault := True;          end;        end;      end;      atDropField:      begin        Result := Format('ALTER TABLE %s DROP COLUMN %s', [ATableName, AFieldDef.FieldName]);      end;      atAlterField:      begin        Result := Format('ALTER TABLE %s ALTER COLUMN %s %s',            [ATableName, AFieldDef.FieldName, ScMDBFieldTypeName[AFieldDef.FieldType]]);        if AFieldDef.FieldType in [ftString] then          Result := Result + Format(' (%d)', [AFieldDef.Size]);        if AFieldDef.NotNull then        begin          Result := Result + ' NOT NULL';          case AFieldDef.FieldType of            ftByte, ftSmallint, ftInteger, ftBoolean, ftSingle, ftDouble,                 ftCurrency, ftDateTime:            ANeedDefault := True;          end;        end;      end;    end;  end;  function GenerateDefaultValueSQL(ATableName: string; AFieldDef: TScFieldDef): string;  begin    Result := '';    case AFieldDef.FieldType of      ftByte, ftSmallint, ftInteger:        Result := Format('UPDATE  %s SET %s = %d',            [ATableName, AFieldDef.FieldName, 0]);      ftSingle, ftDouble, ftCurrency:        Result := Format('UPDATE  %s SET %s = %f',            [ATableName, AFieldDef.FieldName, 0.0]);      ftBoolean:        Result := Format('UPDATE  %s SET %s = %s',            [ATableName, AFieldDef.FieldName, 'FALSE']);      ftDateTime:        Result := Format('UPDATE  %s SET %s = ''%s''',            [ATableName, AFieldDef.FieldName, '2000-1-1 12:00:00']);    end;  end;  function GenerateSingleKeyAlterSQL(ATableName, AIndexName, AFieldNames: string; AOp: TAlterType): string;  begin    Result := '';    case AOp of      atAddIndex:      begin        Result := Format('ALTER TABLE %s ADD CONSTRAINT %s Primary Key (%s)', [ATableName, AIndexName, AFieldNames]);      end;      atDropIndex:      begin        Result := Format('ALTER TABLE %s DROP CONSTRAINT %s', [ATableName, AIndexName]);      end;    end;  end;  procedure GenerateAlterSQL;  var    I, J: Integer;    Field: TField;    pDef: PScFieldDef;    AddList, ModifyList: TList;    KeyFields: string;    bNeedDefaultValue: Boolean;  begin    InternalExcuteSQL(Format('SELECT * FROM %s WHERE 0=1', [ATableDef^.TableName]), False, True);    AddList := TList.Create;    ModifyList := TList.Create;    try      KeyFields := '';      for I := 0 to ATableDef^.FieldCount - 1 do      begin        pDef := @ATableDef^.FieldDefs^[I];        if (KeyFields <> '') and pDef^.PrimaryKey then          KeyFields := KeyFields + ', ';        if pDef^.PrimaryKey then          KeyFields := KeyFields + pDef^.FieldName;        AddList.Add(pDef);      end;{      if KeyFields <> '' then        Delete(KeyFields, Length(KeyFields) - 1, 2);}      for I := 0 to FQuery.Fields.Count - 1 do      begin        Field := FQuery.Fields[I];        for J := 0 to AddList.Count - 1 do        begin          pDef := PScFieldDef(AddList[J]);          if SameText(Field.FieldName, pDef^.FieldName) then          begin            if FForceUpdate then              ModifyList.Add(pDef)            else            begin              if not SameFieldType(Field.DataType, pDef^.FieldType) then                ModifyList.Add(pDef)              else if (Field.DataType in [ftWideString]) and (Field.Size <> pDef^.Size) then                ModifyList.Add(pDef);            end;            AddList.Remove(pDef);            Break;          end;        end;      end;      for I := 0 to ModifyList.Count - 1 do      begin        pDef := PScFieldDef(ModifyList[I]);        ASQLList.Add(GenerateSingleFieldAlterSQL(ATableDef^.TableName, pDef^, atAlterField, bNeedDefaultValue));        if bNeedDefaultValue then          ASQLList.Add(GenerateDefaultValueSQL(ATableDef^.TableName, pDef^));      end;      for I := 0 to AddList.Count - 1 do      begin        pDef := PScFieldDef(AddList[I]);        ASQLList.Add(GenerateSingleFieldAlterSQL(ATableDef^.TableName, pDef^, atAddField, bNeedDefaultValue));        if bNeedDefaultValue then          ASQLList.Add(GenerateDefaultValueSQL(ATableDef^.TableName, pDef^));      end;      if AddList.Count > 0 then        // 添加事件        ASQLList.Add(Format('%s %d %s %s', [SOnUpdateData, Ord(uetAddFields), ATableDef.TableName, SQLTypeStrs[ASQLType]]));      if ATableDef.RecreatePrimaryKey then      begin        ASQLList.AddObject(GenerateSingleKeyAlterSQL(ATableDef^.TableName, PrimaryKey, KeyFields, atDropIndex), TObject(Integer(True)));        // 添加事件        ASQLList.Add(Format('%s %d %s %s', [SOnUpdateData, Ord(uetKeys), ATableDef.TableName, SQLTypeStrs[ASQLType]]));        ASQLList.Add(GenerateSingleKeyAlterSQL(ATableDef^.TableName, PrimaryKey, KeyFields, atAddIndex));      end;    finally      AddList.Free;      ModifyList.Free;    end;  end;begin  ASQLList.Clear;  case ASQLType of    stAlter:      GenerateAlterSQL;    stCreate:      ASQLList.Add(GenerateCreateSQL);    stReCreate:    begin      ASQLList.Add(Format('DROP TABLE %s', [ATableDef^.TableName]));      ASQLList.Add(GenerateCreateSQL);    end;  end;  if ASQLList.Count > 0 then   ASQLList.Add(Format('%s %d %s %s', [SOnUpdateData, Ord(uetAfterUpdate), ATableDef^.TableName, SQLTypeStrs[ASQLType]]));end;function TScUpdater.GetCurFileVersion: string;begin  if FCurFileVersion <> '' then    Result := FCurFileVersion  else  begin    Result := ConstBillsFileVersion;    {$IFDEF _ScBills}    Result := ConstBillsFileVersion;    {$ENDIF}    {$IFDEF _ScBudget}      {$IFDEF _ScEstimate}      Result := ConstEstimateFileVersion;      {$ELSE}      Result := ConstBudgetFileVersion;      {$ENDIF}    {$ENDIF}    {$IFDEF _ScRation}    Result := ConstRationLibFileVersion;    {$ENDIF}  end;end;procedure TScUpdater.InternalExcuteSQL(ASQL: string; AHideException, AOpen: Boolean);begin  FQuery.Close;  FQuery.SQL.Clear;  FQuery.SQL.Add(ASQL);  try    if AOpen then      FQuery.Open    else      FQuery.ExecSQL;  except    if not AHideException then      raise;  end;end;procedure TScUpdater.Open(AFileName: string; AConnection: TADOConnection;  AFileVer: string);begin  FFileName := AFileName;  FConnection := AConnection;  FFileVer := AFileVer;  if AFileVer = '' then    FFileVer := '0.0.0.0';  FQuery.Connection := AConnection;  ClearPointerList(FTableDefList);end;procedure TScUpdater.SetCurFileVersion(const Value: string);begin  FCurFileVersion := Value;end;procedure TScUpdater.SetForceCheck(const Value: Boolean);begin  FForceCheck := Value;end;procedure TScUpdater.SetForceUpdate(const Value: Boolean);begin  FForceUpdate := Value;end;end.
 |