testsqldb.pas 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. unit TestSQLDB;
  2. {
  3. Unit tests which are specific to the sqlDB components like TSQLQuery, TSQLConnection.
  4. }
  5. {$mode objfpc}{$H+}
  6. interface
  7. uses
  8. Classes, sqldb, SysUtils, fpcunit, testregistry,
  9. sqldbtoolsunit,toolsunit, db;
  10. type
  11. { TSQLDBTestCase }
  12. TSQLDBTestCase = class(TTestCase)
  13. private
  14. function GetSQLDBConnector: TSQLDBConnector;
  15. protected
  16. procedure SetUp; override;
  17. procedure TearDown; override;
  18. Property SQLDBConnector : TSQLDBConnector Read GetSQLDBConnector;
  19. end;
  20. { TTestTSQLQuery }
  21. TTestTSQLQuery = class(TSQLDBTestCase)
  22. private
  23. FMyQ: TSQLQuery;
  24. procedure DoAfterPost(DataSet: TDataSet);
  25. Procedure DoApplyUpdates;
  26. Procedure TrySetQueryOptions;
  27. Procedure TrySetPacketRecords;
  28. Protected
  29. Procedure Setup; override;
  30. published
  31. procedure TestMasterDetail;
  32. procedure TestUpdateServerIndexDefs;
  33. Procedure TestKeepOpenOnCommit;
  34. Procedure TestKeepOpenOnCommitPacketRecords;
  35. Procedure TestCheckSettingsOnlyWhenInactive;
  36. Procedure TestAutoApplyUpdatesPost;
  37. Procedure TestAutoApplyUpdatesDelete;
  38. Procedure TestCheckRowsAffected;
  39. Procedure TestAutoCommit;
  40. Procedure TestRefreshSQL;
  41. Procedure TestGeneratedRefreshSQL;
  42. Procedure TestGeneratedRefreshSQL1Field;
  43. Procedure TestGeneratedRefreshSQLNoKey;
  44. Procedure TestRefreshSQLMultipleRecords;
  45. Procedure TestRefreshSQLNoRecords;
  46. Procedure TestFetchAutoInc;
  47. procedure TestSequence;
  48. end;
  49. { TTestTSQLConnection }
  50. TTestTSQLConnection = class(TSQLDBTestCase)
  51. private
  52. procedure SetImplicit;
  53. procedure TestImplicitTransaction;
  54. procedure TestImplicitTransaction2;
  55. procedure TestImplicitTransactionNotAssignable;
  56. procedure TestImplicitTransactionOK;
  57. procedure TryOpen;
  58. published
  59. procedure TestUseImplicitTransaction;
  60. procedure TestUseExplicitTransaction;
  61. procedure TestExplicitConnect;
  62. end;
  63. { TTestTSQLScript }
  64. TTestTSQLScript = class(TSQLDBTestCase)
  65. published
  66. procedure TestExecuteScript;
  67. procedure TestScriptColon; //bug 25334
  68. procedure TestUseCommit; //E.g. Firebird cannot use COMMIT RETAIN if mixing DDL and DML in a script
  69. end;
  70. implementation
  71. { TTestTSQLQuery }
  72. procedure TTestTSQLQuery.Setup;
  73. begin
  74. inherited Setup;
  75. SQLDBConnector.Connection.Options:=[];
  76. end;
  77. procedure TTestTSQLQuery.TestMasterDetail;
  78. var MasterQuery, DetailQuery: TSQLQuery;
  79. MasterSource: TDataSource;
  80. begin
  81. with SQLDBConnector do
  82. try
  83. MasterQuery := GetNDataset(10) as TSQLQuery;
  84. MasterSource := TDatasource.Create(nil);
  85. MasterSource.DataSet := MasterQuery;
  86. DetailQuery := Query;
  87. DetailQuery.SQL.Text := 'select NAME from FPDEV where ID=:ID';
  88. DetailQuery.DataSource := MasterSource;
  89. MasterQuery.Open;
  90. DetailQuery.Open;
  91. CheckEquals('TestName1', DetailQuery.Fields[0].AsString);
  92. MasterQuery.MoveBy(3);
  93. CheckEquals('TestName4', DetailQuery.Fields[0].AsString);
  94. MasterQuery.Close;
  95. CheckTrue(DetailQuery.Active, 'Detail dataset should remain intact, when master dataset is closed');
  96. finally
  97. MasterSource.Free;
  98. end;
  99. end;
  100. procedure TTestTSQLQuery.TestUpdateServerIndexDefs;
  101. var Q: TSQLQuery;
  102. name1, name2, name3: string;
  103. begin
  104. // Test retrieval of information about indexes on unquoted and quoted table names
  105. // (tests also case-sensitivity for DB's that support case-sensitivity of quoted identifiers)
  106. // For ODBC Firebird/Interbase we must define primary key as named constraint and
  107. // in ODBC driver must be set: "quoted identifiers" and "sensitive identifier"
  108. // See also: TTestFieldTypes.TestUpdateIndexDefs
  109. with SQLDBConnector do
  110. begin
  111. // SQLite ignores case-sensitivity of quoted table names
  112. // MS SQL Server case-sensitivity of identifiers depends on the case-sensitivity of default collation of the database
  113. // MySQL case-sensitivity depends on case-sensitivity of server's file system
  114. if SQLServerType in [ssMSSQL,ssSQLite{$IFDEF WINDOWS},ssMySQL{$ENDIF}] then
  115. name1 := Connection.FieldNameQuoteChars[0]+'fpdev 2'+Connection.FieldNameQuoteChars[1]
  116. else
  117. name1 := 'FPDEV2';
  118. ExecuteDirect('create table '+name1+' (id integer not null, constraint PK_FPDEV21 primary key(id))');
  119. // same but quoted table name
  120. name2 := Connection.FieldNameQuoteChars[0]+'FPdev2'+Connection.FieldNameQuoteChars[1];
  121. ExecuteDirect('create table '+name2+' (ID2 integer not null, constraint PK_FPDEV22 primary key(ID2))');
  122. // embedded quote in table name
  123. if SQLServerType in [ssMySQL] then
  124. name3 := '`FPdev``2`'
  125. else
  126. name3 := Connection.FieldNameQuoteChars[0]+'FPdev""2'+Connection.FieldNameQuoteChars[1];
  127. ExecuteDirect('create table '+name3+' (Id3 integer not null, constraint PK_FPDEV23 primary key(Id3))');
  128. CommitDDL;
  129. end;
  130. try
  131. Q := SQLDBConnector.Query;
  132. Q.SQL.Text:='select * from '+name1;
  133. Q.Prepare;
  134. Q.ServerIndexDefs.Update;
  135. CheckEquals(1, Q.ServerIndexDefs.Count);
  136. Q.SQL.Text:='select * from '+name2;
  137. Q.Prepare;
  138. Q.ServerIndexDefs.Update;
  139. CheckEquals(1, Q.ServerIndexDefs.Count, '2.1');
  140. CheckTrue(CompareText('ID2', Q.ServerIndexDefs[0].Fields)=0, '2.2'+Q.ServerIndexDefs[0].Fields);
  141. CheckTrue(Q.ServerIndexDefs[0].Options=[ixPrimary,ixUnique], '2.3');
  142. Q.SQL.Text:='select * from '+name3;
  143. Q.Prepare;
  144. Q.ServerIndexDefs.Update;
  145. CheckEquals(1, Q.ServerIndexDefs.Count, '3.1');
  146. CheckTrue(CompareText('ID3', Q.ServerIndexDefs[0].Fields)=0, '3.2');
  147. CheckTrue(Q.ServerIndexDefs[0].Options=[ixPrimary,ixUnique], '3.3');
  148. finally
  149. Q.UnPrepare;
  150. with SQLDBConnector do
  151. begin
  152. ExecuteDirect('DROP TABLE '+name1);
  153. ExecuteDirect('DROP TABLE '+name2);
  154. ExecuteDirect('DROP TABLE '+name3);
  155. CommitDDL;
  156. end;
  157. end;
  158. end;
  159. procedure TTestTSQLQuery.TestKeepOpenOnCommit;
  160. var Q: TSQLQuery;
  161. I: Integer;
  162. begin
  163. // Test that for a SQL query with Options=sqoKeepOpenOnCommit, calling commit does not close the dataset.
  164. // Test also that an edit still works.
  165. with SQLDBConnector do
  166. begin
  167. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  168. Transaction.Commit;
  169. for I:=1 to 20 do
  170. ExecuteDirect(Format('INSERT INTO FPDEV2 values (%d,''%.6d'')',[i,i]));
  171. Transaction.Commit;
  172. Q := SQLDBConnector.Query;
  173. Q.SQL.Text:='select * from FPDEV2';
  174. Q.Options:=[sqoKeepOpenOnCommit];
  175. AssertEquals('PacketRecords forced to -1',-1,Q.PacketRecords);
  176. Q.Open;
  177. AssertEquals('Got all records',20,Q.RecordCount);
  178. Q.SQLTransaction.Commit;
  179. AssertTrue('Still open after transaction',Q.Active);
  180. // Now check editing
  181. Q.Locate('id',20,[]);
  182. Q.Edit;
  183. Q.FieldByName('a').AsString:='abc';
  184. Q.Post;
  185. AssertTrue('Have updates pending',Q.UpdateStatus=usModified);
  186. Q.ApplyUpdates;
  187. AssertTrue('Have no more updates pending',Q.UpdateStatus=usUnmodified);
  188. Q.Close;
  189. Q.SQL.Text:='select * from FPDEV2 where (id=20) and (a=''abc'')';
  190. Q.Open;
  191. AssertTrue('Have modified data record in database', not (Q.EOF AND Q.BOF));
  192. end;
  193. end;
  194. procedure TTestTSQLQuery.TrySetPacketRecords;
  195. begin
  196. FMyQ.PacketRecords:=10;
  197. end;
  198. procedure TTestTSQLQuery.TestKeepOpenOnCommitPacketRecords;
  199. begin
  200. with SQLDBConnector do
  201. begin
  202. FMyQ := SQLDBConnector.Query;
  203. FMyQ.Options:=[sqoKeepOpenOnCommit];
  204. AssertException('Cannot set PacketRecords when sqoKeepOpenOnCommit is active',EDatabaseError,@TrySetPacketRecords);
  205. end;
  206. end;
  207. procedure TTestTSQLQuery.TrySetQueryOptions;
  208. begin
  209. FMyQ.Options:=[sqoKeepOpenOnCommit];
  210. end;
  211. procedure TTestTSQLQuery.TestCheckSettingsOnlyWhenInactive;
  212. begin
  213. // Check that we can only set QueryOptions when the query is inactive.
  214. with SQLDBConnector do
  215. begin
  216. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  217. Transaction.Commit;
  218. ExecuteDirect(Format('INSERT INTO FPDEV2 values (%d,''%.6d'')',[1,1]));
  219. Transaction.Commit;
  220. FMyQ := SQLDBConnector.Query;
  221. FMyQ.SQL.Text:='select * from FPDEV2';
  222. FMyQ := SQLDBConnector.Query;
  223. FMyQ.Open;
  224. AssertException('Cannot set Options when query is active',EDatabaseError,@TrySetQueryOptions);
  225. end;
  226. end;
  227. procedure TTestTSQLQuery.DoAfterPost(DataSet: TDataSet);
  228. begin
  229. AssertTrue('Have modifications in after post',FMyq.UpdateStatus=usModified)
  230. end;
  231. procedure TTestTSQLQuery.TestAutoApplyUpdatesPost;
  232. var Q: TSQLQuery;
  233. I: Integer;
  234. begin
  235. // Test that if sqoAutoApplyUpdates is in QueryOptions, then POST automatically does an ApplyUpdates
  236. // Test also that POST afterpost event is backwards compatible.
  237. with SQLDBConnector do
  238. begin
  239. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  240. Transaction.COmmit;
  241. for I:=1 to 2 do
  242. ExecuteDirect(Format('INSERT INTO FPDEV2 values (%d,''%.6d'')',[i,i]));
  243. Transaction.COmmit;
  244. Q := SQLDBConnector.Query;
  245. FMyQ:=Q; // so th event handler can reach it.
  246. Q.SQL.Text:='select * from FPDEV2';
  247. Q.Options:=[sqoAutoApplyUpdates];
  248. // We must test that in AfterPost, the modification is still there, for backwards compatibilty
  249. Q.AfterPost:=@DoAfterPost;
  250. Q.Open;
  251. AssertEquals('Got all records',2,Q.RecordCount);
  252. // Now check editing
  253. Q.Locate('id',2,[]);
  254. Q.Edit;
  255. Q.FieldByName('a').AsString:='abc';
  256. Q.Post;
  257. AssertTrue('Have no more updates pending',Q.UpdateStatus=usUnmodified);
  258. Q.Close;
  259. Q.SQL.Text:='select * from FPDEV2 where (id=2) and (a=''abc'')';
  260. Q.Open;
  261. AssertTrue('Have modified data record in database',not (Q.EOF AND Q.BOF));
  262. end;
  263. end;
  264. procedure TTestTSQLQuery.TestAutoApplyUpdatesDelete;
  265. var Q: TSQLQuery;
  266. I: Integer;
  267. begin
  268. // Test that if sqoAutoApplyUpdates is in QueryOptions, then Delete automatically does an ApplyUpdates
  269. with SQLDBConnector do
  270. begin
  271. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  272. Transaction.COmmit;
  273. for I:=1 to 2 do
  274. ExecuteDirect(Format('INSERT INTO FPDEV2 values (%d,''%.6d'')',[i,i]));
  275. Transaction.COmmit;
  276. Q := SQLDBConnector.Query;
  277. FMyQ:=Q; // so th event handler can reach it.
  278. Q.SQL.Text:='select * from FPDEV2';
  279. Q.Options:=[sqoAutoApplyUpdates];
  280. // We must test that in AfterPost, the modification is still there, for backwards compatibilty
  281. Q.AfterPost:=@DoAfterPost;
  282. Q.Open;
  283. AssertEquals('Got all records',2,Q.RecordCount);
  284. // Now check editing
  285. Q.Locate('id',2,[]);
  286. Q.Delete;
  287. AssertTrue('Have no more updates pending',Q.UpdateStatus=usUnmodified);
  288. Q.Close;
  289. Q.SQL.Text:='select * from FPDEV2 where (id=2)';
  290. Q.Open;
  291. AssertTrue('Data record is deleted in database', (Q.EOF AND Q.BOF));
  292. end;
  293. end;
  294. procedure TTestTSQLQuery.DoApplyUpdates;
  295. begin
  296. FMyQ.ApplyUpdates();
  297. end;
  298. procedure TTestTSQLQuery.TestCheckRowsAffected;
  299. var Q: TSQLQuery;
  300. I: Integer;
  301. begin
  302. // Test that if sqoAutoApplyUpdates is in QueryOptions, then Delete automatically does an ApplyUpdates
  303. with SQLDBConnector do
  304. begin
  305. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  306. Transaction.COmmit;
  307. for I:=1 to 2 do
  308. ExecuteDirect(Format('INSERT INTO FPDEV2 values (%d,''%.6d'')',[i,i]));
  309. Transaction.COmmit;
  310. SQLDBConnector.Connection.Options:=[scoApplyUpdatesChecksRowsAffected];
  311. Q := SQLDBConnector.Query;
  312. Q.SQL.Text:='select * from FPDEV2';
  313. Q.DeleteSQL.Text:='delete from FPDEV2';
  314. Q.Open;
  315. AssertEquals('Got all records',2,Q.RecordCount);
  316. // Now check editing
  317. Q.Delete;
  318. FMyQ:=Q;
  319. AssertException('RowsAffected > 1 raises exception',EUpdateError,@DoApplyUpdates);
  320. end;
  321. end;
  322. procedure TTestTSQLQuery.TestAutoCommit;
  323. var
  324. I : Integer;
  325. begin
  326. with SQLDBConnector do
  327. begin
  328. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  329. if Transaction.Active then
  330. Transaction.Commit;
  331. Query.Options:=[sqoAutoCommit];
  332. for I:=1 to 2 do
  333. begin
  334. Query.SQL.Text:=Format('INSERT INTO FPDEV2 values (%d,''%.6d'');',[i,i]);
  335. Query.Prepare;
  336. Query.ExecSQL;
  337. // We do not commit anything explicitly.
  338. end;
  339. AssertFalse('Transaction is still active after expected auto commit', Transaction.Active);
  340. Connection.Close;
  341. Connection.Open;
  342. Query.SQL.Text:='SELECT COUNT(*) from FPDEV2';
  343. Query.Open;
  344. AssertEquals('Records haven''t been committed to database', 2, Query.Fields[0].AsInteger);
  345. end;
  346. end;
  347. procedure TTestTSQLQuery.TestRefreshSQL;
  348. var
  349. Q: TSQLQuery;
  350. begin
  351. with SQLDBConnector do
  352. begin
  353. ExecuteDirect('create table FPDEV2 (id integer not null primary key, a varchar(5) default ''abcde'', b integer default 1)');
  354. if Transaction.Active then
  355. Transaction.Commit;
  356. end;
  357. Q:=SQLDBConnector.Query;
  358. Q.SQL.Text:='select * from FPDEV2';
  359. Q.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  360. Q.RefreshSQL.Text:='SELECT a,b FROM FPDEV2 WHERE (id=:id)';
  361. Q.Open;
  362. Q.Insert; // #1 record
  363. Q.FieldByName('id').AsInteger:=1;
  364. Q.Post;
  365. Q.Append; // #2 record
  366. Q.FieldByName('id').AsInteger:=2;
  367. Q.Post;
  368. AssertTrue('Field value has not been fetched after Post', Q.FieldByName('a').IsNull);
  369. Q.ApplyUpdates(0);
  370. // #2 record:
  371. AssertEquals('Still on correct field', 2, Q.FieldByName('id').AsInteger);
  372. AssertEquals('Field value has been fetched from the database', 'abcde', Q.FieldByName('a').AsString);
  373. AssertEquals('Field value has been fetched from the database', 1, Q.FieldByName('b').AsInteger);
  374. Q.Prior;
  375. // #1 record:
  376. AssertEquals('Still on correct field', 1, Q.FieldByName('id').AsInteger);
  377. AssertEquals('Field value has been fetched from the database', 'abcde', Q.FieldByName('a').AsString);
  378. AssertEquals('Field value has been fetched from the database', 1, Q.FieldByName('b').AsInteger);
  379. end;
  380. procedure TTestTSQLQuery.TestGeneratedRefreshSQL;
  381. var
  382. Q: TSQLQuery;
  383. begin
  384. with SQLDBConnector do
  385. begin
  386. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  387. if Transaction.Active then
  388. Transaction.Commit;
  389. end;
  390. Q:=SQLDBConnector.Query;
  391. Q.SQL.Text:='select * from FPDEV2';
  392. Q.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  393. Q.Open;
  394. With Q.FieldByName('id') do
  395. ProviderFlags:=ProviderFlags+[pfInKey];
  396. With Q.FieldByName('a') do
  397. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  398. With Q.FieldByName('b') do
  399. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  400. Q.Insert;
  401. Q.FieldByName('id').AsInteger:=1;
  402. Q.Post;
  403. AssertTrue('Field value has not been fetched after post',Q.FieldByName('a').IsNull);
  404. Q.ApplyUpdates(0);
  405. AssertEquals('Still on correct field',1,Q.FieldByName('id').AsInteger);
  406. AssertEquals('Field value has been fetched from the database ','abcde',Q.FieldByName('a').AsString);
  407. AssertEquals('Field value has been fetched from the database ','fgh',Q.FieldByName('b').AsString);
  408. end;
  409. procedure TTestTSQLQuery.TestGeneratedRefreshSQL1Field;
  410. var
  411. Q: TSQLQuery;
  412. begin
  413. with SQLDBConnector do
  414. begin
  415. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  416. if Transaction.Active then
  417. Transaction.Commit;
  418. end;
  419. Q:=SQLDBConnector.Query;
  420. Q.SQL.Text:='select * from FPDEV2';
  421. Q.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  422. Q.Open;
  423. With Q.FieldByName('id') do
  424. ProviderFlags:=ProviderFlags+[pfInKey];
  425. With Q.FieldByName('a') do
  426. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  427. Q.Insert;
  428. Q.FieldByName('id').AsInteger:=1;
  429. Q.Post;
  430. AssertTrue('Field value has not been fetched after post',Q.FieldByName('a').IsNull);
  431. Q.ApplyUpdates(0);
  432. AssertEquals('Still on correct field',1,Q.FieldByName('id').AsInteger);
  433. AssertEquals('Field value a has been fetched from the database ','abcde',Q.FieldByName('a').AsString);
  434. AssertEquals('Field value b has NOT been fetched from the database ','',Q.FieldByName('b').AsString);
  435. end;
  436. procedure TTestTSQLQuery.TestGeneratedRefreshSQLNoKey;
  437. begin
  438. with SQLDBConnector do
  439. begin
  440. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  441. if Transaction.Active then
  442. Transaction.Commit;
  443. end;
  444. FMyQ:=SQLDBConnector.Query;
  445. FMyQ.SQL.Text:='select * from FPDEV2';
  446. FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  447. FMyQ.Open;
  448. With FMyQ.FieldByName('id') do
  449. ProviderFlags:=ProviderFlags-[pfInKey];
  450. With FMyQ.FieldByName('a') do
  451. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  452. FMyQ.Insert;
  453. FMyQ.FieldByName('id').AsInteger:=1;
  454. FMyQ.Post;
  455. AssertException('Cannot refresh without primary key',EUpdateError,@DoApplyUpdates);
  456. end;
  457. procedure TTestTSQLQuery.TestRefreshSQLMultipleRecords;
  458. begin
  459. with SQLDBConnector do
  460. begin
  461. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  462. if Transaction.Active then
  463. Transaction.Commit;
  464. ExecuteDirect('insert into FPDEV2 (id) values (123)');
  465. if Transaction.Active then
  466. Transaction.Commit;
  467. end;
  468. FMyQ:=SQLDBConnector.Query;
  469. FMyQ.SQL.Text:='select * from FPDEV2';
  470. FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  471. FMyQ.RefreshSQL.Text:='select * from FPDEV2';
  472. FMyQ.Open;
  473. With FMyQ.FieldByName('id') do
  474. ProviderFlags:=ProviderFlags+[pfInKey];
  475. With FMyQ.FieldByName('a') do
  476. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  477. FMyQ.Insert;
  478. FMyQ.FieldByName('id').AsInteger:=1;
  479. FMyQ.Post;
  480. AssertException('Multiple records returned by RefreshSQL gives an error',EUpdateError,@DoApplyUpdates);
  481. end;
  482. procedure TTestTSQLQuery.TestRefreshSQLNoRecords;
  483. begin
  484. with SQLDBConnector do
  485. begin
  486. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  487. if Transaction.Active then
  488. Transaction.Commit;
  489. ExecuteDirect('insert into FPDEV2 (id) values (123)');
  490. if Transaction.Active then
  491. Transaction.Commit;
  492. end;
  493. FMyQ:=SQLDBConnector.Query;
  494. FMyQ.SQL.Text:='select * from FPDEV2';
  495. FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  496. FMyQ.RefreshSQL.Text:='select * from FPDEV2 where 1=2';
  497. FMyQ.Open;
  498. With FMyQ.FieldByName('id') do
  499. ProviderFlags:=ProviderFlags+[pfInKey];
  500. With FMyQ.FieldByName('a') do
  501. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  502. FMyQ.Insert;
  503. FMyQ.FieldByName('id').AsInteger:=1;
  504. FMyQ.Post;
  505. AssertException('No records returned by RefreshSQL gives an error',EUpdateError,@DoApplyUpdates);
  506. end;
  507. procedure TTestTSQLQuery.TestFetchAutoInc;
  508. var datatype: string;
  509. id: largeint;
  510. begin
  511. with SQLDBConnector do
  512. begin
  513. case SQLServerType of
  514. ssMySQL:
  515. datatype := 'integer auto_increment';
  516. ssMSSQL, ssSybase:
  517. datatype := 'integer identity';
  518. ssSQLite:
  519. datatype := 'integer';
  520. else
  521. Ignore(STestNotApplicable);
  522. end;
  523. ExecuteDirect('create table FPDEV2 (id '+datatype+' primary key, f varchar(5))');
  524. CommitDDL;
  525. end;
  526. with SQLDBConnector.Query do
  527. begin
  528. SQL.Text:='select * from FPDEV2';
  529. Open;
  530. Insert;
  531. FieldByName('f').AsString:='a';
  532. Post; // #1 record
  533. Append;
  534. FieldByName('f').AsString:='b';
  535. Post; // #2 record
  536. AssertTrue('ID field is not null after Post', FieldByName('id').IsNull);
  537. First; // #1 record
  538. ApplyUpdates(0);
  539. AssertTrue('ID field is still null after ApplyUpdates', Not FieldByName('id').IsNull);
  540. // Should be 1 after the table was created, but this is not guaranteed... So we just test positive values.
  541. id := FieldByName('id').AsLargeInt;
  542. AssertTrue('ID field has not positive value', id>0);
  543. Next; // #2 record
  544. AssertTrue('Next ID value is not greater than previous', FieldByName('id').AsLargeInt>id);
  545. end;
  546. end;
  547. procedure TTestTSQLQuery.TestSequence;
  548. var SequenceNames : TStringList;
  549. begin
  550. case SQLServerType of
  551. ssFirebird:
  552. SQLDBConnector.ExecuteDirect('create sequence FPDEV_SEQ1');
  553. ssMSSQL, ssOracle, ssPostgreSQL:
  554. SQLDBConnector.ExecuteDirect('create sequence FPDEV_SEQ1 MINVALUE 1');
  555. else
  556. Ignore(STestNotApplicable);
  557. end;
  558. SQLDBConnector.ExecuteDirect('create table FPDEV2 (id integer)');
  559. SQLDBConnector.CommitDDL;
  560. with SQLDBConnector.Query do
  561. begin
  562. SQL.Text := 'select * from FPDEV2';
  563. Sequence.FieldName:='id';
  564. Sequence.SequenceName:='FPDEV_SEQ1';
  565. Open;
  566. // default is get next value on new record
  567. Append;
  568. AssertEquals(1, FieldByName('id').AsInteger);
  569. Sequence.ApplyEvent:=saeOnPost;
  570. Append;
  571. AssertTrue('Field ID must be null after Append', FieldByName('id').IsNull);
  572. Post;
  573. AssertEquals(2, FieldByName('id').AsInteger);
  574. end;
  575. // test GetSequenceNames
  576. SequenceNames := TStringList.Create;
  577. try
  578. SQLDBConnector.Connection.GetSequenceNames(SequenceNames);
  579. AssertTrue(SequenceNames.IndexOf('FPDEV_SEQ1') >= 0);
  580. finally
  581. SequenceNames.Free;
  582. end;
  583. SQLDBConnector.ExecuteDirect('drop sequence FPDEV_SEQ1');
  584. SQLDBConnector.CommitDDL;
  585. end;
  586. { TTestTSQLConnection }
  587. procedure TTestTSQLConnection.TestImplicitTransaction;
  588. Var
  589. T : TSQLTransaction;
  590. begin
  591. T:=TSQLTransaction.Create(Nil);
  592. try
  593. T.Options:=[stoUseImplicit];
  594. T.DataBase:=SQLDBConnector.Connection;
  595. finally
  596. T.Free;
  597. end;
  598. end;
  599. procedure TTestTSQLConnection.TestImplicitTransaction2;
  600. Var
  601. T : TSQLTransaction;
  602. begin
  603. T:=TSQLTransaction.Create(Nil);
  604. try
  605. T.Options:=[stoUseImplicit];
  606. SQLDBConnector.Connection.Transaction:=T;
  607. finally
  608. T.Free;
  609. end;
  610. end;
  611. procedure TTestTSQLConnection.SetImplicit;
  612. begin
  613. SQLDBConnector.Transaction.Options:=[stoUseImplicit];
  614. end;
  615. procedure TTestTSQLConnection.TestImplicitTransactionNotAssignable;
  616. begin
  617. AssertException('Cannot set toUseImplicit option if database does not allow it',EDatabaseError,@SetImplicit);
  618. AssertException('Cannot assign database to transaction with toUseImplicit, if database does not allow it',EDatabaseError,@TestImplicitTransaction);
  619. AssertException('Cannot assign transaction with toUseImplicit to database, if database does not allow it',EDatabaseError,@TestImplicitTransaction2);
  620. end;
  621. procedure TTestTSQLConnection.TestImplicitTransactionOK;
  622. var
  623. Q : TSQLQuery;
  624. T : TSQLTransaction;
  625. I : Integer;
  626. begin
  627. with SQLDBConnector do
  628. begin
  629. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  630. if Transaction.Active then
  631. Transaction.Commit;
  632. end;
  633. SetImplicit;
  634. Q:=SQLDBConnector.Query;
  635. for I:=1 to 2 do
  636. begin
  637. Q.SQL.Text:=Format('INSERT INTO FPDEV2 values (%d,''%.6d'');',[i,i]);
  638. Q.Prepare;
  639. Q.ExecSQL;
  640. // We do not commit anything explicitly.
  641. end;
  642. Q:=Nil;
  643. T:=Nil;
  644. try
  645. T:=TSQLTransaction.Create(Nil);
  646. Q:=TSQLQuery.Create(Nil);
  647. Q.Transaction:=T;
  648. Q.Database:=SQLDBConnector.Connection;
  649. T.Database:=SQLDBConnector.Connection;
  650. Q.SQL.text:='SELECT COUNT(*) from FPDEV2';
  651. Q.Open;
  652. AssertEquals('Records have been committed to database',2,Q.Fields[0].AsInteger);
  653. finally
  654. Q.Free;
  655. T.Free;
  656. end;
  657. end;
  658. procedure TTestTSQLConnection.TestUseImplicitTransaction;
  659. begin
  660. if (sqImplicitTransaction in SQLDBConnector.Connection.ConnOptions) then
  661. TestImplicitTransactionOK
  662. else
  663. TestImplicitTransactionNotAssignable;
  664. end;
  665. procedure TTestTSQLConnection.TryOpen;
  666. begin
  667. SQLDBConnector.Query.Open;
  668. end;
  669. procedure TTestTSQLConnection.TestUseExplicitTransaction;
  670. begin
  671. SQLDBConnector.Transaction.Active:=False;
  672. SQLDBConnector.Transaction.Options:=[stoExplicitStart];
  673. SQLDBConnector.Query.SQL.Text:='select * from FPDEV';
  674. AssertException('toExplicitStart raises exception on implicit start',EDatabaseError,@TryOpen)
  675. end;
  676. procedure TTestTSQLConnection.TestExplicitConnect;
  677. begin
  678. SQLDBConnector.Transaction.Active:=False;
  679. SQLDBConnector.Connection.Options:=[scoExplicitConnect];
  680. SQLDBConnector.Connection.Connected:=False;
  681. SQLDBConnector.Query.SQL.Text:='select * from FPDEV';
  682. AssertException('toExplicitStart raises exception on implicit start',EDatabaseError,@TryOpen)
  683. end;
  684. { TTestTSQLScript }
  685. procedure TTestTSQLScript.TestExecuteScript;
  686. var Ascript : TSQLScript;
  687. begin
  688. Ascript := TSQLScript.Create(nil);
  689. try
  690. with Ascript do
  691. begin
  692. DataBase := SQLDBConnector.Connection;
  693. Transaction := SQLDBConnector.Transaction;
  694. Script.Clear;
  695. Script.Append('create table FPDEV_A (id int);');
  696. Script.Append('create table FPDEV_B (id int);');
  697. ExecuteScript;
  698. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  699. SQLDBConnector.CommitDDL;
  700. end;
  701. finally
  702. AScript.Free;
  703. SQLDBConnector.ExecuteDirect('drop table FPDEV_A');
  704. SQLDBConnector.ExecuteDirect('drop table FPDEV_B');
  705. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  706. SQLDBConnector.CommitDDL;
  707. end;
  708. end;
  709. procedure TTestTSQLScript.TestScriptColon;
  710. // Bug 25334: TSQLScript incorrectly treats : in scripts as sqldb query parameter markers
  711. // Firebird-only test; can be extended for other dbs that use : in SQL
  712. var
  713. Ascript : TSQLScript;
  714. begin
  715. if not(SQLConnType in [interbase]) then Ignore(STestNotApplicable);
  716. Ascript := TSQLScript.Create(nil);
  717. try
  718. with Ascript do
  719. begin
  720. DataBase := SQLDBConnector.Connection;
  721. Transaction := SQLDBConnector.Transaction;
  722. Script.Clear;
  723. UseSetTerm := true;
  724. // Example procedure that selects table names
  725. Script.Append(
  726. 'SET TERM ^ ; '+LineEnding+
  727. 'CREATE PROCEDURE FPDEV_TESTCOLON '+LineEnding+
  728. 'RETURNS (tblname VARCHAR(31)) '+LineEnding+
  729. 'AS '+LineEnding+
  730. 'begin '+LineEnding+
  731. '/* Show tables. Note statement uses colon */ '+LineEnding+
  732. 'FOR '+LineEnding+
  733. ' SELECT RDB$RELATION_NAME '+LineEnding+
  734. ' FROM RDB$RELATIONS '+LineEnding+
  735. ' ORDER BY RDB$RELATION_NAME '+LineEnding+
  736. ' INTO :tblname '+LineEnding+
  737. 'DO '+LineEnding+
  738. ' SUSPEND; '+LineEnding+
  739. 'end^ '+LineEnding+
  740. 'SET TERM ; ^'
  741. );
  742. ExecuteScript;
  743. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  744. SQLDBConnector.CommitDDL;
  745. end;
  746. finally
  747. AScript.Free;
  748. SQLDBConnector.ExecuteDirect('DROP PROCEDURE FPDEV_TESTCOLON');
  749. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  750. SQLDBConnector.CommitDDL;
  751. end;
  752. end;
  753. procedure TTestTSQLScript.TestUseCommit;
  754. // E.g. Firebird needs explicit COMMIT sometimes, e.g. if mixing DDL and DML
  755. // statements in a script.
  756. // Probably same as bug 17829 Error executing SQL script
  757. const
  758. TestValue='Some text';
  759. var
  760. Ascript : TSQLScript;
  761. CheckQuery : TSQLQuery;
  762. begin
  763. Ascript := TSQLScript.Create(nil);
  764. try
  765. with Ascript do
  766. begin
  767. DataBase := SQLDBConnector.Connection;
  768. Transaction := SQLDBConnector.Transaction;
  769. Script.Clear;
  770. UseCommit:=true;
  771. // Example procedure that selects table names
  772. Script.Append('CREATE TABLE fpdev_scriptusecommit (logmessage VARCHAR(255));');
  773. Script.Append('COMMIT;'); //needed for table to show up
  774. Script.Append('INSERT INTO fpdev_scriptusecommit (logmessage) VALUES('''+TestValue+''');');
  775. Script.Append('COMMIT;');
  776. ExecuteScript;
  777. // This line should not run, as the commit above should have taken care of it:
  778. //SQLDBConnector.CommitDDL;
  779. // Test whether second line of script executed, just to be sure
  780. CheckQuery:=SQLDBConnector.Query;
  781. CheckQuery.SQL.Text:='SELECT logmessage FROM fpdev_scriptusecommit ';
  782. CheckQuery.Open;
  783. CheckEquals(TestValue, CheckQuery.Fields[0].AsString, 'Insert script line should have inserted '+TestValue);
  784. CheckQuery.Close;
  785. end;
  786. finally
  787. AScript.Free;
  788. SQLDBConnector.ExecuteDirect('DROP TABLE fpdev_scriptusecommit');
  789. SQLDBConnector.Transaction.Commit;
  790. end;
  791. end;
  792. { TSQLDBTestCase }
  793. function TSQLDBTestCase.GetSQLDBConnector: TSQLDBConnector;
  794. begin
  795. Result:=DBConnector as TSQLDBConnector;
  796. end;
  797. procedure TSQLDBTestCase.SetUp;
  798. begin
  799. inherited SetUp;
  800. InitialiseDBConnector;
  801. DBConnector.StartTest(TestName);
  802. end;
  803. procedure TSQLDBTestCase.TearDown;
  804. begin
  805. DBConnector.StopTest(TestName);
  806. if assigned(DBConnector) then
  807. with SQLDBConnector do
  808. if Assigned(Transaction) and Transaction.Active and not (stoUseImplicit in Transaction.Options) then
  809. Transaction.Rollback;
  810. FreeDBConnector;
  811. inherited TearDown;
  812. end;
  813. initialization
  814. if uppercase(dbconnectorname)='SQL' then
  815. begin
  816. RegisterTest(TTestTSQLQuery);
  817. RegisterTest(TTestTSQLConnection);
  818. RegisterTest(TTestTSQLScript);
  819. end;
  820. end.