testsqldb.pas 40 KB

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