testsqldb.pas 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982
  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. 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,sqoPreferRefresh];
  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.TestRefreshSQL;
  350. var
  351. Q: TSQLQuery;
  352. begin
  353. with SQLDBConnector do
  354. begin
  355. ExecuteDirect('create table FPDEV2 (id integer not null primary key, a varchar(5) default ''abcde'', b integer default 1)');
  356. if Transaction.Active then
  357. Transaction.Commit;
  358. end;
  359. Q:=SQLDBConnector.Query;
  360. Q.OPtions:=Q.OPtions+[sqoPreferRefresh];
  361. Q.SQL.Text:='select * from FPDEV2';
  362. Q.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  363. Q.RefreshSQL.Text:='SELECT a,b FROM FPDEV2 WHERE (id=:id)';
  364. Q.Open;
  365. Q.Insert; // #1 record
  366. Q.FieldByName('id').AsInteger:=1;
  367. Q.Post;
  368. Q.Append; // #2 record
  369. Q.FieldByName('id').AsInteger:=2;
  370. Q.Post;
  371. AssertTrue('Field value has not been fetched after Post', Q.FieldByName('a').IsNull);
  372. Q.ApplyUpdates(0);
  373. // #2 record:
  374. AssertEquals('Still on correct field', 2, Q.FieldByName('id').AsInteger);
  375. AssertEquals('Field value has been fetched from the database', 'abcde', Q.FieldByName('a').AsString);
  376. AssertEquals('Field value has been fetched from the database', 1, Q.FieldByName('b').AsInteger);
  377. Q.Prior;
  378. // #1 record:
  379. AssertEquals('Still on correct field', 1, Q.FieldByName('id').AsInteger);
  380. AssertEquals('Field value has been fetched from the database', 'abcde', Q.FieldByName('a').AsString);
  381. AssertEquals('Field value has been fetched from the database', 1, Q.FieldByName('b').AsInteger);
  382. end;
  383. procedure TTestTSQLQuery.TestGeneratedRefreshSQL;
  384. var
  385. Q: TSQLQuery;
  386. begin
  387. with SQLDBConnector do
  388. begin
  389. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  390. if Transaction.Active then
  391. Transaction.Commit;
  392. end;
  393. Q:=SQLDBConnector.Query;
  394. Q.SQL.Text:='select * from FPDEV2';
  395. Q.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  396. Q.OPtions:=Q.OPtions+[sqoPreferRefresh];
  397. Q.Open;
  398. With Q.FieldByName('id') do
  399. ProviderFlags:=ProviderFlags+[pfInKey];
  400. With Q.FieldByName('a') do
  401. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  402. With Q.FieldByName('b') do
  403. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  404. Q.Insert;
  405. Q.FieldByName('id').AsInteger:=1;
  406. Q.Post;
  407. AssertTrue('Field value has not been fetched after post',Q.FieldByName('a').IsNull);
  408. Q.ApplyUpdates(0);
  409. AssertEquals('Still on correct field',1,Q.FieldByName('id').AsInteger);
  410. AssertEquals('Field value has been fetched from the database ','abcde',Q.FieldByName('a').AsString);
  411. AssertEquals('Field value has been fetched from the database ','fgh',Q.FieldByName('b').AsString);
  412. end;
  413. procedure TTestTSQLQuery.TestGeneratedRefreshSQL1Field;
  414. var
  415. Q: TSQLQuery;
  416. begin
  417. with SQLDBConnector do
  418. begin
  419. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  420. if Transaction.Active then
  421. Transaction.Commit;
  422. end;
  423. Q:=SQLDBConnector.Query;
  424. Q.SQL.Text:='select * from FPDEV2';
  425. Q.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  426. Q.OPtions:=Q.OPtions+[sqoPreferRefresh];
  427. Q.Open;
  428. With Q.FieldByName('id') do
  429. ProviderFlags:=ProviderFlags+[pfInKey];
  430. With Q.FieldByName('a') do
  431. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  432. Q.Insert;
  433. Q.FieldByName('id').AsInteger:=1;
  434. Q.Post;
  435. AssertTrue('Field value has not been fetched after post',Q.FieldByName('a').IsNull);
  436. Q.ApplyUpdates(0);
  437. AssertEquals('Still on correct field',1,Q.FieldByName('id').AsInteger);
  438. AssertEquals('Field value a has been fetched from the database ','abcde',Q.FieldByName('a').AsString);
  439. AssertEquals('Field value b has NOT been fetched from the database ','',Q.FieldByName('b').AsString);
  440. end;
  441. procedure TTestTSQLQuery.TestGeneratedRefreshSQLNoKey;
  442. begin
  443. with SQLDBConnector do
  444. begin
  445. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  446. if Transaction.Active then
  447. Transaction.Commit;
  448. end;
  449. FMyQ:=SQLDBConnector.Query;
  450. FMyQ.SQL.Text:='select * from FPDEV2';
  451. FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  452. FMyQ.OPtions:=FMyQ.OPtions+[sqoPreferRefresh];
  453. FMyQ.Open;
  454. With FMyQ.FieldByName('id') do
  455. ProviderFlags:=ProviderFlags-[pfInKey];
  456. With FMyQ.FieldByName('a') do
  457. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  458. FMyQ.Insert;
  459. FMyQ.FieldByName('id').AsInteger:=1;
  460. FMyQ.Post;
  461. AssertException('Cannot refresh without primary key',EUpdateError,@DoApplyUpdates);
  462. end;
  463. procedure TTestTSQLQuery.TestRefreshSQLMultipleRecords;
  464. begin
  465. with SQLDBConnector do
  466. begin
  467. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  468. if Transaction.Active then
  469. Transaction.Commit;
  470. ExecuteDirect('insert into FPDEV2 (id) values (123)');
  471. if Transaction.Active then
  472. Transaction.Commit;
  473. end;
  474. FMyQ:=SQLDBConnector.Query;
  475. FMyQ.OPtions:=FMyQ.OPtions+[sqoPreferRefresh];
  476. FMyQ.SQL.Text:='select * from FPDEV2';
  477. FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  478. FMyQ.RefreshSQL.Text:='select * from FPDEV2';
  479. FMyQ.Open;
  480. With FMyQ.FieldByName('id') do
  481. ProviderFlags:=ProviderFlags+[pfInKey];
  482. With FMyQ.FieldByName('a') do
  483. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  484. FMyQ.Insert;
  485. FMyQ.FieldByName('id').AsInteger:=1;
  486. FMyQ.Post;
  487. AssertException('Multiple records returned by RefreshSQL gives an error',EUpdateError,@DoApplyUpdates);
  488. end;
  489. procedure TTestTSQLQuery.TestRefreshSQLNoRecords;
  490. begin
  491. with SQLDBConnector do
  492. begin
  493. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  494. if Transaction.Active then
  495. Transaction.Commit;
  496. ExecuteDirect('insert into FPDEV2 (id) values (123)');
  497. if Transaction.Active then
  498. Transaction.Commit;
  499. end;
  500. FMyQ:=SQLDBConnector.Query;
  501. FMyQ.OPtions:=FMyQ.OPtions+[sqoPreferRefresh];
  502. FMyQ.SQL.Text:='select * from FPDEV2';
  503. FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  504. FMyQ.RefreshSQL.Text:='select * from FPDEV2 where 1=2';
  505. FMyQ.Open;
  506. With FMyQ.FieldByName('id') do
  507. ProviderFlags:=ProviderFlags+[pfInKey];
  508. With FMyQ.FieldByName('a') do
  509. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  510. FMyQ.Insert;
  511. FMyQ.FieldByName('id').AsInteger:=1;
  512. FMyQ.Post;
  513. AssertException('No records returned by RefreshSQL gives an error',EUpdateError,@DoApplyUpdates);
  514. end;
  515. procedure TTestTSQLQuery.TestFetchAutoInc;
  516. var datatype: string;
  517. id: largeint;
  518. begin
  519. with SQLDBConnector do
  520. begin
  521. case SQLServerType of
  522. ssMySQL:
  523. datatype := 'integer auto_increment';
  524. ssMSSQL, ssSybase:
  525. datatype := 'integer identity';
  526. ssSQLite:
  527. datatype := 'integer';
  528. else
  529. Ignore(STestNotApplicable);
  530. end;
  531. ExecuteDirect('create table FPDEV2 (id '+datatype+' primary key, f varchar(5))');
  532. CommitDDL;
  533. end;
  534. with SQLDBConnector.Query do
  535. begin
  536. SQL.Text:='select * from FPDEV2';
  537. Open;
  538. Insert;
  539. FieldByName('f').AsString:='a';
  540. Post; // #1 record
  541. Append;
  542. FieldByName('f').AsString:='b';
  543. Post; // #2 record
  544. AssertTrue('ID field is not null after Post', FieldByName('id').IsNull);
  545. First; // #1 record
  546. ApplyUpdates(0);
  547. AssertTrue('ID field is still null after ApplyUpdates', Not FieldByName('id').IsNull);
  548. // Should be 1 after the table was created, but this is not guaranteed... So we just test positive values.
  549. id := FieldByName('id').AsLargeInt;
  550. AssertTrue('ID field has not positive value', id>0);
  551. Next; // #2 record
  552. AssertTrue('Next ID value is not greater than previous', FieldByName('id').AsLargeInt>id);
  553. end;
  554. end;
  555. procedure TTestTSQLQuery.TestSequence;
  556. var SequenceNames : TStringList;
  557. begin
  558. case SQLServerType of
  559. ssFirebird:
  560. SQLDBConnector.ExecuteDirect('create sequence FPDEV_SEQ1');
  561. ssMSSQL, ssOracle, ssPostgreSQL:
  562. SQLDBConnector.ExecuteDirect('create sequence FPDEV_SEQ1 MINVALUE 1');
  563. else
  564. Ignore(STestNotApplicable);
  565. end;
  566. SQLDBConnector.ExecuteDirect('create table FPDEV2 (id integer)');
  567. SQLDBConnector.CommitDDL;
  568. with SQLDBConnector.Query do
  569. begin
  570. SQL.Text := 'select * from FPDEV2';
  571. Sequence.FieldName:='id';
  572. Sequence.SequenceName:='FPDEV_SEQ1';
  573. Open;
  574. // default is get next value on new record
  575. Append;
  576. AssertEquals(1, FieldByName('id').AsInteger);
  577. Sequence.ApplyEvent:=saeOnPost;
  578. Append;
  579. AssertTrue('Field ID must be null after Append', FieldByName('id').IsNull);
  580. Post;
  581. AssertEquals(2, FieldByName('id').AsInteger);
  582. end;
  583. // test GetSequenceNames
  584. SequenceNames := TStringList.Create;
  585. try
  586. SQLDBConnector.Connection.GetSequenceNames(SequenceNames);
  587. AssertTrue(SequenceNames.IndexOf('FPDEV_SEQ1') >= 0);
  588. finally
  589. SequenceNames.Free;
  590. end;
  591. SQLDBConnector.ExecuteDirect('drop sequence FPDEV_SEQ1');
  592. SQLDBConnector.CommitDDL;
  593. end;
  594. procedure TTestTSQLQuery.TestReturningInsert;
  595. begin
  596. with SQLDBConnector do
  597. begin
  598. if not (sqSupportReturning in Connection.ConnOptions) then
  599. Ignore(STestNotApplicable);
  600. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  601. if Transaction.Active then
  602. Transaction.Commit;
  603. ExecuteDirect('insert into FPDEV2 (id) values (123)');
  604. if Transaction.Active then
  605. Transaction.Commit;
  606. end;
  607. FMyQ:=SQLDBConnector.Query;
  608. FMyQ.SQL.Text:='select * from FPDEV2';
  609. // FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  610. FMyQ.Open;
  611. With FMyQ.FieldByName('id') do
  612. ProviderFlags:=ProviderFlags+[pfInKey];
  613. With FMyQ.FieldByName('a') do
  614. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert];
  615. With FMyQ.FieldByName('b') do
  616. ProviderFlags:=[];
  617. FMyQ.Insert;
  618. FMyQ.FieldByName('id').AsInteger:=1;
  619. FMyQ.Post;
  620. FMyQ.ApplyUpdates;
  621. AssertEquals('a updated','abcde',FMyQ.FieldByName('a').AsString);
  622. AssertEquals('b not updated','',FMyQ.FieldByName('b').AsString);
  623. end;
  624. procedure TTestTSQLQuery.TestReturningUpdate;
  625. begin
  626. with SQLDBConnector do
  627. begin
  628. if not (sqSupportReturning in Connection.ConnOptions) then
  629. Ignore(STestNotApplicable);
  630. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  631. if Transaction.Active then
  632. Transaction.Commit;
  633. ExecuteDirect('insert into FPDEV2 (id) values (123)');
  634. if Transaction.Active then
  635. Transaction.Commit;
  636. end;
  637. FMyQ:=SQLDBConnector.Query;
  638. FMyQ.SQL.Text:='select * from FPDEV2';
  639. FMyQ.Open;
  640. With FMyQ.FieldByName('id') do
  641. ProviderFlags:=ProviderFlags+[pfInKey];
  642. With FMyQ.FieldByName('b') do
  643. ProviderFlags:=[pfRefreshOnUpdate]; // Do not update, just fetch new value
  644. FMyQ.Edit;
  645. FMyQ.FieldByName('a').AsString:='ccc';
  646. FMyQ.Post;
  647. SQLDBConnector.ExecuteDirect('update FPDEV2 set b=''123'' where id=123');
  648. FMyQ.ApplyUpdates;
  649. AssertEquals('a updated','ccc',FMyQ.FieldByName('a').AsString);
  650. AssertEquals('b updated','123',FMyQ.FieldByName('b').AsString);
  651. end;
  652. { TTestTSQLConnection }
  653. procedure TTestTSQLConnection.TestImplicitTransaction;
  654. Var
  655. T : TSQLTransaction;
  656. begin
  657. T:=TSQLTransaction.Create(Nil);
  658. try
  659. T.Options:=[stoUseImplicit];
  660. T.DataBase:=SQLDBConnector.Connection;
  661. finally
  662. T.Free;
  663. end;
  664. end;
  665. procedure TTestTSQLConnection.TestImplicitTransaction2;
  666. Var
  667. T : TSQLTransaction;
  668. begin
  669. T:=TSQLTransaction.Create(Nil);
  670. try
  671. T.Options:=[stoUseImplicit];
  672. SQLDBConnector.Connection.Transaction:=T;
  673. finally
  674. T.Free;
  675. end;
  676. end;
  677. procedure TTestTSQLConnection.SetImplicit;
  678. begin
  679. SQLDBConnector.Transaction.Options:=[stoUseImplicit];
  680. end;
  681. procedure TTestTSQLConnection.TestImplicitTransactionNotAssignable;
  682. begin
  683. AssertException('Cannot set toUseImplicit option if database does not allow it',EDatabaseError,@SetImplicit);
  684. AssertException('Cannot assign database to transaction with toUseImplicit, if database does not allow it',EDatabaseError,@TestImplicitTransaction);
  685. AssertException('Cannot assign transaction with toUseImplicit to database, if database does not allow it',EDatabaseError,@TestImplicitTransaction2);
  686. end;
  687. procedure TTestTSQLConnection.TestImplicitTransactionOK;
  688. var
  689. Q : TSQLQuery;
  690. T : TSQLTransaction;
  691. I : Integer;
  692. begin
  693. with SQLDBConnector do
  694. begin
  695. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  696. if Transaction.Active then
  697. Transaction.Commit;
  698. end;
  699. SetImplicit;
  700. Q:=SQLDBConnector.Query;
  701. for I:=1 to 2 do
  702. begin
  703. Q.SQL.Text:=Format('INSERT INTO FPDEV2 values (%d,''%.6d'');',[i,i]);
  704. Q.Prepare;
  705. Q.ExecSQL;
  706. // We do not commit anything explicitly.
  707. end;
  708. Q:=Nil;
  709. T:=Nil;
  710. try
  711. T:=TSQLTransaction.Create(Nil);
  712. Q:=TSQLQuery.Create(Nil);
  713. Q.Transaction:=T;
  714. Q.Database:=SQLDBConnector.Connection;
  715. T.Database:=SQLDBConnector.Connection;
  716. Q.SQL.text:='SELECT COUNT(*) from FPDEV2';
  717. Q.Open;
  718. AssertEquals('Records have been committed to database',2,Q.Fields[0].AsInteger);
  719. finally
  720. Q.Free;
  721. T.Free;
  722. end;
  723. end;
  724. procedure TTestTSQLConnection.TestUseImplicitTransaction;
  725. begin
  726. if (sqImplicitTransaction in SQLDBConnector.Connection.ConnOptions) then
  727. TestImplicitTransactionOK
  728. else
  729. TestImplicitTransactionNotAssignable;
  730. end;
  731. procedure TTestTSQLConnection.TryOpen;
  732. begin
  733. SQLDBConnector.Query.Open;
  734. end;
  735. procedure TTestTSQLConnection.TestUseExplicitTransaction;
  736. begin
  737. SQLDBConnector.Transaction.Active:=False;
  738. SQLDBConnector.Transaction.Options:=[stoExplicitStart];
  739. SQLDBConnector.Query.SQL.Text:='select * from FPDEV';
  740. AssertException('toExplicitStart raises exception on implicit start',EDatabaseError,@TryOpen)
  741. end;
  742. procedure TTestTSQLConnection.TestExplicitConnect;
  743. begin
  744. SQLDBConnector.Transaction.Active:=False;
  745. SQLDBConnector.Connection.Options:=[scoExplicitConnect];
  746. SQLDBConnector.Connection.Connected:=False;
  747. SQLDBConnector.Query.SQL.Text:='select * from FPDEV';
  748. AssertException('toExplicitStart raises exception on implicit start',EDatabaseError,@TryOpen)
  749. end;
  750. { TTestTSQLScript }
  751. procedure TTestTSQLScript.TestExecuteScript;
  752. var Ascript : TSQLScript;
  753. begin
  754. Ascript := TSQLScript.Create(nil);
  755. try
  756. with Ascript do
  757. begin
  758. DataBase := SQLDBConnector.Connection;
  759. Transaction := SQLDBConnector.Transaction;
  760. Script.Clear;
  761. Script.Append('create table FPDEV_A (id int);');
  762. Script.Append('create table FPDEV_B (id int);');
  763. ExecuteScript;
  764. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  765. SQLDBConnector.CommitDDL;
  766. end;
  767. finally
  768. AScript.Free;
  769. SQLDBConnector.ExecuteDirect('drop table FPDEV_A');
  770. SQLDBConnector.ExecuteDirect('drop table FPDEV_B');
  771. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  772. SQLDBConnector.CommitDDL;
  773. end;
  774. end;
  775. procedure TTestTSQLScript.TestScriptColon;
  776. // Bug 25334: TSQLScript incorrectly treats : in scripts as sqldb query parameter markers
  777. // Firebird-only test; can be extended for other dbs that use : in SQL
  778. var
  779. Ascript : TSQLScript;
  780. begin
  781. if not(SQLConnType in [interbase]) then Ignore(STestNotApplicable);
  782. Ascript := TSQLScript.Create(nil);
  783. try
  784. with Ascript do
  785. begin
  786. DataBase := SQLDBConnector.Connection;
  787. Transaction := SQLDBConnector.Transaction;
  788. Script.Clear;
  789. UseSetTerm := true;
  790. // Example procedure that selects table names
  791. Script.Append(
  792. 'SET TERM ^ ; '+LineEnding+
  793. 'CREATE PROCEDURE FPDEV_TESTCOLON '+LineEnding+
  794. 'RETURNS (tblname VARCHAR(31)) '+LineEnding+
  795. 'AS '+LineEnding+
  796. 'begin '+LineEnding+
  797. '/* Show tables. Note statement uses colon */ '+LineEnding+
  798. 'FOR '+LineEnding+
  799. ' SELECT RDB$RELATION_NAME '+LineEnding+
  800. ' FROM RDB$RELATIONS '+LineEnding+
  801. ' ORDER BY RDB$RELATION_NAME '+LineEnding+
  802. ' INTO :tblname '+LineEnding+
  803. 'DO '+LineEnding+
  804. ' SUSPEND; '+LineEnding+
  805. 'end^ '+LineEnding+
  806. 'SET TERM ; ^'
  807. );
  808. ExecuteScript;
  809. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  810. SQLDBConnector.CommitDDL;
  811. end;
  812. finally
  813. AScript.Free;
  814. SQLDBConnector.ExecuteDirect('DROP PROCEDURE FPDEV_TESTCOLON');
  815. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  816. SQLDBConnector.CommitDDL;
  817. end;
  818. end;
  819. procedure TTestTSQLScript.TestUseCommit;
  820. // E.g. Firebird needs explicit COMMIT sometimes, e.g. if mixing DDL and DML
  821. // statements in a script.
  822. // Probably same as bug 17829 Error executing SQL script
  823. const
  824. TestValue='Some text';
  825. var
  826. Ascript : TSQLScript;
  827. CheckQuery : TSQLQuery;
  828. begin
  829. Ascript := TSQLScript.Create(nil);
  830. try
  831. with Ascript do
  832. begin
  833. DataBase := SQLDBConnector.Connection;
  834. Transaction := SQLDBConnector.Transaction;
  835. Script.Clear;
  836. UseCommit:=true;
  837. // Example procedure that selects table names
  838. Script.Append('CREATE TABLE fpdev_scriptusecommit (logmessage VARCHAR(255));');
  839. Script.Append('COMMIT;'); //needed for table to show up
  840. Script.Append('INSERT INTO fpdev_scriptusecommit (logmessage) VALUES('''+TestValue+''');');
  841. Script.Append('COMMIT;');
  842. ExecuteScript;
  843. // This line should not run, as the commit above should have taken care of it:
  844. //SQLDBConnector.CommitDDL;
  845. // Test whether second line of script executed, just to be sure
  846. CheckQuery:=SQLDBConnector.Query;
  847. CheckQuery.SQL.Text:='SELECT logmessage FROM fpdev_scriptusecommit ';
  848. CheckQuery.Open;
  849. CheckEquals(TestValue, CheckQuery.Fields[0].AsString, 'Insert script line should have inserted '+TestValue);
  850. CheckQuery.Close;
  851. end;
  852. finally
  853. AScript.Free;
  854. SQLDBConnector.ExecuteDirect('DROP TABLE fpdev_scriptusecommit');
  855. SQLDBConnector.Transaction.Commit;
  856. end;
  857. end;
  858. { TSQLDBTestCase }
  859. function TSQLDBTestCase.GetSQLDBConnector: TSQLDBConnector;
  860. begin
  861. Result:=DBConnector as TSQLDBConnector;
  862. end;
  863. procedure TSQLDBTestCase.SetUp;
  864. begin
  865. inherited SetUp;
  866. InitialiseDBConnector;
  867. DBConnector.StartTest(TestName);
  868. end;
  869. procedure TSQLDBTestCase.TearDown;
  870. begin
  871. DBConnector.StopTest(TestName);
  872. if assigned(DBConnector) then
  873. with SQLDBConnector do
  874. if Assigned(Transaction) and Transaction.Active and not (stoUseImplicit in Transaction.Options) then
  875. Transaction.Rollback;
  876. FreeDBConnector;
  877. inherited TearDown;
  878. end;
  879. initialization
  880. if uppercase(dbconnectorname)='SQL' then
  881. begin
  882. RegisterTest(TTestTSQLQuery);
  883. RegisterTest(TTestTSQLConnection);
  884. RegisterTest(TTestTSQLScript);
  885. end;
  886. end.