testsqldb.pas 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  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 GetDBC: TSQLDBConnector;
  15. protected
  16. procedure SetUp; override;
  17. procedure TearDown; override;
  18. Property SQLDBConnector : TSQLDBConnector Read GetDBC;
  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 TestDisconnected;
  35. Procedure TestDisconnectedPacketRecords;
  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.TestDisconnected;
  160. var Q: TSQLQuery;
  161. I, J : Integer;
  162. begin
  163. // Test that for a disconnected SQL query, 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.TestDisconnectedPacketRecords;
  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, J : 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, J : 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, J : 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. Q: TSQLQuery;
  331. T : TSQLTransaction;
  332. I, J : Integer;
  333. begin
  334. with SQLDBConnector do
  335. begin
  336. TryDropIfExist('testdiscon');
  337. ExecuteDirect('create table testdiscon (id integer not null, a varchar(10), constraint pk_testdiscon primary key(id))');
  338. if Transaction.Active then
  339. Transaction.Commit;
  340. end;
  341. Q:=SQLDBConnector.Query;
  342. Q.Options:=[sqoAutoCommit];
  343. for I:=1 to 2 do
  344. begin
  345. Q.SQL.Text:=Format('INSERT INTO testdiscon values (%d,''%.6d'');',[i,i]);
  346. Q.Prepare;
  347. Q.ExecSQL;
  348. // We do not commit anything explicitly.
  349. end;
  350. Q:=Nil;
  351. T:=Nil;
  352. try
  353. T:=TSQLTransaction.Create(Nil);
  354. Q:=TSQLQuery.Create(Nil);
  355. Q.Transaction:=T;
  356. Q.Database:=SQLDBConnector.Connection;
  357. T.Database:=SQLDBConnector.Connection;
  358. Q.SQL.text:='SELECT COUNT(*) from testdiscon';
  359. Q.Open;
  360. AssertEquals('Records have been committed to database',2,Q.Fields[0].AsInteger);
  361. finally
  362. Q.Free;
  363. T.Free;
  364. end;
  365. end;
  366. Procedure TTestTSQLQuery.TestRefreshSQL;
  367. var
  368. Q: TSQLQuery;
  369. T : TSQLTransaction;
  370. I, J : Integer;
  371. begin
  372. with SQLDBConnector do
  373. begin
  374. TryDropIfExist('testdefval');
  375. ExecuteDirect('create table testdefval (id integer not null, a varchar(10) default ''abcde'', constraint pk_testdefval primary key(id))');
  376. if Transaction.Active then
  377. Transaction.Commit;
  378. end;
  379. Q:=SQLDBConnector.Query;
  380. Q.SQL.Text:='select * from testdefval';
  381. Q.InsertSQL.Text:='insert into testdefval (id) values (:id)';
  382. Q.RefreshSQL.Text:='SELECT a FROM testdefval WHERE (id=:id)';
  383. Q.Open;
  384. Q.Insert;
  385. Q.FieldByName('id').AsInteger:=1;
  386. Q.Post;
  387. AssertTrue('field value has not been fetched after post',Q.FieldByName('a').IsNull);
  388. Q.ApplyUpdates(0);
  389. AssertEquals('Still on correc field',1,Q.FieldByName('id').AsInteger);
  390. AssertEquals('field value has been fetched from the database ','abcde',Q.FieldByName('a').AsString);
  391. end;
  392. Procedure TTestTSQLQuery.TestGeneratedRefreshSQL;
  393. var
  394. Q: TSQLQuery;
  395. T : TSQLTransaction;
  396. I, J : Integer;
  397. begin
  398. with SQLDBConnector do
  399. begin
  400. TryDropIfExist('testdefval');
  401. ExecuteDirect('create table testdefval (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint pk_testdefval primary key(id))');
  402. if Transaction.Active then
  403. Transaction.Commit;
  404. end;
  405. Q:=SQLDBConnector.Query;
  406. Q.SQL.Text:='select * from testdefval';
  407. Q.InsertSQL.Text:='insert into testdefval (id) values (:id)';
  408. Q.Open;
  409. With Q.FieldByName('id') do
  410. ProviderFlags:=ProviderFlags+[pfInKey];
  411. With Q.FieldByName('a') do
  412. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  413. With Q.FieldByName('b') do
  414. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  415. Q.Insert;
  416. Q.FieldByName('id').AsInteger:=1;
  417. Q.Post;
  418. AssertTrue('field value has not been fetched after post',Q.FieldByName('a').IsNull);
  419. Q.ApplyUpdates(0);
  420. AssertEquals('Still on correc field',1,Q.FieldByName('id').AsInteger);
  421. AssertEquals('field value has been fetched from the database ','abcde',Q.FieldByName('a').AsString);
  422. AssertEquals('field value has been fetched from the database ','fgh',Q.FieldByName('b').AsString);
  423. end;
  424. Procedure TTestTSQLQuery.TestGeneratedRefreshSQL1Field;
  425. var
  426. Q: TSQLQuery;
  427. T : TSQLTransaction;
  428. I, J : Integer;
  429. begin
  430. with SQLDBConnector do
  431. begin
  432. TryDropIfExist('testdefval');
  433. ExecuteDirect('create table testdefval (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint pk_testdefval primary key(id))');
  434. if Transaction.Active then
  435. Transaction.Commit;
  436. end;
  437. Q:=SQLDBConnector.Query;
  438. Q.SQL.Text:='select * from testdefval';
  439. Q.InsertSQL.Text:='insert into testdefval (id) values (:id)';
  440. Q.Open;
  441. With Q.FieldByName('id') do
  442. ProviderFlags:=ProviderFlags+[pfInKey];
  443. With Q.FieldByName('a') do
  444. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  445. Q.Insert;
  446. Q.FieldByName('id').AsInteger:=1;
  447. Q.Post;
  448. AssertTrue('field value has not been fetched after post',Q.FieldByName('a').IsNull);
  449. Q.ApplyUpdates(0);
  450. AssertEquals('Still on correc field',1,Q.FieldByName('id').AsInteger);
  451. AssertEquals('field value a has been fetched from the database ','abcde',Q.FieldByName('a').AsString);
  452. AssertEquals('field value b has NOT been fetched from the database ','',Q.FieldByName('b').AsString);
  453. end;
  454. Procedure TTestTSQLQuery.TestGeneratedRefreshSQLNoKey;
  455. begin
  456. with SQLDBConnector do
  457. begin
  458. TryDropIfExist('testdefval');
  459. ExecuteDirect('create table testdefval (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint pk_testdefval primary key(id))');
  460. if Transaction.Active then
  461. Transaction.Commit;
  462. end;
  463. FMyQ:=SQLDBConnector.Query;
  464. FMyQ.SQL.Text:='select * from testdefval';
  465. FMyQ.InsertSQL.Text:='insert into testdefval (id) values (:id)';
  466. FMyQ.Open;
  467. With FMyQ.FieldByName('id') do
  468. ProviderFlags:=ProviderFlags-[pfInKey];
  469. With FMyQ.FieldByName('a') do
  470. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  471. FMyQ.Insert;
  472. FMyQ.FieldByName('id').AsInteger:=1;
  473. FMyQ.Post;
  474. AssertException('Cannot refresh without primary key',EUpdateError,@DoApplyUpdates);
  475. end;
  476. Procedure TTestTSQLQuery.TestRefreshSQLMultipleRecords;
  477. begin
  478. with SQLDBConnector do
  479. begin
  480. TryDropIfExist('testdefval');
  481. ExecuteDirect('create table testdefval (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint pk_testdefval primary key(id))');
  482. if Transaction.Active then
  483. Transaction.Commit;
  484. ExecuteDirect('insert into testdefval (id) values (123)');
  485. if Transaction.Active then
  486. Transaction.Commit;
  487. end;
  488. FMyQ:=SQLDBConnector.Query;
  489. FMyQ.SQL.Text:='select * from testdefval';
  490. FMyQ.InsertSQL.Text:='insert into testdefval (id) values (:id)';
  491. FMyQ.RefreshSQL.Text:='select * from testdefval';
  492. FMyQ.Open;
  493. With FMyQ.FieldByName('id') do
  494. ProviderFlags:=ProviderFlags+[pfInKey];
  495. With FMyQ.FieldByName('a') do
  496. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  497. FMyQ.Insert;
  498. FMyQ.FieldByName('id').AsInteger:=1;
  499. FMyQ.Post;
  500. AssertException('Multiple records returned by RefreshSQL gives an error',EUpdateError,@DoApplyUpdates);
  501. end;
  502. Procedure TTestTSQLQuery.TestRefreshSQLNoRecords;
  503. begin
  504. with SQLDBConnector do
  505. begin
  506. TryDropIfExist('testdefval');
  507. ExecuteDirect('create table testdefval (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint pk_testdefval primary key(id))');
  508. if Transaction.Active then
  509. Transaction.Commit;
  510. ExecuteDirect('insert into testdefval (id) values (123)');
  511. if Transaction.Active then
  512. Transaction.Commit;
  513. end;
  514. FMyQ:=SQLDBConnector.Query;
  515. FMyQ.SQL.Text:='select * from testdefval';
  516. FMyQ.InsertSQL.Text:='insert into testdefval (id) values (:id)';
  517. FMyQ.RefreshSQL.Text:='select * from testdefval where 1=2';
  518. FMyQ.Open;
  519. With FMyQ.FieldByName('id') do
  520. ProviderFlags:=ProviderFlags+[pfInKey];
  521. With FMyQ.FieldByName('a') do
  522. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  523. FMyQ.Insert;
  524. FMyQ.FieldByName('id').AsInteger:=1;
  525. FMyQ.Post;
  526. AssertException('Multiple records returned by RefreshSQL gives an error',EUpdateError,@DoApplyUpdates);
  527. end;
  528. Procedure TTestTSQLQuery.TestFetchAutoInc;
  529. begin
  530. with SQLDBConnector do
  531. begin
  532. if not (sqLastInsertID in Connection.ConnOptions) then
  533. Ignore(STestNotApplicable);
  534. TryDropIfExist('testautoinc');
  535. // Syntax may vary. This works for MySQL.
  536. ExecuteDirect('create table testautoinc (id integer auto_increment, a varchar(5), constraint PK_AUTOINC primary key(id))');
  537. CommitDDL;
  538. end;
  539. FMyQ:=SQLDBConnector.Query;
  540. FMyQ.SQL.Text:='select * from testautoinc';
  541. FMyQ.Open;
  542. FMyQ.Insert;
  543. FMyQ.FieldByName('a').AsString:='b';
  544. FMyQ.Post;
  545. AssertTrue('ID field null after post',FMyQ.FieldByname('id').IsNull);
  546. FMyQ.ApplyUpdates(0);
  547. AssertTrue('ID field no longer null after applyupdates',Not FMyQ.FieldByname('id').IsNull);
  548. // Should be 1 after the table was created, but this is not guaranteed... So we just test positive values.
  549. AssertTrue('ID field has positive value',FMyQ.FieldByname('id').AsLargeInt>0);
  550. end;
  551. { TTestTSQLConnection }
  552. procedure TTestTSQLConnection.TestImplicitTransaction;
  553. Var
  554. T : TSQLTransaction;
  555. begin
  556. T:=TSQLTransaction.Create(Nil);
  557. try
  558. T.Options:=[stoUseImplicit];
  559. T.DataBase:=SQLDBConnector.Connection;
  560. finally
  561. T.Free;
  562. end;
  563. end;
  564. procedure TTestTSQLConnection.TestImplicitTransaction2;
  565. Var
  566. T : TSQLTransaction;
  567. begin
  568. T:=TSQLTransaction.Create(Nil);
  569. try
  570. T.Options:=[stoUseImplicit];
  571. SQLDBConnector.Connection.Transaction:=T;
  572. finally
  573. T.Free;
  574. end;
  575. end;
  576. procedure TTestTSQLConnection.SetImplicit;
  577. begin
  578. SQLDBConnector.Transaction.Options:=[stoUseImplicit];
  579. end;
  580. procedure TTestTSQLConnection.TestImplicitTransactionNotAssignable;
  581. begin
  582. AssertException('Cannot set toUseImplicit option if database does not allow it',EDatabaseError,@SetImplicit);
  583. AssertException('Cannot assign database to transaction with toUseImplicit, if database does not allow it',EDatabaseError,@TestImplicitTransaction);
  584. AssertException('Cannot assign transaction with toUseImplicit to database, if database does not allow it',EDatabaseError,@TestImplicitTransaction2);
  585. end;
  586. procedure TTestTSQLConnection.TestImplicitTransactionOK;
  587. var
  588. Q : TSQLQuery;
  589. T : TSQLTransaction;
  590. I, J : Integer;
  591. begin
  592. with SQLDBConnector do
  593. begin
  594. TryDropIfExist('testdiscon');
  595. ExecuteDirect('create table testdiscon (id integer not null, a varchar(10), constraint pk_testdiscon primary key(id))');
  596. if Transaction.Active then
  597. Transaction.Commit;
  598. end;
  599. SetImplicit;
  600. Q:=SQLDBConnector.Query;
  601. for I:=1 to 2 do
  602. begin
  603. Q.SQL.Text:=Format('INSERT INTO testdiscon values (%d,''%.6d'');',[i,i]);
  604. Q.Prepare;
  605. Q.ExecSQL;
  606. // We do not commit anything explicitly.
  607. end;
  608. Q:=Nil;
  609. T:=Nil;
  610. try
  611. T:=TSQLTransaction.Create(Nil);
  612. Q:=TSQLQuery.Create(Nil);
  613. Q.Transaction:=T;
  614. Q.Database:=SQLDBConnector.Connection;
  615. T.Database:=SQLDBConnector.Connection;
  616. Q.SQL.text:='SELECT COUNT(*) from testdiscon';
  617. Q.Open;
  618. AssertEquals('Records have been committed to database',2,Q.Fields[0].AsInteger);
  619. finally
  620. Q.Free;
  621. T.Free;
  622. end;
  623. end;
  624. procedure TTestTSQLConnection.TestUseImplicitTransaction;
  625. begin
  626. if (sqImplicitTransaction in SQLDBConnector.Connection.ConnOptions) then
  627. TestImplicitTransactionOK
  628. else
  629. TestImplicitTransactionNotAssignable;
  630. end;
  631. procedure TTestTSQLConnection.TryOpen;
  632. begin
  633. SQLDBConnector.Query.Open;
  634. end;
  635. procedure TTestTSQLConnection.TestUseExplicitTransaction;
  636. begin
  637. SQLDBConnector.Transaction.Active:=False;
  638. SQLDBConnector.Transaction.Options:=[stoExplicitStart];
  639. SQLDBConnector.Query.SQL.Text:='select * from FPDEV';
  640. AssertException('toExplicitStart raises exception on implicit start',EDatabaseError,@TryOpen)
  641. end;
  642. procedure TTestTSQLConnection.TestExplicitConnect;
  643. begin
  644. SQLDBConnector.Transaction.Active:=False;
  645. SQLDBConnector.Connection.Options:=[scoExplicitConnect];
  646. SQLDBConnector.Connection.Connected:=False;
  647. SQLDBConnector.Query.SQL.Text:='select * from FPDEV';
  648. AssertException('toExplicitStart raises exception on implicit start',EDatabaseError,@TryOpen)
  649. end;
  650. { TTestTSQLScript }
  651. procedure TTestTSQLScript.TestExecuteScript;
  652. var Ascript : TSQLScript;
  653. begin
  654. Ascript := TSQLScript.Create(nil);
  655. try
  656. with Ascript do
  657. begin
  658. DataBase := SQLDBConnector.Connection;
  659. Transaction := SQLDBConnector.Transaction;
  660. Script.Clear;
  661. Script.Append('create table FPDEV_A (id int);');
  662. Script.Append('create table FPDEV_B (id int);');
  663. ExecuteScript;
  664. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  665. SQLDBConnector.CommitDDL;
  666. end;
  667. finally
  668. AScript.Free;
  669. SQLDBConnector.ExecuteDirect('drop table FPDEV_A');
  670. SQLDBConnector.ExecuteDirect('drop table FPDEV_B');
  671. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  672. SQLDBConnector.CommitDDL;
  673. end;
  674. end;
  675. procedure TTestTSQLScript.TestScriptColon;
  676. // Bug 25334: TSQLScript incorrectly treats : in scripts as sqldb query parameter markers
  677. // Firebird-only test; can be extended for other dbs that use : in SQL
  678. var
  679. Ascript : TSQLScript;
  680. begin
  681. if not(SQLConnType in [interbase]) then Ignore(STestNotApplicable);
  682. Ascript := TSQLScript.Create(nil);
  683. try
  684. with Ascript do
  685. begin
  686. DataBase := SQLDBConnector.Connection;
  687. Transaction := SQLDBConnector.Transaction;
  688. Script.Clear;
  689. UseSetTerm := true;
  690. // Example procedure that selects table names
  691. Script.Append(
  692. 'SET TERM ^ ; '+LineEnding+
  693. 'CREATE PROCEDURE FPDEV_TESTCOLON '+LineEnding+
  694. 'RETURNS (tblname VARCHAR(31)) '+LineEnding+
  695. 'AS '+LineEnding+
  696. 'begin '+LineEnding+
  697. '/* Show tables. Note statement uses colon */ '+LineEnding+
  698. 'FOR '+LineEnding+
  699. ' SELECT RDB$RELATION_NAME '+LineEnding+
  700. ' FROM RDB$RELATIONS '+LineEnding+
  701. ' ORDER BY RDB$RELATION_NAME '+LineEnding+
  702. ' INTO :tblname '+LineEnding+
  703. 'DO '+LineEnding+
  704. ' SUSPEND; '+LineEnding+
  705. 'end^ '+LineEnding+
  706. 'SET TERM ; ^'
  707. );
  708. ExecuteScript;
  709. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  710. SQLDBConnector.CommitDDL;
  711. end;
  712. finally
  713. AScript.Free;
  714. SQLDBConnector.ExecuteDirect('DROP PROCEDURE FPDEV_TESTCOLON');
  715. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  716. SQLDBConnector.CommitDDL;
  717. end;
  718. end;
  719. procedure TTestTSQLScript.TestUseCommit;
  720. // E.g. Firebird needs explicit COMMIT sometimes, e.g. if mixing DDL and DML
  721. // statements in a script.
  722. // Probably same as bug 17829 Error executing SQL script
  723. const
  724. TestValue='Some text';
  725. var
  726. Ascript : TSQLScript;
  727. CheckQuery : TSQLQuery;
  728. begin
  729. Ascript := TSQLScript.Create(nil);
  730. try
  731. with Ascript do
  732. begin
  733. DataBase := SQLDBConnector.Connection;
  734. Transaction := SQLDBConnector.Transaction;
  735. Script.Clear;
  736. UseCommit:=true;
  737. // Example procedure that selects table names
  738. Script.Append('CREATE TABLE fpdev_scriptusecommit (logmessage VARCHAR(255));');
  739. Script.Append('COMMIT;'); //needed for table to show up
  740. Script.Append('INSERT INTO fpdev_scriptusecommit (logmessage) VALUES('''+TestValue+''');');
  741. Script.Append('COMMIT;');
  742. ExecuteScript;
  743. // This line should not run, as the commit above should have taken care of it:
  744. //SQLDBConnector.CommitDDL;
  745. // Test whether second line of script executed, just to be sure
  746. CheckQuery:=SQLDBConnector.Query;
  747. CheckQuery.SQL.Text:='SELECT logmessage FROM fpdev_scriptusecommit ';
  748. CheckQuery.Open;
  749. CheckEquals(TestValue, CheckQuery.Fields[0].AsString, 'Insert script line should have inserted '+TestValue);
  750. CheckQuery.Close;
  751. end;
  752. finally
  753. AScript.Free;
  754. SQLDBConnector.ExecuteDirect('DROP TABLE fpdev_scriptusecommit');
  755. SQLDBConnector.Transaction.Commit;
  756. end;
  757. end;
  758. { TSQLDBTestCase }
  759. function TSQLDBTestCase.GetDBC: TSQLDBConnector;
  760. begin
  761. Result:=DBConnector as TSQLDBConnector;
  762. end;
  763. procedure TSQLDBTestCase.SetUp;
  764. begin
  765. inherited SetUp;
  766. InitialiseDBConnector;
  767. DBConnector.StartTest(TestName);
  768. end;
  769. procedure TSQLDBTestCase.TearDown;
  770. begin
  771. DBConnector.StopTest(TestName);
  772. if assigned(DBConnector) then
  773. with SQLDBConnector do
  774. if Assigned(Transaction) and not (stoUseImplicit in Transaction.Options) then
  775. Transaction.Rollback;
  776. FreeDBConnector;
  777. inherited TearDown;
  778. end;
  779. initialization
  780. if uppercase(dbconnectorname)='SQL' then
  781. begin
  782. RegisterTest(TTestTSQLQuery);
  783. RegisterTest(TTestTSQLConnection);
  784. RegisterTest(TTestTSQLScript);
  785. end;
  786. end.