testsqldb.pas 32 KB

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