ScAutoUpdateUnit.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. unit ScAutoUpdateUnit;
  2. interface
  3. uses
  4. DB, ADODB, ScFileArchiver, ScTablesUnit, Classes;
  5. const
  6. MaxFieldCount = 512;
  7. PrimaryKey = 'PrimaryKey';
  8. type
  9. PFieldDefs = ^TFieldDefs;
  10. TFieldDefs = array [0..MaxFieldCount - 1] of TScFieldDef;
  11. PTableDef = ^TTableDef;
  12. // 表定义结构
  13. TTableDef = record
  14. // 表名
  15. TableName: string;
  16. // 字段数
  17. FieldCount: Integer;
  18. // 字段结构数组
  19. FieldDefs: PFieldDefs;
  20. // 是否需要重新创建
  21. Recreate: Boolean;
  22. // 重新创建主键
  23. RecreatePrimaryKey: Boolean;
  24. end;
  25. TSQLType = (stAlter, stCreate, stReCreate);
  26. TUpdateEventType = (uetAddFields, uetKeys, uetAfterUpdate);
  27. TUpdateEvent = procedure (ATableName: string; AEventType: TUpdateEventType;
  28. ASQLType: TSQLType; AConnection: TADOConnection);
  29. TScUpdater = class(TObject)
  30. private
  31. FTableDefList: TList;
  32. FFileName: string;
  33. FConnection: TADOConnection;
  34. FFileVer: string;
  35. FQuery: TADOQuery;
  36. FForceUpdate: Boolean;
  37. FForceCheck: Boolean;
  38. FCurFileVersion: string;
  39. FOnUpdateData: TUpdateEvent;
  40. function GetCurFileVersion: string;
  41. // 返回True:表存在,返回False: 表不存在
  42. function CheckTable(ATableName: string): Boolean;
  43. procedure GenerateSQL(ATableDef: PTableDef; ASQLType: TSQLType; ASQLList: TStrings);
  44. procedure InternalExcuteSQL(ASQL: string; AHideException: Boolean = False; AOpen: Boolean = False);
  45. function ExcuteUpdateSQL(ASQLList: TStrings): Boolean;
  46. procedure SetForceUpdate(const Value: Boolean);
  47. procedure SetForceCheck(const Value: Boolean);
  48. procedure SetCurFileVersion(const Value: string);
  49. // 字符串是否是事件
  50. // 事件字符串格式:"事件名 表名 操作类型"
  51. function CheckEvent(AText: string): Boolean;
  52. public
  53. constructor Create;
  54. destructor Destroy; override;
  55. // 打开
  56. procedure Open(AFileName: string; AConnection: TADOConnection;
  57. AFileVer: string);
  58. // 关闭
  59. procedure Close;
  60. // 执行升级操作
  61. function ExcuteUpdate: Boolean;
  62. // 执行其他SQL语句(如建立、删除索引)
  63. procedure ExcuteSQL(ASQL: string);
  64. // 添加表定义
  65. function AddTableDef(ATableName: string; AFieldDefs: PFieldDefs; AFieldCount: Integer;
  66. AReCreate, ARecreatePK: Boolean): Integer;
  67. // 文件是否需要升级(根据版本号判断)
  68. function FileNeedUpdate: Boolean;
  69. // 强制进行表和字段的升级检查
  70. property ForceCheck: Boolean read FForceCheck write SetForceCheck;
  71. // 强制升级所有表和字段
  72. property ForceUpdate: Boolean read FForceUpdate write SetForceUpdate;
  73. // 最新文件版本
  74. property CurrentFileVer: string read GetCurFileVersion write SetCurFileVersion;
  75. // 事件
  76. // 重建关键字之前
  77. property OnUpdateData: TUpdateEvent read FOnUpdateData
  78. write FOnUpdateData;
  79. end;
  80. const
  81. SQLTypeStrs: array [TSQLType] of string = ('Modify', 'Create', 'ReCreate');
  82. SOnUpdateData = 'OnUpdateData';
  83. implementation
  84. uses
  85. ConstMethodUnit, SysUtils;
  86. function StrToSQLType(ASQLType: string): TSQLType;
  87. var
  88. I: TSQLType;
  89. begin
  90. Result := stAlter;
  91. for I := Low(SQLTypeStrs) to High(SQLTypeStrs) do
  92. begin
  93. if SameText(ASQLType, SQLTypeStrs[I]) then
  94. begin
  95. Result := I;
  96. Break;
  97. end;
  98. end;
  99. end;
  100. { TScUpdater }
  101. function TScUpdater.AddTableDef(ATableName: string; AFieldDefs: PFieldDefs;
  102. AFieldCount: Integer; AReCreate, ARecreatePK: Boolean): Integer;
  103. var
  104. pRec: PTableDef;
  105. begin
  106. New(pRec);
  107. pRec^.TableName := ATableName;
  108. pRec^.FieldCount := AFieldCount;
  109. pRec^.FieldDefs := AFieldDefs;
  110. pRec^.Recreate := AReCreate;
  111. pRec^.RecreatePrimaryKey := ARecreatePK;
  112. Result := FTableDefList.Add(pRec);
  113. end;
  114. function TScUpdater.CheckEvent(AText: string): Boolean;
  115. var
  116. strText, strEvent, strTableName, strEventType, strSQLType: string;
  117. SQLType: TSQLType;
  118. iPos: Integer;
  119. begin
  120. Result := False;
  121. strText := AText;
  122. // 检查事件名称
  123. iPos := Pos(' ', strText);
  124. if iPos > 0 then
  125. begin
  126. strEvent := Copy(strText, 1, iPos - 1);
  127. Delete(strText, 1, iPos);
  128. if SameText(strEvent, SOnUpdateData) then
  129. begin
  130. // 事件类型
  131. iPos := Pos(' ', strText);
  132. strEventType := Copy(strText, 1, iPos - 1);
  133. Delete(strText, 1, iPos);
  134. // 表名
  135. iPos := Pos(' ', strText);
  136. strTableName := Copy(strText, 1, iPos - 1);
  137. Delete(strText, 1, iPos);
  138. // 操作类型
  139. strSQLType := strText;
  140. SQLType := StrToSQLType(strSQLType);
  141. Result := True;
  142. if Assigned(FOnUpdateData) then
  143. FOnUpdateData(strTableName, TUpdateEventType(StrToInt(strEventType)), SQLType, FConnection);
  144. end;
  145. end;
  146. end;
  147. function TScUpdater.CheckTable(ATableName: string): Boolean;
  148. var
  149. I: Integer;
  150. Names: TStringList;
  151. begin
  152. Names := TStringList.Create;
  153. try
  154. FConnection.GetTableNames(Names);
  155. if Names.IndexOf(ATableName) < 0 then
  156. Result := False
  157. else
  158. Result := True;
  159. finally
  160. Names.Free;
  161. end;
  162. end;
  163. procedure TScUpdater.Close;
  164. begin
  165. FQuery.Close;
  166. end;
  167. constructor TScUpdater.Create;
  168. begin
  169. FForceUpdate := False;
  170. FForceCheck := False;
  171. FTableDefList := TList.Create;
  172. FQuery := TADOQuery.Create(nil);
  173. end;
  174. destructor TScUpdater.Destroy;
  175. begin
  176. Close;
  177. FQuery.Free;
  178. ClearPointerList(FTableDefList);
  179. FTableDefList.Free;
  180. inherited;
  181. end;
  182. procedure TScUpdater.ExcuteSQL(ASQL: string);
  183. begin
  184. InternalExcuteSQL(ASQL);
  185. end;
  186. function TScUpdater.ExcuteUpdate: Boolean;
  187. var
  188. I: Integer;
  189. pRec: PTableDef;
  190. SQLs: TStringList;
  191. SQLType: TSQLType;
  192. bHasError: Boolean;
  193. sError: string;
  194. begin
  195. Result := False;
  196. bHasError := False;
  197. sError := '';
  198. if FileNeedUpdate then
  199. begin
  200. SQLs := TStringList.Create;
  201. try
  202. for I := 0 to FTableDefList.Count - 1 do
  203. begin
  204. pRec := PTableDef(FTableDefList[I]);
  205. if CheckTable(pRec^.TableName) then
  206. begin
  207. if pRec^.Recreate then
  208. SQLType := stReCreate
  209. else
  210. SQLType := stAlter;
  211. end
  212. else
  213. SQLType := stCreate;
  214. GenerateSQL(pRec, SQLType, SQLs);
  215. if SQLs.Count > 0 then
  216. if not ExcuteUpdateSQL(SQLs) then
  217. begin
  218. bHasError := True;
  219. sError := sError + #13#10 + Format('Update operation [%s] on table [%s] can not excute!', [SQLTypeStrs[SQLType], pRec^.TableName]);
  220. end;
  221. end;
  222. finally
  223. SQLs.Free;
  224. end;
  225. if bHasError then
  226. MessageWarning(0, '升级文件时发生错误,无法完成升级。'#13#10'错误信息:' + sError)
  227. else
  228. Result := True;
  229. end;
  230. end;
  231. function TScUpdater.ExcuteUpdateSQL(ASQLList: TStrings): Boolean;
  232. var
  233. I: Integer;
  234. HideExcption: Boolean;
  235. begin
  236. Result := False;
  237. try
  238. for I := 0 to ASQLList.Count - 1 do
  239. begin
  240. if not CheckEvent(ASQLList[I]) then
  241. begin
  242. HideExcption := ASQLList.Objects[I] <> nil;
  243. if HideExcption then
  244. HideExcption := Boolean(Integer(ASQLList.Objects[I]));
  245. InternalExcuteSQL(ASQLList[I], HideExcption);
  246. end;
  247. end;
  248. Result := True;
  249. except
  250. end;
  251. end;
  252. function TScUpdater.FileNeedUpdate: Boolean;
  253. begin
  254. Result := (ScCompareFileVer(FFileVer, GetCurFileVersion) <> 0) or FForceCheck;
  255. end;
  256. function SameFieldType(AFieldType: TFieldType; AScFieldType: TScMDBFieldType): Boolean;
  257. begin
  258. Result := False;
  259. case AScFieldType of
  260. ftString:
  261. Result := (AFieldType = DB.ftWideString) or (AFieldType = DB.ftString);
  262. ftByte:
  263. Result := AFieldType = DB.ftWord;
  264. ftSmallint:
  265. Result := AFieldType = DB.ftSmallint;
  266. ftInteger:
  267. Result := AFieldType = DB.ftInteger;
  268. ftBoolean:
  269. Result := AFieldType = DB.ftBoolean;
  270. ftSingle:
  271. Result := AFieldType = DB.ftFloat;
  272. ftDouble:
  273. Result := AFieldType = DB.ftFloat;
  274. ftCurrency:
  275. Result := (AFieldType = DB.ftCurrency) or (AFieldType = DB.ftBCD);
  276. ftDateTime:
  277. Result := AFieldType = DB.ftDateTime;
  278. ftMemo:
  279. Result := AFieldType = DB.ftMemo;
  280. ftOLEObject:
  281. Result := AFieldType = DB.ftBlob;
  282. end;
  283. end;
  284. procedure TScUpdater.GenerateSQL(ATableDef: PTableDef; ASQLType: TSQLType; ASQLList: TStrings);
  285. function GenerateCreateSQL: string;
  286. var
  287. I: Integer;
  288. Def: TScFieldDef;
  289. strField, strFields, strKeyFields: string;
  290. begin
  291. Result := '';
  292. if ATableDef^.FieldCount > 0 then
  293. begin
  294. // CREATE TABLE table1
  295. Result := Format('CREATE TABLE %s ', [ATableDef^.TableName]);
  296. strFields := '';
  297. strKeyFields := '';
  298. for I := 0 to ATableDef^.FieldCount - 1 do
  299. begin
  300. Def := ATableDef^.FieldDefs[I];
  301. // field1 type
  302. strField := Def.FieldName + ' ' + ScMDBFieldTypeName[Def.FieldType];
  303. if Def.FieldType in [ftString] then
  304. // field1 type (size)
  305. strField := strField + ' ' + Format('(%d)', [Def.Size]);
  306. if Def.NotNull then
  307. // field1 type (size) NOT NULL
  308. strField := strField + ' ' + 'NOT NULL';
  309. if Def.PrimaryKey then
  310. strKeyFields := strKeyFields + Def.FieldName + ', ';
  311. strFields := strFields + strField + ', ';
  312. end;
  313. if strKeyFields <> '' then
  314. begin
  315. Delete(strKeyFields, Length(strKeyFields) - 1, 2);
  316. // CONSTRAINT PrimaryKey PRIMARY KEY (field1, field2...)
  317. strKeyFields := Format('CONSTRAINT %s PRIMARY KEY (%s)', [PrimaryKey, strKeyFields]);
  318. end
  319. else
  320. Delete(strFields, Length(strFields) - 1, 2);
  321. // (field1 type (size) NOT NULL, field2 type (size) NOT NULL..., CONSTRAINT PrimaryKey PRIMARY KEY (field1, field2...))
  322. strFields := Format('(%s)', [strFields + strKeyFields]);
  323. // CREATE TABLE table1 (field1 type (size) NOT NULL, field2 type (size) NOT NULL..., CONSTRAINT PrimaryKey PRIMARY KEY (field1, field2...))
  324. Result := Result + strFields;
  325. end;
  326. end;
  327. type
  328. TAlterType = (atAddField, atAlterField, atDropField, atAddIndex, atDropIndex);
  329. function GenerateSingleFieldAlterSQL(ATableName: string; AFieldDef: TScFieldDef;
  330. AOp: TAlterType; var ANeedDefault: Boolean): string;
  331. begin
  332. Result := '';
  333. ANeedDefault := False;
  334. case AOp of
  335. atAddField:
  336. begin
  337. Result := Format('ALTER TABLE %s ADD COLUMN %s %s',
  338. [ATableName, AFieldDef.FieldName, ScMDBFieldTypeName[AFieldDef.FieldType]]);
  339. if AFieldDef.FieldType in [ftString] then
  340. Result := Result + Format(' (%d)', [AFieldDef.Size]);
  341. if AFieldDef.NotNull then
  342. begin
  343. Result := Result + ' NOT NULL';
  344. case AFieldDef.FieldType of
  345. ftByte, ftSmallint, ftInteger, ftBoolean, ftSingle, ftDouble,
  346. ftCurrency, ftDateTime:
  347. ANeedDefault := True;
  348. end;
  349. end;
  350. end;
  351. atDropField:
  352. begin
  353. Result := Format('ALTER TABLE %s DROP COLUMN %s', [ATableName, AFieldDef.FieldName]);
  354. end;
  355. atAlterField:
  356. begin
  357. Result := Format('ALTER TABLE %s ALTER COLUMN %s %s',
  358. [ATableName, AFieldDef.FieldName, ScMDBFieldTypeName[AFieldDef.FieldType]]);
  359. if AFieldDef.FieldType in [ftString] then
  360. Result := Result + Format(' (%d)', [AFieldDef.Size]);
  361. if AFieldDef.NotNull then
  362. begin
  363. Result := Result + ' NOT NULL';
  364. case AFieldDef.FieldType of
  365. ftByte, ftSmallint, ftInteger, ftBoolean, ftSingle, ftDouble,
  366. ftCurrency, ftDateTime:
  367. ANeedDefault := True;
  368. end;
  369. end;
  370. end;
  371. end;
  372. end;
  373. function GenerateDefaultValueSQL(ATableName: string; AFieldDef: TScFieldDef): string;
  374. begin
  375. Result := '';
  376. case AFieldDef.FieldType of
  377. ftByte, ftSmallint, ftInteger:
  378. Result := Format('UPDATE %s SET %s = %d',
  379. [ATableName, AFieldDef.FieldName, 0]);
  380. ftSingle, ftDouble, ftCurrency:
  381. Result := Format('UPDATE %s SET %s = %f',
  382. [ATableName, AFieldDef.FieldName, 0.0]);
  383. ftBoolean:
  384. Result := Format('UPDATE %s SET %s = %s',
  385. [ATableName, AFieldDef.FieldName, 'FALSE']);
  386. ftDateTime:
  387. Result := Format('UPDATE %s SET %s = ''%s''',
  388. [ATableName, AFieldDef.FieldName, '2000-1-1 12:00:00']);
  389. end;
  390. end;
  391. function GenerateSingleKeyAlterSQL(ATableName, AIndexName, AFieldNames: string; AOp: TAlterType): string;
  392. begin
  393. Result := '';
  394. case AOp of
  395. atAddIndex:
  396. begin
  397. Result := Format('ALTER TABLE %s ADD CONSTRAINT %s Primary Key (%s)', [ATableName, AIndexName, AFieldNames]);
  398. end;
  399. atDropIndex:
  400. begin
  401. Result := Format('ALTER TABLE %s DROP CONSTRAINT %s', [ATableName, AIndexName]);
  402. end;
  403. end;
  404. end;
  405. procedure GenerateAlterSQL;
  406. var
  407. I, J: Integer;
  408. Field: TField;
  409. pDef: PScFieldDef;
  410. AddList, ModifyList: TList;
  411. KeyFields: string;
  412. bNeedDefaultValue: Boolean;
  413. begin
  414. InternalExcuteSQL(Format('SELECT * FROM %s WHERE 0=1', [ATableDef^.TableName]), False, True);
  415. AddList := TList.Create;
  416. ModifyList := TList.Create;
  417. try
  418. KeyFields := '';
  419. for I := 0 to ATableDef^.FieldCount - 1 do
  420. begin
  421. pDef := @ATableDef^.FieldDefs^[I];
  422. if (KeyFields <> '') and pDef^.PrimaryKey then
  423. KeyFields := KeyFields + ', ';
  424. if pDef^.PrimaryKey then
  425. KeyFields := KeyFields + pDef^.FieldName;
  426. AddList.Add(pDef);
  427. end;
  428. { if KeyFields <> '' then
  429. Delete(KeyFields, Length(KeyFields) - 1, 2);}
  430. for I := 0 to FQuery.Fields.Count - 1 do
  431. begin
  432. Field := FQuery.Fields[I];
  433. for J := 0 to AddList.Count - 1 do
  434. begin
  435. pDef := PScFieldDef(AddList[J]);
  436. if SameText(Field.FieldName, pDef^.FieldName) then
  437. begin
  438. if FForceUpdate then
  439. ModifyList.Add(pDef)
  440. else
  441. begin
  442. if not SameFieldType(Field.DataType, pDef^.FieldType) then
  443. ModifyList.Add(pDef)
  444. else if (Field.DataType in [ftWideString]) and (Field.Size <> pDef^.Size) then
  445. ModifyList.Add(pDef);
  446. end;
  447. AddList.Remove(pDef);
  448. Break;
  449. end;
  450. end;
  451. end;
  452. for I := 0 to ModifyList.Count - 1 do
  453. begin
  454. pDef := PScFieldDef(ModifyList[I]);
  455. ASQLList.Add(GenerateSingleFieldAlterSQL(ATableDef^.TableName, pDef^, atAlterField, bNeedDefaultValue));
  456. if bNeedDefaultValue then
  457. ASQLList.Add(GenerateDefaultValueSQL(ATableDef^.TableName, pDef^));
  458. end;
  459. for I := 0 to AddList.Count - 1 do
  460. begin
  461. pDef := PScFieldDef(AddList[I]);
  462. ASQLList.Add(GenerateSingleFieldAlterSQL(ATableDef^.TableName, pDef^, atAddField, bNeedDefaultValue));
  463. if bNeedDefaultValue then
  464. ASQLList.Add(GenerateDefaultValueSQL(ATableDef^.TableName, pDef^));
  465. end;
  466. if AddList.Count > 0 then
  467. // 添加事件
  468. ASQLList.Add(Format('%s %d %s %s', [SOnUpdateData, Ord(uetAddFields), ATableDef.TableName, SQLTypeStrs[ASQLType]]));
  469. if ATableDef.RecreatePrimaryKey then
  470. begin
  471. ASQLList.AddObject(GenerateSingleKeyAlterSQL(ATableDef^.TableName, PrimaryKey, KeyFields, atDropIndex), TObject(Integer(True)));
  472. // 添加事件
  473. ASQLList.Add(Format('%s %d %s %s', [SOnUpdateData, Ord(uetKeys), ATableDef.TableName, SQLTypeStrs[ASQLType]]));
  474. ASQLList.Add(GenerateSingleKeyAlterSQL(ATableDef^.TableName, PrimaryKey, KeyFields, atAddIndex));
  475. end;
  476. finally
  477. AddList.Free;
  478. ModifyList.Free;
  479. end;
  480. end;
  481. begin
  482. ASQLList.Clear;
  483. case ASQLType of
  484. stAlter:
  485. GenerateAlterSQL;
  486. stCreate:
  487. ASQLList.Add(GenerateCreateSQL);
  488. stReCreate:
  489. begin
  490. ASQLList.Add(Format('DROP TABLE %s', [ATableDef^.TableName]));
  491. ASQLList.Add(GenerateCreateSQL);
  492. end;
  493. end;
  494. if ASQLList.Count > 0 then
  495. ASQLList.Add(Format('%s %d %s %s', [SOnUpdateData, Ord(uetAfterUpdate), ATableDef^.TableName, SQLTypeStrs[ASQLType]]));
  496. end;
  497. function TScUpdater.GetCurFileVersion: string;
  498. begin
  499. if FCurFileVersion <> '' then
  500. Result := FCurFileVersion
  501. else
  502. begin
  503. Result := ConstBillsFileVersion;
  504. {$IFDEF _ScBills}
  505. Result := ConstBillsFileVersion;
  506. {$ENDIF}
  507. {$IFDEF _ScBudget}
  508. {$IFDEF _ScEstimate}
  509. Result := ConstEstimateFileVersion;
  510. {$ELSE}
  511. Result := ConstBudgetFileVersion;
  512. {$ENDIF}
  513. {$ENDIF}
  514. {$IFDEF _ScRation}
  515. Result := ConstRationLibFileVersion;
  516. {$ENDIF}
  517. end;
  518. end;
  519. procedure TScUpdater.InternalExcuteSQL(ASQL: string; AHideException, AOpen: Boolean);
  520. begin
  521. FQuery.Close;
  522. FQuery.SQL.Clear;
  523. FQuery.SQL.Add(ASQL);
  524. try
  525. if AOpen then
  526. FQuery.Open
  527. else
  528. FQuery.ExecSQL;
  529. except
  530. if not AHideException then
  531. raise;
  532. end;
  533. end;
  534. procedure TScUpdater.Open(AFileName: string; AConnection: TADOConnection;
  535. AFileVer: string);
  536. begin
  537. FFileName := AFileName;
  538. FConnection := AConnection;
  539. FFileVer := AFileVer;
  540. if AFileVer = '' then
  541. FFileVer := '0.0.0.0';
  542. FQuery.Connection := AConnection;
  543. ClearPointerList(FTableDefList);
  544. end;
  545. procedure TScUpdater.SetCurFileVersion(const Value: string);
  546. begin
  547. FCurFileVersion := Value;
  548. end;
  549. procedure TScUpdater.SetForceCheck(const Value: Boolean);
  550. begin
  551. FForceCheck := Value;
  552. end;
  553. procedure TScUpdater.SetForceUpdate(const Value: Boolean);
  554. begin
  555. FForceUpdate := Value;
  556. end;
  557. end.