123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 |
- unit ScAutoUpdateUnit;
- interface
- uses
- 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';
- implementation
- uses
- 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.
|