testsqldb.pas 33 KB

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