testsqldb.pas 28 KB

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