testsqldb.pas 28 KB

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