testsqldb.pas 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282
  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. FPrepareCount:Integer;
  25. procedure CreateAndFillIDField;
  26. procedure DoAfterPost(DataSet: TDataSet);
  27. Procedure DoApplyUpdates;
  28. procedure DoCount(Sender: TSQLConnection; EventType: TDBEventType; const Msg: String);
  29. Procedure TrySetQueryOptions;
  30. Procedure TrySetPacketRecords;
  31. Protected
  32. Procedure Setup; override;
  33. published
  34. procedure TestMasterDetail;
  35. procedure TestUpdateServerIndexDefs;
  36. Procedure TestKeepOpenOnCommit;
  37. Procedure TestKeepOpenOnCommitPacketRecords;
  38. Procedure TestCheckSettingsOnlyWhenInactive;
  39. Procedure TestAutoApplyUpdatesPost;
  40. Procedure TestAutoApplyUpdatesDelete;
  41. Procedure TestCheckRowsAffected;
  42. Procedure TestAutoCommit;
  43. Procedure TestGeneratedRefreshSQL;
  44. Procedure TestGeneratedRefreshSQL1Field;
  45. Procedure TestGeneratedRefreshSQLNoKey;
  46. Procedure TestRefreshSQL;
  47. Procedure TestRefreshSQLMultipleRecords;
  48. Procedure TestRefreshSQLNoRecords;
  49. Procedure TestFetchAutoInc;
  50. procedure TestSequence;
  51. procedure TestReturningInsert;
  52. procedure TestReturningUpdate;
  53. procedure TestMacros;
  54. Procedure TestPrepareCount;
  55. Procedure TestPrepareCount2;
  56. Procedure TestNullTypeParam;
  57. procedure TestChangeSQLCloseUnprepare;
  58. procedure TestChangeSQLCloseUnprepareDisabled;
  59. end;
  60. { TTestTSQLConnection }
  61. TTestTSQLConnection = class(TSQLDBTestCase)
  62. private
  63. procedure SetImplicit;
  64. procedure TestImplicitTransaction;
  65. procedure TestImplicitTransaction2;
  66. procedure TestImplicitTransactionNotAssignable;
  67. procedure TestImplicitTransactionOK;
  68. procedure TryOpen;
  69. procedure TestUnprepare(DoCommit : Boolean);
  70. published
  71. procedure TestUseImplicitTransaction;
  72. procedure TestUseExplicitTransaction;
  73. procedure TestExplicitConnect;
  74. procedure TestGetStatementInfo;
  75. procedure TestGetNextValue;
  76. Procedure TestCommitUnprepares;
  77. Procedure TestRollBackUnprepares;
  78. end;
  79. { TTestTSQLScript }
  80. TTestTSQLScript = class(TSQLDBTestCase)
  81. published
  82. procedure TestExecuteScript;
  83. procedure TestScriptColon; //bug 25334
  84. procedure TestUseCommit; //E.g. Firebird cannot use COMMIT RETAIN if mixing DDL and DML in a script
  85. end;
  86. implementation
  87. { TTestTSQLQuery }
  88. procedure TTestTSQLQuery.Setup;
  89. begin
  90. inherited Setup;
  91. FPrepareCount:=0;
  92. SQLDBConnector.Connection.Options:=[];
  93. end;
  94. procedure TTestTSQLQuery.TestMasterDetail;
  95. var MasterQuery, DetailQuery: TSQLQuery;
  96. MasterSource: TDataSource;
  97. begin
  98. with SQLDBConnector do
  99. try
  100. MasterQuery := GetNDataset(10) as TSQLQuery;
  101. MasterSource := TDatasource.Create(nil);
  102. MasterSource.DataSet := MasterQuery;
  103. DetailQuery := Query;
  104. DetailQuery.SQL.Text := 'select NAME from FPDEV where ID=:ID';
  105. DetailQuery.DataSource := MasterSource;
  106. MasterQuery.Open;
  107. DetailQuery.Open;
  108. CheckEquals('TestName1', DetailQuery.Fields[0].AsString);
  109. MasterQuery.MoveBy(3);
  110. CheckEquals('TestName4', DetailQuery.Fields[0].AsString);
  111. MasterQuery.Close;
  112. CheckTrue(DetailQuery.Active, 'Detail dataset should remain intact, when master dataset is closed');
  113. finally
  114. MasterSource.Free;
  115. end;
  116. end;
  117. procedure TTestTSQLQuery.TestUpdateServerIndexDefs;
  118. var Q: TSQLQuery;
  119. name1, name2, name3: string;
  120. begin
  121. // Test retrieval of information about indexes on unquoted and quoted table names
  122. // (tests also case-sensitivity for DB's that support case-sensitivity of quoted identifiers)
  123. // For ODBC Firebird/Interbase we must define primary key as named constraint and
  124. // in ODBC driver must be set: "quoted identifiers" and "sensitive identifier"
  125. // See also: TTestFieldTypes.TestUpdateIndexDefs
  126. with SQLDBConnector do
  127. begin
  128. // SQLite ignores case-sensitivity of quoted table names
  129. // MS SQL Server case-sensitivity of identifiers depends on the case-sensitivity of default collation of the database
  130. // MySQL case-sensitivity depends on case-sensitivity of server's file system
  131. if SQLServerType in [ssMSSQL,ssSQLite{$IFDEF WINDOWS},ssMySQL{$ENDIF}] then
  132. name1 := Connection.FieldNameQuoteChars[0]+'fpdev 2'+Connection.FieldNameQuoteChars[1]
  133. else
  134. name1 := 'FPDEV2';
  135. ExecuteDirect('create table '+name1+' (id integer not null, constraint PK_FPDEV21 primary key(id))');
  136. // same but quoted table name
  137. name2 := Connection.FieldNameQuoteChars[0]+'FPdev2'+Connection.FieldNameQuoteChars[1];
  138. ExecuteDirect('create table '+name2+' (ID2 integer not null, constraint PK_FPDEV22 primary key(ID2))');
  139. // embedded quote in table name
  140. if SQLServerType in [ssMySQL] then
  141. name3 := '`FPdev``2`'
  142. else
  143. name3 := Connection.FieldNameQuoteChars[0]+'FPdev""2'+Connection.FieldNameQuoteChars[1];
  144. ExecuteDirect('create table '+name3+' (Id3 integer not null, constraint PK_FPDEV23 primary key(Id3))');
  145. CommitDDL;
  146. end;
  147. try
  148. Q := SQLDBConnector.Query;
  149. Q.SQL.Text:='select * from '+name1;
  150. Q.Prepare;
  151. Q.ServerIndexDefs.Update;
  152. CheckEquals(1, Q.ServerIndexDefs.Count);
  153. Q.SQL.Text:='select * from '+name2;
  154. Q.Prepare;
  155. Q.ServerIndexDefs.Update;
  156. CheckEquals(1, Q.ServerIndexDefs.Count, '2.1');
  157. CheckTrue(CompareText('ID2', Q.ServerIndexDefs[0].Fields)=0, '2.2'+Q.ServerIndexDefs[0].Fields);
  158. CheckTrue(Q.ServerIndexDefs[0].Options=[ixPrimary,ixUnique], '2.3');
  159. Q.SQL.Text:='select * from '+name3;
  160. Q.Prepare;
  161. Q.ServerIndexDefs.Update;
  162. CheckEquals(1, Q.ServerIndexDefs.Count, '3.1');
  163. CheckTrue(CompareText('ID3', Q.ServerIndexDefs[0].Fields)=0, '3.2');
  164. CheckTrue(Q.ServerIndexDefs[0].Options=[ixPrimary,ixUnique], '3.3');
  165. finally
  166. Q.UnPrepare;
  167. with SQLDBConnector do
  168. begin
  169. ExecuteDirect('DROP TABLE '+name1);
  170. ExecuteDirect('DROP TABLE '+name2);
  171. ExecuteDirect('DROP TABLE '+name3);
  172. CommitDDL;
  173. end;
  174. end;
  175. end;
  176. procedure TTestTSQLQuery.TestKeepOpenOnCommit;
  177. var Q: TSQLQuery;
  178. I: Integer;
  179. begin
  180. // Test that for a SQL query with Options=sqoKeepOpenOnCommit, calling commit does not close the dataset.
  181. // Test also that an edit still works.
  182. with SQLDBConnector do
  183. begin
  184. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  185. Transaction.Commit;
  186. for I:=1 to 20 do
  187. ExecuteDirect(Format('INSERT INTO FPDEV2 values (%d,''%.6d'')',[i,i]));
  188. Transaction.Commit;
  189. Q := SQLDBConnector.Query;
  190. Q.SQL.Text:='select * from FPDEV2';
  191. Q.Options:=[sqoKeepOpenOnCommit,sqoRefreshUsingSelect];
  192. AssertEquals('PacketRecords forced to -1',-1,Q.PacketRecords);
  193. Q.Open;
  194. AssertEquals('Got all records',20,Q.RecordCount);
  195. Q.SQLTransaction.Commit;
  196. AssertTrue('Still open after transaction',Q.Active);
  197. // Now check editing
  198. Q.Locate('id',20,[]);
  199. Q.Edit;
  200. Q.FieldByName('a').AsString:='abc';
  201. Q.Post;
  202. AssertTrue('Have updates pending',Q.UpdateStatus=usModified);
  203. Q.ApplyUpdates;
  204. AssertTrue('Have no more updates pending',Q.UpdateStatus=usUnmodified);
  205. Q.Close;
  206. Q.SQL.Text:='select * from FPDEV2 where (id=20) and (a=''abc'')';
  207. Q.Open;
  208. AssertTrue('Have modified data record in database', not (Q.EOF AND Q.BOF));
  209. end;
  210. end;
  211. procedure TTestTSQLQuery.TrySetPacketRecords;
  212. begin
  213. FMyQ.PacketRecords:=10;
  214. end;
  215. procedure TTestTSQLQuery.TestKeepOpenOnCommitPacketRecords;
  216. begin
  217. with SQLDBConnector do
  218. begin
  219. FMyQ := SQLDBConnector.Query;
  220. FMyQ.Options:=[sqoKeepOpenOnCommit];
  221. AssertException('Cannot set PacketRecords when sqoKeepOpenOnCommit is active',EDatabaseError,@TrySetPacketRecords);
  222. end;
  223. end;
  224. procedure TTestTSQLQuery.TrySetQueryOptions;
  225. begin
  226. FMyQ.Options:=[sqoKeepOpenOnCommit];
  227. end;
  228. procedure TTestTSQLQuery.TestCheckSettingsOnlyWhenInactive;
  229. begin
  230. // Check that we can only set QueryOptions when the query is inactive.
  231. with SQLDBConnector do
  232. begin
  233. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  234. Transaction.Commit;
  235. ExecuteDirect(Format('INSERT INTO FPDEV2 values (%d,''%.6d'')',[1,1]));
  236. Transaction.Commit;
  237. FMyQ := SQLDBConnector.Query;
  238. FMyQ.SQL.Text:='select * from FPDEV2';
  239. FMyQ := SQLDBConnector.Query;
  240. FMyQ.Open;
  241. AssertException('Cannot set Options when query is active',EDatabaseError,@TrySetQueryOptions);
  242. end;
  243. end;
  244. procedure TTestTSQLQuery.DoAfterPost(DataSet: TDataSet);
  245. begin
  246. AssertTrue('Have modifications in after post',FMyq.UpdateStatus=usModified)
  247. end;
  248. procedure TTestTSQLQuery.TestAutoApplyUpdatesPost;
  249. var Q: TSQLQuery;
  250. I: Integer;
  251. begin
  252. // Test that if sqoAutoApplyUpdates is in QueryOptions, then POST automatically does an ApplyUpdates
  253. // Test also that POST afterpost event is backwards compatible.
  254. with SQLDBConnector do
  255. begin
  256. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  257. Transaction.COmmit;
  258. for I:=1 to 2 do
  259. ExecuteDirect(Format('INSERT INTO FPDEV2 values (%d,''%.6d'')',[i,i]));
  260. Transaction.COmmit;
  261. Q := SQLDBConnector.Query;
  262. FMyQ:=Q; // so th event handler can reach it.
  263. Q.SQL.Text:='select * from FPDEV2';
  264. Q.Options:=[sqoAutoApplyUpdates];
  265. // We must test that in AfterPost, the modification is still there, for backwards compatibilty
  266. Q.AfterPost:=@DoAfterPost;
  267. Q.Open;
  268. AssertEquals('Got all records',2,Q.RecordCount);
  269. // Now check editing
  270. Q.Locate('id',2,[]);
  271. Q.Edit;
  272. Q.FieldByName('a').AsString:='abc';
  273. Q.Post;
  274. AssertTrue('Have no more updates pending',Q.UpdateStatus=usUnmodified);
  275. Q.Close;
  276. Q.SQL.Text:='select * from FPDEV2 where (id=2) and (a=''abc'')';
  277. Q.Open;
  278. AssertTrue('Have modified data record in database',not (Q.EOF AND Q.BOF));
  279. end;
  280. end;
  281. procedure TTestTSQLQuery.TestAutoApplyUpdatesDelete;
  282. var Q: TSQLQuery;
  283. I: Integer;
  284. begin
  285. // Test that if sqoAutoApplyUpdates is in QueryOptions, then Delete automatically does an ApplyUpdates
  286. with SQLDBConnector do
  287. begin
  288. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  289. Transaction.COmmit;
  290. for I:=1 to 2 do
  291. ExecuteDirect(Format('INSERT INTO FPDEV2 values (%d,''%.6d'')',[i,i]));
  292. Transaction.COmmit;
  293. Q := SQLDBConnector.Query;
  294. FMyQ:=Q; // so th event handler can reach it.
  295. Q.SQL.Text:='select * from FPDEV2';
  296. Q.Options:=[sqoAutoApplyUpdates];
  297. // We must test that in AfterPost, the modification is still there, for backwards compatibilty
  298. Q.AfterPost:=@DoAfterPost;
  299. Q.Open;
  300. AssertEquals('Got all records',2,Q.RecordCount);
  301. // Now check editing
  302. Q.Locate('id',2,[]);
  303. Q.Delete;
  304. AssertTrue('Have no more updates pending',Q.UpdateStatus=usUnmodified);
  305. Q.Close;
  306. Q.SQL.Text:='select * from FPDEV2 where (id=2)';
  307. Q.Open;
  308. AssertTrue('Data record is deleted in database', (Q.EOF AND Q.BOF));
  309. end;
  310. end;
  311. procedure TTestTSQLQuery.DoApplyUpdates;
  312. begin
  313. FMyQ.ApplyUpdates();
  314. end;
  315. procedure TTestTSQLQuery.DoCount(Sender: TSQLConnection; EventType: TDBEventType; const Msg: String);
  316. begin
  317. If (EventType=detPrepare) then
  318. Inc(FPrepareCount);
  319. end;
  320. procedure TTestTSQLQuery.TestCheckRowsAffected;
  321. var Q: TSQLQuery;
  322. I: Integer;
  323. begin
  324. // Test that if sqoAutoApplyUpdates is in QueryOptions, then Delete automatically does an ApplyUpdates
  325. with SQLDBConnector do
  326. begin
  327. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  328. Transaction.COmmit;
  329. for I:=1 to 2 do
  330. ExecuteDirect(Format('INSERT INTO FPDEV2 values (%d,''%.6d'')',[i,i]));
  331. Transaction.COmmit;
  332. SQLDBConnector.Connection.Options:=[scoApplyUpdatesChecksRowsAffected];
  333. Q := SQLDBConnector.Query;
  334. Q.SQL.Text:='select * from FPDEV2';
  335. Q.DeleteSQL.Text:='delete from FPDEV2';
  336. Q.Open;
  337. AssertEquals('Got all records',2,Q.RecordCount);
  338. // Now check editing
  339. Q.Delete;
  340. FMyQ:=Q;
  341. AssertException('RowsAffected > 1 raises exception',EUpdateError,@DoApplyUpdates);
  342. end;
  343. end;
  344. procedure TTestTSQLQuery.TestAutoCommit;
  345. var
  346. I : Integer;
  347. begin
  348. with SQLDBConnector do
  349. begin
  350. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  351. if Transaction.Active then
  352. Transaction.Commit;
  353. Query.Options:=[sqoAutoCommit];
  354. for I:=1 to 2 do
  355. begin
  356. Query.SQL.Text:=Format('INSERT INTO FPDEV2 values (%d,''%.6d'');',[i,i]);
  357. Query.Prepare;
  358. Query.ExecSQL;
  359. // We do not commit anything explicitly.
  360. end;
  361. AssertFalse('Transaction is still active after expected auto commit', Transaction.Active);
  362. Connection.Close;
  363. Connection.Open;
  364. Query.SQL.Text:='SELECT COUNT(*) from FPDEV2';
  365. Query.Open;
  366. AssertEquals('Records haven''t been committed to database', 2, Query.Fields[0].AsInteger);
  367. end;
  368. end;
  369. procedure TTestTSQLQuery.TestGeneratedRefreshSQL;
  370. var
  371. Q: TSQLQuery;
  372. begin
  373. with SQLDBConnector do
  374. begin
  375. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  376. if Transaction.Active then
  377. Transaction.Commit;
  378. end;
  379. Q:=SQLDBConnector.Query;
  380. Q.SQL.Text:='select * from FPDEV2';
  381. Q.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  382. Q.Options:=Q.Options+[sqoRefreshUsingSelect];
  383. Q.Open;
  384. With Q.FieldByName('id') do
  385. ProviderFlags:=ProviderFlags+[pfInKey];
  386. With Q.FieldByName('a') do
  387. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  388. With Q.FieldByName('b') do
  389. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  390. Q.Insert;
  391. Q.FieldByName('id').AsInteger:=1;
  392. Q.Post;
  393. AssertTrue('Field value has not been fetched after post',Q.FieldByName('a').IsNull);
  394. Q.ApplyUpdates(0);
  395. AssertEquals('Still on correct field',1,Q.FieldByName('id').AsInteger);
  396. AssertEquals('Field value has been fetched from the database ','abcde',Q.FieldByName('a').AsString);
  397. AssertEquals('Field value has been fetched from the database ','fgh',Q.FieldByName('b').AsString);
  398. end;
  399. procedure TTestTSQLQuery.TestGeneratedRefreshSQL1Field;
  400. var
  401. Q: TSQLQuery;
  402. begin
  403. with SQLDBConnector do
  404. begin
  405. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  406. if Transaction.Active then
  407. Transaction.Commit;
  408. end;
  409. Q:=SQLDBConnector.Query;
  410. Q.SQL.Text:='select * from FPDEV2';
  411. Q.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  412. Q.Options:=Q.Options+[sqoRefreshUsingSelect];
  413. Q.Open;
  414. With Q.FieldByName('id') do
  415. ProviderFlags:=ProviderFlags+[pfInKey];
  416. With Q.FieldByName('a') do
  417. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  418. Q.Insert;
  419. Q.FieldByName('id').AsInteger:=1;
  420. Q.Post;
  421. AssertTrue('Field value has not been fetched after post',Q.FieldByName('a').IsNull);
  422. Q.ApplyUpdates(0);
  423. AssertEquals('Still on correct field',1,Q.FieldByName('id').AsInteger);
  424. AssertEquals('Field value a has been fetched from the database ','abcde',Q.FieldByName('a').AsString);
  425. AssertEquals('Field value b has NOT been fetched from the database ','',Q.FieldByName('b').AsString);
  426. end;
  427. procedure TTestTSQLQuery.TestGeneratedRefreshSQLNoKey;
  428. begin
  429. with SQLDBConnector do
  430. begin
  431. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  432. if Transaction.Active then
  433. Transaction.Commit;
  434. end;
  435. FMyQ:=SQLDBConnector.Query;
  436. FMyQ.SQL.Text:='select * from FPDEV2';
  437. FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  438. FMyQ.Options:=FMyQ.Options+[sqoRefreshUsingSelect];
  439. FMyQ.Open;
  440. With FMyQ.FieldByName('id') do
  441. ProviderFlags:=ProviderFlags-[pfInKey];
  442. With FMyQ.FieldByName('a') do
  443. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  444. FMyQ.Insert;
  445. FMyQ.FieldByName('id').AsInteger:=1;
  446. FMyQ.Post;
  447. AssertException('Cannot refresh without primary key',EUpdateError,@DoApplyUpdates);
  448. end;
  449. procedure TTestTSQLQuery.TestRefreshSQL;
  450. var
  451. Q: TSQLQuery;
  452. begin
  453. with SQLDBConnector do
  454. begin
  455. ExecuteDirect('create table FPDEV2 (id integer not null primary key, a varchar(5) default ''abcde'', b integer default 1)');
  456. if Transaction.Active then
  457. Transaction.Commit;
  458. end;
  459. Q:=SQLDBConnector.Query;
  460. Q.SQL.Text:='select * from FPDEV2';
  461. Q.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  462. Q.RefreshSQL.Text:='SELECT a,b FROM FPDEV2 WHERE (id=:id)';
  463. Q.Open;
  464. Q.Insert; // #1 record
  465. Q.FieldByName('id').AsInteger:=1;
  466. Q.Post;
  467. Q.Append; // #2 record
  468. Q.FieldByName('id').AsInteger:=2;
  469. Q.Post;
  470. AssertTrue('Field value has not been fetched after Post', Q.FieldByName('a').IsNull);
  471. Q.ApplyUpdates(0);
  472. // #2 record:
  473. AssertEquals('Still on correct field', 2, Q.FieldByName('id').AsInteger);
  474. AssertEquals('Field value has been fetched from the database', 'abcde', Q.FieldByName('a').AsString);
  475. AssertEquals('Field value has been fetched from the database', 1, Q.FieldByName('b').AsInteger);
  476. Q.Prior;
  477. // #1 record:
  478. AssertEquals('Still on correct field', 1, Q.FieldByName('id').AsInteger);
  479. AssertEquals('Field value has been fetched from the database', 'abcde', Q.FieldByName('a').AsString);
  480. AssertEquals('Field value has been fetched from the database', 1, Q.FieldByName('b').AsInteger);
  481. end;
  482. procedure TTestTSQLQuery.TestRefreshSQLMultipleRecords;
  483. begin
  484. with SQLDBConnector do
  485. begin
  486. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  487. if Transaction.Active then
  488. Transaction.Commit;
  489. ExecuteDirect('insert into FPDEV2 (id) values (123)');
  490. if Transaction.Active then
  491. Transaction.Commit;
  492. end;
  493. FMyQ:=SQLDBConnector.Query;
  494. FMyQ.SQL.Text:='select * from FPDEV2';
  495. FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  496. FMyQ.RefreshSQL.Text:='select * from FPDEV2';
  497. FMyQ.Open;
  498. With FMyQ.FieldByName('id') do
  499. ProviderFlags:=ProviderFlags+[pfInKey];
  500. With FMyQ.FieldByName('a') do
  501. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  502. FMyQ.Insert;
  503. FMyQ.FieldByName('id').AsInteger:=1;
  504. FMyQ.Post;
  505. AssertException('Multiple records returned by RefreshSQL gives an error',EUpdateError,@DoApplyUpdates);
  506. end;
  507. procedure TTestTSQLQuery.TestRefreshSQLNoRecords;
  508. begin
  509. with SQLDBConnector do
  510. begin
  511. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  512. if Transaction.Active then
  513. Transaction.Commit;
  514. ExecuteDirect('insert into FPDEV2 (id) values (123)');
  515. if Transaction.Active then
  516. Transaction.Commit;
  517. end;
  518. FMyQ:=SQLDBConnector.Query;
  519. FMyQ.SQL.Text:='select * from FPDEV2';
  520. FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  521. FMyQ.RefreshSQL.Text:='select * from FPDEV2 where 1=2';
  522. FMyQ.Open;
  523. With FMyQ.FieldByName('id') do
  524. ProviderFlags:=ProviderFlags+[pfInKey];
  525. With FMyQ.FieldByName('a') do
  526. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert,pfRefreshOnUpdate];
  527. FMyQ.Insert;
  528. FMyQ.FieldByName('id').AsInteger:=1;
  529. FMyQ.Post;
  530. AssertException('No records returned by RefreshSQL gives an error',EUpdateError,@DoApplyUpdates);
  531. end;
  532. procedure TTestTSQLQuery.TestFetchAutoInc;
  533. var datatype: string;
  534. id: largeint;
  535. begin
  536. with SQLDBConnector do
  537. begin
  538. case SQLServerType of
  539. ssMySQL:
  540. datatype := 'integer auto_increment';
  541. ssMSSQL, ssSybase:
  542. datatype := 'integer identity';
  543. ssSQLite:
  544. datatype := 'integer';
  545. else
  546. Ignore(STestNotApplicable);
  547. end;
  548. ExecuteDirect('create table FPDEV2 (id '+datatype+' primary key, f varchar(5))');
  549. CommitDDL;
  550. end;
  551. with SQLDBConnector.Query do
  552. begin
  553. SQL.Text:='select * from FPDEV2';
  554. Open;
  555. Insert;
  556. FieldByName('f').AsString:='a';
  557. Post; // #1 record
  558. Append;
  559. FieldByName('f').AsString:='b';
  560. Post; // #2 record
  561. AssertTrue('ID field is not null after Post', FieldByName('id').IsNull);
  562. First; // #1 record
  563. ApplyUpdates(0);
  564. AssertTrue('ID field is still null after ApplyUpdates', Not FieldByName('id').IsNull);
  565. // Should be 1 after the table was created, but this is not guaranteed... So we just test positive values.
  566. id := FieldByName('id').AsLargeInt;
  567. AssertTrue('ID field has not positive value', id>0);
  568. Next; // #2 record
  569. AssertTrue('Next ID value is not greater than previous', FieldByName('id').AsLargeInt>id);
  570. end;
  571. end;
  572. procedure TTestTSQLQuery.TestSequence;
  573. var SequenceNames : TStringList;
  574. begin
  575. case SQLServerType of
  576. ssFirebird:
  577. SQLDBConnector.ExecuteDirect('create sequence FPDEV_SEQ1');
  578. ssMSSQL, ssOracle, ssPostgreSQL:
  579. SQLDBConnector.ExecuteDirect('create sequence FPDEV_SEQ1 MINVALUE 1');
  580. else
  581. Ignore(STestNotApplicable);
  582. end;
  583. SQLDBConnector.ExecuteDirect('create table FPDEV2 (id integer)');
  584. SQLDBConnector.CommitDDL;
  585. with SQLDBConnector.Query do
  586. begin
  587. SQL.Text := 'select * from FPDEV2';
  588. Sequence.FieldName:='id';
  589. Sequence.SequenceName:='FPDEV_SEQ1';
  590. Open;
  591. // default is get next value on new record
  592. Append;
  593. AssertEquals(1, FieldByName('id').AsInteger);
  594. Sequence.ApplyEvent:=saeOnPost;
  595. Append;
  596. AssertTrue('Field ID must be null after Append', FieldByName('id').IsNull);
  597. Post;
  598. AssertEquals(2, FieldByName('id').AsInteger);
  599. end;
  600. // test GetSequenceNames
  601. SequenceNames := TStringList.Create;
  602. try
  603. SQLDBConnector.Connection.GetSequenceNames(SequenceNames);
  604. AssertTrue(SequenceNames.IndexOf('FPDEV_SEQ1') >= 0);
  605. finally
  606. SequenceNames.Free;
  607. end;
  608. SQLDBConnector.ExecuteDirect('drop sequence FPDEV_SEQ1');
  609. SQLDBConnector.CommitDDL;
  610. end;
  611. procedure TTestTSQLQuery.TestReturningInsert;
  612. begin
  613. with SQLDBConnector do
  614. begin
  615. if not (sqSupportReturning in Connection.ConnOptions) then
  616. Ignore(STestNotApplicable);
  617. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  618. if Transaction.Active then
  619. Transaction.Commit;
  620. ExecuteDirect('insert into FPDEV2 (id) values (123)');
  621. if Transaction.Active then
  622. Transaction.Commit;
  623. end;
  624. FMyQ:=SQLDBConnector.Query;
  625. FMyQ.SQL.Text:='select * from FPDEV2';
  626. // FMyQ.InsertSQL.Text:='insert into FPDEV2 (id) values (:id)';
  627. FMyQ.Open;
  628. With FMyQ.FieldByName('id') do
  629. ProviderFlags:=ProviderFlags+[pfInKey];
  630. With FMyQ.FieldByName('a') do
  631. ProviderFlags:=ProviderFlags+[pfRefreshOnInsert];
  632. With FMyQ.FieldByName('b') do
  633. ProviderFlags:=[];
  634. FMyQ.Insert;
  635. FMyQ.FieldByName('id').AsInteger:=1;
  636. FMyQ.Post;
  637. FMyQ.ApplyUpdates;
  638. AssertEquals('a updated','abcde',FMyQ.FieldByName('a').AsString);
  639. AssertEquals('b not updated','',FMyQ.FieldByName('b').AsString);
  640. end;
  641. procedure TTestTSQLQuery.TestReturningUpdate;
  642. begin
  643. with SQLDBConnector do
  644. begin
  645. if not (sqSupportReturning in Connection.ConnOptions) then
  646. Ignore(STestNotApplicable);
  647. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10) default ''abcde'', b varchar(5) default ''fgh'', constraint PK_FPDEV2 primary key(id))');
  648. CommitDDL;
  649. ExecuteDirect('insert into FPDEV2 (id) values (1)');
  650. ExecuteDirect('insert into FPDEV2 (id) values (2)');
  651. end;
  652. FMyQ:=SQLDBConnector.Query;
  653. FMyQ.SQL.Text:='select * from FPDEV2';
  654. FMyQ.Open;
  655. With FMyQ.FieldByName('id') do
  656. ProviderFlags:=ProviderFlags+[pfInKey];
  657. With FMyQ.FieldByName('b') do
  658. ProviderFlags:=[pfRefreshOnUpdate]; // Do not update, just fetch new value
  659. SQLDBConnector.ExecuteDirect('update FPDEV2 set b=''b1'' where id=1');
  660. SQLDBConnector.ExecuteDirect('update FPDEV2 set b=''b2'' where id=2');
  661. FMyQ.Edit;
  662. FMyQ.FieldByName('a').AsString:='a1';
  663. FMyQ.Post; // #1 record
  664. FMyQ.Next;
  665. FMyQ.Edit;
  666. FMyQ.FieldByName('a').AsString:='a2';
  667. FMyQ.Post; // #2 record
  668. FMyQ.ApplyUpdates;
  669. FMyQ.First;
  670. AssertEquals('#1.a updated', 'a1', FMyQ.FieldByName('a').AsString);
  671. AssertEquals('#1.b updated', 'b1', FMyQ.FieldByName('b').AsString);
  672. FMyQ.Next;
  673. AssertEquals('#2.a updated', 'a2', FMyQ.FieldByName('a').AsString);
  674. AssertEquals('#2.b updated', 'b2', FMyQ.FieldByName('b').AsString);
  675. end;
  676. procedure TTestTSQLQuery.TestMacros;
  677. begin
  678. with SQLDBConnector do
  679. begin
  680. ExecuteDirect('create table FPDEV2 (id integer not null, constraint PK_FPDEV2 primary key(id))');
  681. CommitDDL;
  682. ExecuteDirect('insert into FPDEV2 (id) values (1)');
  683. ExecuteDirect('insert into FPDEV2 (id) values (2)');
  684. end;
  685. With SQLDBConnector.Query do
  686. begin
  687. SQL.Text:='Select ID from FPDEV2 '+
  688. '%WHERE_CL' +sLineBreak+
  689. '%ORDER_CL' +sLineBreak;
  690. MacroCheck:=true;
  691. MacroByName('WHERE_CL').AsString:='where 1=1';
  692. MacroByName('ORDER_CL').AsString:='order by 1';
  693. Open;
  694. AssertEquals('Correct SQL executed, macros substituted: ',1,Fields[0].AsInteger);
  695. Close;
  696. MacroByName('ORDER_CL').AsString := 'Order by 1 DESC';
  697. Open;
  698. AssertEquals('Correct SQL executed, macro value changed: ',2,Fields[0].AsInteger);
  699. end;
  700. end;
  701. procedure TTestTSQLQuery.CreateAndFillIDField;
  702. Var
  703. I : Integer;
  704. begin
  705. with SQLDBConnector do
  706. begin
  707. TryDropIfExist('FPDEV2');
  708. ExecuteDirect('create table FPDEV2 (id integer not null, constraint PK_FPDEV2 primary key(id))');
  709. CommitDDL;
  710. for I:=1 to 10 do
  711. ExecuteDirect('insert into FPDEV2 (id) values ('+IntToStr(I)+')');
  712. Connection.OnLog:=@DoCount;
  713. Connection.LogEvents:=[detPrepare];
  714. end;
  715. end;
  716. procedure TTestTSQLQuery.TestPrepareCount;
  717. begin
  718. CreateAndFillIDField;
  719. try
  720. With SQLDBConnector.Query do
  721. begin
  722. UsePrimaryKeyAsKey:=False; // Disable server index defs etc
  723. SQL.Text:='Select ID from FPDEV2 where (ID>=:ID) order by ID';
  724. ParamByname('ID').AsInteger:=1;
  725. AssertFalse('Not Prepared',SQLDBConnector.Query.Prepared);
  726. Open;
  727. AssertEquals('Correct record count param 1',10,RecordCount);
  728. AssertEquals('Correct SQL executed, correct parameter: ',1,Fields[0].AsInteger);
  729. Close;
  730. AssertFalse('Still not prepared',SQLDBConnector.Query.Prepared);
  731. ParamByname('ID').AsInteger:=2;
  732. Open;
  733. AssertEquals('Correct record count param 2',9,RecordCount);
  734. AssertEquals('Correct SQL executed, macro value changed: ',2,Fields[0].AsInteger);
  735. Close;
  736. AssertFalse('Still not prepared',SQLDBConnector.Query.Prepared);
  737. end;
  738. AssertEquals('Prepare called only once ',2,FPrepareCount);
  739. finally
  740. SQLDBConnector.Connection.OnLog:=Nil;
  741. end;
  742. end;
  743. procedure TTestTSQLQuery.TestPrepareCount2;
  744. begin
  745. CreateAndFillIDField;
  746. try
  747. With SQLDBConnector.Query do
  748. begin
  749. UsePrimaryKeyAsKey:=False; // Disable server index defs etc
  750. SQL.Text:='Select ID from FPDEV2 where (ID>=:ID) order by ID';
  751. ParamByname('ID').AsInteger:=1;
  752. Prepare;
  753. AssertTrue('Prepared',SQLDBConnector.Query.Prepared);
  754. Open;
  755. AssertEquals('Correct record count param 1',10,RecordCount);
  756. AssertEquals('Correct SQL executed, correct parameter: ',1,Fields[0].AsInteger);
  757. Close;
  758. AssertTrue('Still prepared',SQLDBConnector.Query.Prepared);
  759. ParamByname('ID').AsInteger:=2;
  760. Open;
  761. AssertEquals('Correct record count param 2',9,RecordCount);
  762. AssertEquals('Correct SQL executed, macro value changed: ',2,Fields[0].AsInteger);
  763. end;
  764. AssertEquals('Prepare called only once ',1,FPrepareCount);
  765. finally
  766. SQLDBConnector.Connection.OnLog:=Nil;
  767. end;
  768. end;
  769. procedure TTestTSQLQuery.TestNullTypeParam;
  770. begin
  771. if not (SQLServerType in [ssSQLite, ssFirebird]) then
  772. Ignore(STestNotApplicable);
  773. CreateAndFillIDField;
  774. try
  775. With SQLDBConnector.Query do
  776. begin
  777. UsePrimaryKeyAsKey:=False; // Disable server index defs etc
  778. SQL.Text:='Select ID from FPDEV2 where (:ID IS NULL or ID = :ID)';
  779. Open;
  780. AssertEquals('Correct record count param NULL',10,RecordCount);
  781. Close;
  782. ParamByname('ID').AsInteger:=1;
  783. Open;
  784. AssertEquals('Correct record count param 1',1,RecordCount);
  785. AssertEquals('Correct field value: ',1,Fields[0].AsInteger);
  786. Close;
  787. end;
  788. finally
  789. SQLDBConnector.Connection.OnLog:=Nil;
  790. end;
  791. end;
  792. procedure TTestTSQLQuery.TestChangeSQLCloseUnprepare;
  793. begin
  794. with SQLDBConnector.GetNDataset(10) as TSQLQuery do
  795. begin
  796. Open;
  797. AssertTrue('Prepared after open', Prepared);
  798. SQL.Text := 'SELECT * FROM FPDEV WHERE ID<0';
  799. // statement must be unprepared when SQL is changed
  800. AssertFalse('Prepared after SQL changed', Prepared);
  801. // dataset remained active in FPC <= 3.2.2
  802. AssertFalse('Active after SQL changed', Active);
  803. SQL.Text := 'UPDATE FPDEV SET NAME=''Test'' WHERE ID>100';
  804. ExecSQL;
  805. end;
  806. end;
  807. procedure TTestTSQLQuery.TestChangeSQLCloseUnprepareDisabled;
  808. begin
  809. with SQLDBConnector.GetNDataset(10) as TSQLQuery do
  810. begin
  811. OPtions:=OPtions+[sqoNoCloseOnSQLChange];
  812. Open;
  813. AssertTrue('Prepared after open', Prepared);
  814. SQL.Text := 'SELECT * FROM FPDEV WHERE ID<0';
  815. // statement must be unprepared when SQL is changed
  816. AssertFalse('Prepared after SQL changed', Prepared);
  817. // dataset remained active in FPC <= 3.2.2
  818. AssertTrue('Active after SQL changed', Active);
  819. Close;
  820. SQL.Text := 'UPDATE FPDEV SET NAME=''Test'' WHERE ID>100';
  821. ExecSQL;
  822. end;
  823. end;
  824. { TTestTSQLConnection }
  825. procedure TTestTSQLConnection.TestImplicitTransaction;
  826. Var
  827. T : TSQLTransaction;
  828. begin
  829. T:=TSQLTransaction.Create(Nil);
  830. try
  831. T.Options:=[stoUseImplicit];
  832. T.DataBase:=SQLDBConnector.Connection;
  833. finally
  834. T.Free;
  835. end;
  836. end;
  837. procedure TTestTSQLConnection.TestImplicitTransaction2;
  838. Var
  839. T : TSQLTransaction;
  840. begin
  841. T:=TSQLTransaction.Create(Nil);
  842. try
  843. T.Options:=[stoUseImplicit];
  844. SQLDBConnector.Connection.Transaction:=T;
  845. finally
  846. T.Free;
  847. end;
  848. end;
  849. procedure TTestTSQLConnection.SetImplicit;
  850. begin
  851. SQLDBConnector.Transaction.Options:=[stoUseImplicit];
  852. end;
  853. procedure TTestTSQLConnection.TestImplicitTransactionNotAssignable;
  854. begin
  855. AssertException('Cannot set toUseImplicit option if database does not allow it',EDatabaseError,@SetImplicit);
  856. AssertException('Cannot assign database to transaction with toUseImplicit, if database does not allow it',EDatabaseError,@TestImplicitTransaction);
  857. AssertException('Cannot assign transaction with toUseImplicit to database, if database does not allow it',EDatabaseError,@TestImplicitTransaction2);
  858. end;
  859. procedure TTestTSQLConnection.TestImplicitTransactionOK;
  860. var
  861. Q : TSQLQuery;
  862. T : TSQLTransaction;
  863. I : Integer;
  864. begin
  865. with SQLDBConnector do
  866. begin
  867. ExecuteDirect('create table FPDEV2 (id integer not null, a varchar(10), constraint PK_FPDEV2 primary key(id))');
  868. if Transaction.Active then
  869. Transaction.Commit;
  870. end;
  871. SetImplicit;
  872. Q:=SQLDBConnector.Query;
  873. for I:=1 to 2 do
  874. begin
  875. Q.SQL.Text:=Format('INSERT INTO FPDEV2 values (%d,''%.6d'');',[i,i]);
  876. Q.Prepare;
  877. Q.ExecSQL;
  878. // We do not commit anything explicitly.
  879. end;
  880. Q:=Nil;
  881. T:=Nil;
  882. try
  883. T:=TSQLTransaction.Create(Nil);
  884. Q:=TSQLQuery.Create(Nil);
  885. Q.Transaction:=T;
  886. Q.Database:=SQLDBConnector.Connection;
  887. T.Database:=SQLDBConnector.Connection;
  888. Q.SQL.text:='SELECT COUNT(*) from FPDEV2';
  889. Q.Open;
  890. AssertEquals('Records have been committed to database',2,Q.Fields[0].AsInteger);
  891. finally
  892. Q.Free;
  893. T.Free;
  894. end;
  895. end;
  896. procedure TTestTSQLConnection.TestUseImplicitTransaction;
  897. begin
  898. if (sqImplicitTransaction in SQLDBConnector.Connection.ConnOptions) then
  899. TestImplicitTransactionOK
  900. else
  901. TestImplicitTransactionNotAssignable;
  902. end;
  903. procedure TTestTSQLConnection.TryOpen;
  904. begin
  905. SQLDBConnector.Query.Open;
  906. end;
  907. procedure TTestTSQLConnection.TestUnprepare(DoCommit: Boolean);
  908. Var
  909. Q1,Q2 : TSQLQuery;
  910. S1,S2 : TSQLStatement;
  911. PrepState : Boolean;
  912. begin
  913. S1:=Nil;
  914. S2:=Nil;
  915. Q2:=Nil;
  916. try
  917. // Only prepared, not open
  918. Q1:=TSQLQuery.Create(Nil);
  919. Q1.DataBase:=SQLDBConnector.Connection;
  920. Q1.Transaction:=SQLDBConnector.Transaction;
  921. Q1.SQL.text:='SELECT COUNT(*) from FPDEV where (ID<:MaxID)';
  922. Q1.Prepare;
  923. // Explicitly prepared and opened
  924. Q2:=TSQLQuery.Create(Nil);
  925. Q2.DataBase:=SQLDBConnector.Connection;
  926. Q2.Transaction:=SQLDBConnector.Transaction;
  927. Q2.SQL.text:='SELECT COUNT(*) from FPDEV where (ID>:MinID)';
  928. Q2.Prepare;
  929. Q2.Open;
  930. // A prepared statement;
  931. S1:=TSQLStatement.Create(Nil);
  932. S1.DataBase:=SQLDBConnector.Connection;
  933. S1.Transaction:=SQLDBConnector.Transaction;
  934. S1.SQL.Text:='update fpdev set id=id+1 where (id<:MaxID);';
  935. S1.Prepare;
  936. // A prepared and exected statement;
  937. S2:=TSQLStatement.Create(Nil);
  938. S2.DataBase:=SQLDBConnector.Connection;
  939. S2.Transaction:=SQLDBConnector.Transaction;
  940. S2.SQL.Text:='update fpdev set id=id+1 where (id<:MaxID);';
  941. S2.Prepare;
  942. S2.Execute;
  943. if DoCommit then
  944. begin
  945. SQLDBConnector.Transaction.Commit;
  946. PrepState:=Not (sqCommitEndsPrepared in SQLDBConnector.Connection.ConnOptions);
  947. end
  948. else
  949. begin
  950. SQLDBConnector.Transaction.RollBack;
  951. PrepState:=Not (sqRollbackEndsPrepared in SQLDBConnector.Connection.ConnOptions);
  952. end;
  953. AssertEquals('Q1 prepared state',PrepState,Q1.Prepared);
  954. AssertEquals('Q2 prepared state',PrepState,Q2.Prepared);
  955. AssertEquals('S prepared state',PrepState,S1.Prepared);
  956. AssertEquals('S prepared state',PrepState,S2.Prepared);
  957. finally
  958. Q1.Free;
  959. Q2.Free;
  960. end;
  961. end;
  962. procedure TTestTSQLConnection.TestUseExplicitTransaction;
  963. begin
  964. SQLDBConnector.Transaction.Active:=False;
  965. SQLDBConnector.Transaction.Options:=[stoExplicitStart];
  966. SQLDBConnector.Query.SQL.Text:='select * from FPDEV';
  967. AssertException('toExplicitStart raises exception on implicit start',EDatabaseError,@TryOpen)
  968. end;
  969. procedure TTestTSQLConnection.TestExplicitConnect;
  970. begin
  971. SQLDBConnector.Transaction.Active:=False;
  972. SQLDBConnector.Connection.Options:=[scoExplicitConnect];
  973. SQLDBConnector.Connection.Connected:=False;
  974. SQLDBConnector.Query.SQL.Text:='select * from FPDEV';
  975. AssertException('toExplicitStart raises exception on implicit start',EDatabaseError,@TryOpen)
  976. end;
  977. procedure TTestTSQLConnection.TestGetStatementInfo;
  978. var StmtInfo: TSQLStatementInfo;
  979. begin
  980. // single table
  981. StmtInfo := SQLDBConnector.Connection.GetStatementInfo('SELECT * FROM tab1');
  982. AssertEquals('StatementType', ord(stSELECT), ord(StmtInfo.StatementType));
  983. AssertEquals('TableName', 'tab1', StmtInfo.TableName);
  984. AssertEquals('Updateable', True, StmtInfo.Updateable);
  985. StmtInfo := SQLDBConnector.Connection.GetStatementInfo('SELECT * FROM tab2 WHERE col1=1');
  986. AssertEquals('TableName', 'tab2', StmtInfo.TableName);
  987. AssertEquals('Updateable', True, StmtInfo.Updateable);
  988. // single table with schema
  989. StmtInfo := SQLDBConnector.Connection.GetStatementInfo('SELECT * FROM dbo.tab2 WHERE col1=1');
  990. AssertEquals('TableName', 'dbo.tab2', StmtInfo.TableName);
  991. AssertEquals('Updateable', True, StmtInfo.Updateable);
  992. // single table with quoted schema
  993. StmtInfo := SQLDBConnector.Connection.GetStatementInfo('SELECT * FROM "dbo".tab2 WHERE col1=1');
  994. AssertEquals('TableName', '"dbo".tab2', StmtInfo.TableName);
  995. AssertEquals('Updateable', True, StmtInfo.Updateable);
  996. StmtInfo := SQLDBConnector.Connection.GetStatementInfo('SELECT * FROM "dbo"."tab2" WHERE col1=1');
  997. AssertEquals('TableName', '"dbo"."tab2"', StmtInfo.TableName);
  998. AssertEquals('Updateable', True, StmtInfo.Updateable);
  999. // multiple tables
  1000. StmtInfo := SQLDBConnector.Connection.GetStatementInfo('SELECT * FROM tab3,tab4 WHERE col1=1');
  1001. AssertEquals('TableName', '', StmtInfo.TableName);
  1002. AssertEquals('Updateable', False, StmtInfo.Updateable);
  1003. // function
  1004. StmtInfo := SQLDBConnector.Connection.GetStatementInfo('SELECT * FROM dbo.fn1(1)');
  1005. AssertEquals('TableName', '', StmtInfo.TableName);
  1006. AssertEquals('Updateable', False, StmtInfo.Updateable);
  1007. end;
  1008. procedure TTestTSQLConnection.TestGetNextValue;
  1009. begin
  1010. if not (sqSequences in SQLDBConnector.Connection.ConnOptions) then
  1011. Ignore('Connector '+SQLDBConnector.Connection.ClassName+' does not support sequences');
  1012. if SQLServerType=ssSQLite then
  1013. begin
  1014. SQLDBConnector.TryDropIfExist('me');
  1015. SQLDBConnector.ExecuteDirect('create table me (a integer primary key autoincrement,b int)');
  1016. SQLDBConnector.ExecuteDirect('insert into me (b) values (1)');// Will create table sqlite_sequence if it didn't exist yet
  1017. SQLDBConnector.ExecuteDirect('drop table me');
  1018. end;
  1019. SQLDBConnector.TryDropSequence('me');
  1020. SQLDBConnector.TryCreateSequence('me');
  1021. AssertTrue('Get value',SQLDBConnector.Connection.GetNextValue('me',1)>0);
  1022. end;
  1023. procedure TTestTSQLConnection.TestCommitUnprepares;
  1024. begin
  1025. TestUnprepare(True);
  1026. end;
  1027. procedure TTestTSQLConnection.TestRollBackUnprepares;
  1028. begin
  1029. TestUnprepare(False);
  1030. end;
  1031. { TTestTSQLScript }
  1032. procedure TTestTSQLScript.TestExecuteScript;
  1033. var Ascript : TSQLScript;
  1034. begin
  1035. Ascript := TSQLScript.Create(nil);
  1036. try
  1037. with Ascript do
  1038. begin
  1039. DataBase := SQLDBConnector.Connection;
  1040. Transaction := SQLDBConnector.Transaction;
  1041. Script.Clear;
  1042. Script.Append('create table FPDEV_A (id int);');
  1043. Script.Append('create table FPDEV_B (id int);');
  1044. ExecuteScript;
  1045. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  1046. SQLDBConnector.CommitDDL;
  1047. end;
  1048. finally
  1049. AScript.Free;
  1050. SQLDBConnector.ExecuteDirect('drop table FPDEV_A');
  1051. SQLDBConnector.ExecuteDirect('drop table FPDEV_B');
  1052. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  1053. SQLDBConnector.CommitDDL;
  1054. end;
  1055. end;
  1056. procedure TTestTSQLScript.TestScriptColon;
  1057. // Bug 25334: TSQLScript incorrectly treats : in scripts as sqldb query parameter markers
  1058. // Firebird-only test; can be extended for other dbs that use : in SQL
  1059. var
  1060. Ascript : TSQLScript;
  1061. begin
  1062. if not(SQLConnType in [interbase]) then Ignore(STestNotApplicable);
  1063. Ascript := TSQLScript.Create(nil);
  1064. try
  1065. with Ascript do
  1066. begin
  1067. DataBase := SQLDBConnector.Connection;
  1068. Transaction := SQLDBConnector.Transaction;
  1069. Script.Clear;
  1070. UseSetTerm := true;
  1071. // Example procedure that selects table names
  1072. Script.Append(
  1073. 'SET TERM ^ ; '+LineEnding+
  1074. 'CREATE PROCEDURE FPDEV_TESTCOLON '+LineEnding+
  1075. 'RETURNS (tblname VARCHAR(31)) '+LineEnding+
  1076. 'AS '+LineEnding+
  1077. 'begin '+LineEnding+
  1078. '/* Show tables. Note statement uses colon */ '+LineEnding+
  1079. 'FOR '+LineEnding+
  1080. ' SELECT RDB$RELATION_NAME '+LineEnding+
  1081. ' FROM RDB$RELATIONS '+LineEnding+
  1082. ' ORDER BY RDB$RELATION_NAME '+LineEnding+
  1083. ' INTO :tblname '+LineEnding+
  1084. 'DO '+LineEnding+
  1085. ' SUSPEND; '+LineEnding+
  1086. 'end^ '+LineEnding+
  1087. 'SET TERM ; ^'
  1088. );
  1089. ExecuteScript;
  1090. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  1091. SQLDBConnector.CommitDDL;
  1092. end;
  1093. finally
  1094. AScript.Free;
  1095. SQLDBConnector.ExecuteDirect('DROP PROCEDURE FPDEV_TESTCOLON');
  1096. // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
  1097. SQLDBConnector.CommitDDL;
  1098. end;
  1099. end;
  1100. procedure TTestTSQLScript.TestUseCommit;
  1101. // E.g. Firebird needs explicit COMMIT sometimes, e.g. if mixing DDL and DML
  1102. // statements in a script.
  1103. // Probably same as bug 17829 Error executing SQL script
  1104. const
  1105. TestValue='Some text';
  1106. var
  1107. Ascript : TSQLScript;
  1108. CheckQuery : TSQLQuery;
  1109. begin
  1110. Ascript := TSQLScript.Create(nil);
  1111. try
  1112. with Ascript do
  1113. begin
  1114. DataBase := SQLDBConnector.Connection;
  1115. Transaction := SQLDBConnector.Transaction;
  1116. Script.Clear;
  1117. UseCommit:=true;
  1118. // Example procedure that selects table names
  1119. Script.Append('CREATE TABLE fpdev_scriptusecommit (logmessage VARCHAR(255));');
  1120. Script.Append('COMMIT;'); //needed for table to show up
  1121. Script.Append('INSERT INTO fpdev_scriptusecommit (logmessage) VALUES('''+TestValue+''');');
  1122. Script.Append('COMMIT;');
  1123. ExecuteScript;
  1124. // This line should not run, as the commit above should have taken care of it:
  1125. //SQLDBConnector.CommitDDL;
  1126. // Test whether second line of script executed, just to be sure
  1127. CheckQuery:=SQLDBConnector.Query;
  1128. CheckQuery.SQL.Text:='SELECT logmessage FROM fpdev_scriptusecommit ';
  1129. CheckQuery.Open;
  1130. CheckEquals(TestValue, CheckQuery.Fields[0].AsString, 'Insert script line should have inserted '+TestValue);
  1131. CheckQuery.Close;
  1132. end;
  1133. finally
  1134. AScript.Free;
  1135. SQLDBConnector.ExecuteDirect('DROP TABLE fpdev_scriptusecommit');
  1136. SQLDBConnector.Transaction.Commit;
  1137. end;
  1138. end;
  1139. { TSQLDBTestCase }
  1140. function TSQLDBTestCase.GetSQLDBConnector: TSQLDBConnector;
  1141. begin
  1142. Result := DBConnector as TSQLDBConnector;
  1143. end;
  1144. procedure TSQLDBTestCase.SetUp;
  1145. begin
  1146. inherited SetUp;
  1147. InitialiseDBConnector;
  1148. DBConnector.StartTest(TestName);
  1149. end;
  1150. procedure TSQLDBTestCase.TearDown;
  1151. begin
  1152. DBConnector.StopTest(TestName);
  1153. if assigned(DBConnector) then
  1154. with SQLDBConnector do
  1155. if Assigned(Transaction) and Transaction.Active and not (stoUseImplicit in Transaction.Options) then
  1156. Transaction.Rollback;
  1157. FreeDBConnector;
  1158. inherited TearDown;
  1159. end;
  1160. initialization
  1161. if uppercase(dbconnectorname)='SQL' then
  1162. begin
  1163. RegisterTest(TTestTSQLQuery);
  1164. RegisterTest(TTestTSQLConnection);
  1165. RegisterTest(TTestTSQLScript);
  1166. end;
  1167. end.