mssqlconn.pp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. MS SQL Server connection using DB-Library
  4. See the file COPYING.FPC, included in this distribution,
  5. for details about the copyright.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  9. The Original Code was created by (c) 2010 Ladislav Karrach (Windows)
  10. for the Free Pascal project.
  11. **********************************************************************
  12. MS SQL Server Client Library is required (ntwdblib.dll)
  13. - or -
  14. FreeTDS (dblib.dll)
  15. freetds.conf: (http://www.freetds.org/userguide/freetdsconf.htm)
  16. [global]
  17. tds version = 7.1
  18. client charset = UTF-8
  19. port = 1433 or instance = ... (optional)
  20. dump file = freetds.log (optional)
  21. text size = 2147483647 (optional)
  22. TMSSQLConnection properties:
  23. HostName - can be specified also as 'servername:port' or 'servername\instance'
  24. (SQL Server Browser Service must be running on server to connect to specific instance)
  25. CharSet - if you use Microsoft DB-Lib and set to 'UTF-8' then char/varchar fields will be UTF8Encoded/Decoded
  26. if you use FreeTDS DB-Lib then you must compile with iconv support (requires libiconv2.dll) or cast char/varchar to nchar/nvarchar in SELECTs
  27. Params - "AutoCommit=true" - if you don't want explicitly commit/rollback transactions
  28. "TextSize=16777216" - set maximum size of text/image data returned
  29. "ApplicationName=YourAppName" - Set the app name for the connection. MSSQL 2000 and higher only
  30. }
  31. unit mssqlconn;
  32. {$mode objfpc}{$H+}
  33. interface
  34. uses
  35. Classes, SysUtils, sqldb, db, BufDataset,
  36. dblib;
  37. type
  38. TServerInfo = record
  39. ServerVersion: string;
  40. ServerVersionString: string;
  41. UserName: string;
  42. end;
  43. TClientCharset = (ccNone, ccUTF8, ccISO88591, ccUnknown);
  44. { TMSSQLConnection }
  45. TMSSQLConnection = class(TSQLConnection)
  46. private
  47. FDBLogin: PLOGINREC;
  48. FDBProc : PDBPROCESS;
  49. Ftds : integer; // TDS protocol version
  50. Fstatus : STATUS; // current result/rows fetch status
  51. FServerInfo: TServerInfo;
  52. function CheckError(const Ret: RETCODE): RETCODE;
  53. procedure Execute(const cmd: string); overload;
  54. procedure ExecuteDirectSQL(const Query: string);
  55. procedure GetParameters(cursor: TSQLCursor; AParams: TParams);
  56. function TranslateFldType(SQLDataType: integer): TFieldType;
  57. function ClientCharset: TClientCharset;
  58. function AutoCommit: boolean;
  59. function IsSybase: boolean;
  60. protected
  61. // Overrides from TSQLConnection
  62. function GetHandle:pointer; override;
  63. function GetAsSQLText(Param : TParam) : string; overload; override;
  64. // - Connect/disconnect
  65. procedure DoInternalConnect; override;
  66. procedure DoInternalDisconnect; override;
  67. // - Handle (de)allocation
  68. function AllocateCursorHandle:TSQLCursor; override;
  69. procedure DeAllocateCursorHandle(var cursor:TSQLCursor); override;
  70. function AllocateTransactionHandle:TSQLHandle; override;
  71. // - Transaction handling
  72. function GetTransactionHandle(trans:TSQLHandle):pointer; override;
  73. function StartDBTransaction(trans:TSQLHandle; AParams:string):boolean; override;
  74. function Commit(trans:TSQLHandle):boolean; override;
  75. function Rollback(trans:TSQLHandle):boolean; override;
  76. procedure CommitRetaining(trans:TSQLHandle); override;
  77. procedure RollbackRetaining(trans:TSQLHandle); override;
  78. // - Statement handling
  79. function StrToStatementType(s : string) : TStatementType; override;
  80. procedure PrepareStatement(cursor:TSQLCursor; ATransaction:TSQLTransaction; buf:string; AParams:TParams); override;
  81. procedure UnPrepareStatement(cursor:TSQLCursor); override;
  82. // - Statement execution
  83. procedure Execute(cursor:TSQLCursor; ATransaction:TSQLTransaction; AParams:TParams); override;
  84. function RowsAffected(cursor: TSQLCursor): TRowsCount; override;
  85. function RefreshLastInsertID(Query : TCustomSQLQuery; Field : TField): boolean; override;
  86. // - Result retrieving
  87. procedure AddFieldDefs(cursor:TSQLCursor; FieldDefs:TFieldDefs); override;
  88. function Fetch(cursor:TSQLCursor):boolean; override;
  89. function LoadField(cursor:TSQLCursor; FieldDef:TFieldDef; buffer:pointer; out CreateBlob : boolean):boolean; override;
  90. procedure LoadBlobIntoBuffer(FieldDef: TFieldDef;ABlobBuf: PBufBlobField; cursor: TSQLCursor; ATransaction : TSQLTransaction); override;
  91. procedure FreeFldBuffers(cursor:TSQLCursor); override;
  92. // - UpdateIndexDefs
  93. procedure UpdateIndexDefs(IndexDefs:TIndexDefs; TableName:string); override;
  94. // - Schema info
  95. function GetSchemaInfoSQL(SchemaType:TSchemaType; SchemaObjectName, SchemaObjectPattern:string):string; override;
  96. public
  97. constructor Create(AOwner : TComponent); override;
  98. function GetConnectionInfo(InfoType:TConnInfoType): string; override;
  99. procedure CreateDB; override;
  100. procedure DropDB; override;
  101. //property TDS:integer read Ftds;
  102. published
  103. // Redeclare properties from TSQLConnection
  104. property Password;
  105. property Transaction;
  106. property UserName;
  107. property CharSet;
  108. property HostName;
  109. // Redeclare properties from TDatabase
  110. property Connected;
  111. property Role;
  112. property DatabaseName;
  113. property KeepConnection;
  114. property LoginPrompt;
  115. property Params;
  116. property OnLogin;
  117. end;
  118. { TSybaseConnection }
  119. TSybaseConnection = class(TMSSQLConnection)
  120. public
  121. constructor Create(AOwner : TComponent); override;
  122. end;
  123. { EMSSQLDatabaseError }
  124. EMSSQLDatabaseError = class(ESQLDatabaseError)
  125. public
  126. property DBErrorCode: integer read ErrorCode; deprecated 'Please use ErrorCode instead of DBErrorCode'; // Feb 2014
  127. end;
  128. { TMSSQLConnectionDef }
  129. TMSSQLConnectionDef = Class(TConnectionDef)
  130. Class Function TypeName : String; override;
  131. Class Function ConnectionClass : TSQLConnectionClass; override;
  132. Class Function Description : String; override;
  133. Class Function DefaultLibraryName : String; override;
  134. Class Function LoadFunction : TLibraryLoadFunction; override;
  135. Class Function UnLoadFunction : TLibraryUnLoadFunction; override;
  136. Class Function LoadedLibraryName: string; override;
  137. end;
  138. { TSybaseConnectionDef }
  139. TSybaseConnectionDef = Class(TMSSQLConnectionDef)
  140. Class Function TypeName : String; override;
  141. Class Function ConnectionClass : TSQLConnectionClass; override;
  142. Class Function Description : String; override;
  143. end;
  144. var
  145. DBLibLibraryName: string = DBLIBDLL;
  146. implementation
  147. uses StrUtils, FmtBCD;
  148. type
  149. { TDBLibCursor }
  150. TDBLibCursor = class(TSQLCursor)
  151. private
  152. FConnection: TMSSQLConnection; // owner connection
  153. FQuery: string; // :ParamNames converted to $1,$2,..,$n
  154. FParamReplaceString: string;
  155. protected
  156. FRowsAffected: integer;
  157. function ReplaceParams(AParams: TParams): string; // replaces parameters placeholders $1,$2,..$n in FQuery with supplied values in AParams
  158. procedure Prepare(Buf: string; AParams: TParams);
  159. procedure Execute(AParams: TParams);
  160. function Fetch: boolean;
  161. procedure Put(column: integer; out s: string); overload;
  162. public
  163. constructor Create(AConnection: TMSSQLConnection); overload;
  164. end;
  165. const
  166. SBeginTransaction = 'BEGIN TRANSACTION';
  167. SAutoCommit = 'AUTOCOMMIT';
  168. STextSize = 'TEXTSIZE';
  169. SAppName = 'APPLICATIONNAME';
  170. var
  171. DBErrorStr, DBMsgStr: string;
  172. DBErrorNo, DBMsgNo: integer;
  173. function DBErrHandler(dbproc: PDBPROCESS; severity, dberr, oserr:INT; dberrstr, oserrstr:PChar):INT; cdecl;
  174. begin
  175. DBErrorStr:=DBErrorStr+LineEnding+dberrstr;
  176. DBErrorNo :=dberr;
  177. Result :=INT_CANCEL;
  178. // for server messages with severity greater than 10 error handler is also called
  179. end;
  180. function DBMsgHandler(dbproc: PDBPROCESS; msgno: DBINT; msgstate, severity:INT; msgtext, srvname, procname:PChar; line:DBUSMALLINT):INT; cdecl;
  181. begin
  182. DBMsgStr:=DBMsgStr+LineEnding+msgtext;
  183. DBMsgNo :=msgno;
  184. Result :=0;
  185. end;
  186. { TDBLibCursor }
  187. procedure TDBLibCursor.Prepare(Buf: string; AParams: TParams);
  188. var
  189. ParamBinding : TParamBinding;
  190. begin
  191. if assigned(AParams) and (AParams.Count > 0) then
  192. FQuery:=AParams.ParseSQL(Buf, false, sqEscapeSlash in FConnection.ConnOptions, sqEscapeRepeat in FConnection.ConnOptions, psSimulated, ParamBinding, FParamReplaceString)
  193. else
  194. FQuery:=Buf;
  195. end;
  196. function TDBLibCursor.ReplaceParams(AParams: TParams): string;
  197. var i: integer;
  198. ParamNames, ParamValues: array of string;
  199. begin
  200. if Assigned(AParams) and (AParams.Count > 0) then //taken from mysqlconn, pqconnection
  201. begin
  202. setlength(ParamNames, AParams.Count);
  203. setlength(ParamValues, AParams.Count);
  204. for i := 0 to AParams.Count -1 do
  205. begin
  206. ParamNames[AParams.Count-i-1] := format('%s%d', [FParamReplaceString, AParams[i].Index+1]);
  207. ParamValues[AParams.Count-i-1] := FConnection.GetAsSQLText(AParams[i]);
  208. end;
  209. Result := stringsreplace(FQuery, ParamNames, ParamValues, [rfReplaceAll]);
  210. end
  211. else
  212. Result := FQuery;
  213. end;
  214. procedure TDBLibCursor.Execute(AParams: TParams);
  215. begin
  216. Fconnection.Execute(Self, nil, AParams);
  217. end;
  218. function TDBLibCursor.Fetch: boolean;
  219. begin
  220. Result := Fconnection.Fetch(Self);
  221. end;
  222. procedure TDBLibCursor.Put(column: integer; out s: string);
  223. var
  224. data: PByte;
  225. datalen: DBINT;
  226. begin
  227. data := dbdata(Fconnection.FDBProc, column);
  228. datalen := dbdatlen(Fconnection.FDBProc, column);
  229. SetString(s, PAnsiChar(data), datalen);
  230. end;
  231. constructor TDBLibCursor.Create(AConnection: TMSSQLConnection);
  232. begin
  233. inherited Create;
  234. FConnection := AConnection;
  235. end;
  236. { TSybaseConnection }
  237. constructor TSybaseConnection.Create(AOwner: TComponent);
  238. begin
  239. inherited Create(AOwner);
  240. Ftds := DBTDS_50;
  241. end;
  242. { TMSSQLConnection }
  243. function TMSSQLConnection.IsSybase: boolean;
  244. begin
  245. Result := (Ftds=DBTDS_50) or (Ftds=DBTDS_42);
  246. end;
  247. function TMSSQLConnection.CheckError(const Ret: RETCODE): RETCODE;
  248. var E: EMSSQLDatabaseError;
  249. begin
  250. if (Ret=FAIL) or (DBErrorStr<>'') then
  251. begin
  252. // try clear all pending results to allow ROLLBACK and prevent error 10038 "Results pending"
  253. if assigned(FDBProc) then dbcancel(FDBProc);
  254. if DBErrorStr = '' then
  255. case DBErrorNo of
  256. SYBEFCON: DBErrorStr:='SQL Server connection failed!';
  257. end;
  258. E:=EMSSQLDatabaseError.CreateFmt('Error %d : %s'+LineEnding+'%s', [DBErrorNo, DBErrorStr, DBMsgStr], Self, DBErrorNo, '');
  259. DBErrorStr:='';
  260. DBMsgStr:='';
  261. raise E;
  262. end;
  263. Result:=Ret;
  264. end;
  265. constructor TMSSQLConnection.Create(AOwner: TComponent);
  266. begin
  267. inherited Create(AOwner);
  268. FConnOptions := [sqSupportEmptyDatabaseName, sqEscapeRepeat, sqImplicitTransaction, sqLastInsertID];
  269. //FieldNameQuoteChars:=DoubleQuotes; //default
  270. Ftds := DBTDS_UNKNOWN;
  271. end;
  272. procedure TMSSQLConnection.CreateDB;
  273. begin
  274. ExecuteDirectSQL('CREATE DATABASE '+DatabaseName);
  275. end;
  276. procedure TMSSQLConnection.DropDB;
  277. begin
  278. ExecuteDirectSQL('DROP DATABASE '+DatabaseName);
  279. end;
  280. procedure TMSSQLConnection.ExecuteDirectSQL(const Query: string);
  281. var ADatabaseName: string;
  282. begin
  283. CheckDisConnected;
  284. ADatabaseName:=DatabaseName;
  285. DatabaseName:='';
  286. try
  287. Open;
  288. Execute(Query);
  289. finally
  290. Close;
  291. DatabaseName:=ADatabaseName;
  292. end;
  293. end;
  294. function TMSSQLConnection.GetHandle: pointer;
  295. begin
  296. Result:=FDBProc;
  297. end;
  298. function TMSSQLConnection.GetAsSQLText(Param: TParam): string;
  299. function IsBinary(const s: string): boolean;
  300. var i: integer;
  301. begin
  302. for i:=1 to length(s) do if s[i] < #9 then Exit(true);
  303. Exit(false);
  304. end;
  305. function StrToHex(const s: string): string;
  306. begin
  307. setlength(Result, 2*length(s));
  308. BinToHex(PChar(s), PChar(Result), length(s));
  309. end;
  310. begin
  311. if not Param.IsNull then
  312. case Param.DataType of
  313. ftBoolean:
  314. if Param.AsBoolean then
  315. Result:='1'
  316. else
  317. Result:='0';
  318. ftString, ftFixedChar, ftMemo:
  319. //if IsBinary(Param.AsString) then
  320. // Result := '0x' + StrToHex(Param.AsString)
  321. //else
  322. begin
  323. Result := QuotedStr(Param.AsString);
  324. if (Ftds >= DBTDS_70) then
  325. Result := 'N' + Result
  326. else if (Ftds = 0) and (ClientCharset = ccUTF8) then //hack: Microsoft DB-Lib used
  327. Result := UTF8Decode(Result);
  328. end;
  329. ftBlob, ftBytes, ftVarBytes:
  330. Result := '0x' + StrToHex(Param.AsString);
  331. else
  332. Result := inherited GetAsSQLText(Param);
  333. end
  334. else
  335. Result:=inherited GetAsSQLText(Param);
  336. end;
  337. procedure TMSSQLConnection.DoInternalConnect;
  338. const
  339. DBVERSION: array[boolean] of BYTE = (DBVER60, DBVERSION_100);
  340. IMPLICIT_TRANSACTIONS_OFF: array[boolean] of shortstring = ('SET IMPLICIT_TRANSACTIONS OFF', 'SET CHAINED OFF');
  341. ANSI_DEFAULTS_ON: array[boolean] of shortstring = ('SET ANSI_DEFAULTS ON', 'SET QUOTED_IDENTIFIER ON');
  342. CURSOR_CLOSE_ON_COMMIT_OFF: array[boolean] of shortstring = ('SET CURSOR_CLOSE_ON_COMMIT OFF', 'SET CLOSE ON ENDTRAN OFF');
  343. VERSION_NUMBER: array[boolean] of shortstring = ('SERVERPROPERTY(''ProductVersion'')', '@@version_number');
  344. begin
  345. // empty DatabaseName=default database defined for login
  346. inherited DoInternalConnect;
  347. InitialiseDBLib(DBLibLibraryName);
  348. if not DBLibInit then
  349. begin
  350. dbinit();
  351. dberrhandle(@DBErrHandler);
  352. dbmsghandle(@DBMsgHandler);
  353. DBLibInit:=true;
  354. end;
  355. FDBLogin:=dblogin();
  356. if FDBLogin=nil then DatabaseError('dblogin() failed!');
  357. // DBVERSION_100 is ATM not implemented by FreeTDS 0.91;
  358. // set environment variable TDSVER to 5.0: Windows: SET TDSVER=5.0, Unix/Linux: TDSVER=5.0
  359. // or in freetds.conf: include "tds version=5.0"
  360. dbsetlversion(FDBLogin, DBVERSION[IsSybase]);
  361. if UserName = '' then
  362. dbsetlsecure(FDBLogin)
  363. else
  364. begin
  365. dbsetlname(FDBLogin, PChar(UserName), DBSETUSER);
  366. dbsetlname(FDBLogin, PChar(Password), DBSETPWD);
  367. end;
  368. if CharSet = '' then
  369. dbsetlcharset(FDBLogin, 'UTF-8')
  370. else
  371. dbsetlcharset(FDBLogin, PChar(CharSet));
  372. if Params.IndexOfName(SAppName) <> -1 then
  373. dbsetlname(FDBLogin, PChar(Params.Values[SAppName]), DBSETAPP);
  374. //dbsetlname(FDBLogin, PChar(TIMEOUT_IGNORE), DBSET_LOGINTIME);
  375. dbsetlogintime(10);
  376. FDBProc := dbopen(FDBLogin, PChar(HostName));
  377. if FDBProc=nil then CheckError(FAIL);
  378. Ftds := dbtds(FDBProc);
  379. //CheckError( dbsetopt(FDBProc, DBQUOTEDIDENT, '') ); //in FreeTDS executes: "SET QUOTED_IDENTIFIER ON"
  380. //CheckError( dbsetopt(FDBProc, DBTEXTSIZE, '2147483647') ); //in FreeTDS: unimplemented, returns FAIL
  381. //CheckError( dbsetopt(FDBProc, DBTEXTLIMIT, '2147483647') ); //in FreeTDS: unimplemented, returns FAIL, but required by ntwdblib.dll
  382. //CheckError( dbsqlexec(FDBProc) ); //after setting DBTEXTSIZE option
  383. //CheckError (dbresults(FDBProc));
  384. //while dbresults(FDBProc) = SUCCEED do ;
  385. // Also SQL Server ODBC driver and Microsoft OLE DB Provider for SQL Server set ANSI_DEFAULTS to ON when connecting
  386. //Execute(ANSI_DEFAULTS_ON[IsSybase]);
  387. Execute('SET QUOTED_IDENTIFIER ON');
  388. if Params.IndexOfName(STextSize) <> -1 then
  389. Execute('SET TEXTSIZE '+Params.Values[STextSize])
  390. else
  391. Execute('SET TEXTSIZE 16777216');
  392. if AutoCommit then
  393. Execute(IMPLICIT_TRANSACTIONS_OFF[IsSybase]); //set connection to autocommit mode - default
  394. if DatabaseName <> '' then
  395. CheckError( dbuse(FDBProc, PChar(DatabaseName)) );
  396. with TDBLibCursor.Create(Self) do
  397. begin
  398. try
  399. Prepare(format('SELECT cast(%s as varchar), @@version, user_name()', [VERSION_NUMBER[IsSybase]]), nil);
  400. Execute(nil);
  401. while Fetch do
  402. begin
  403. Put(1, FServerInfo.ServerVersion);
  404. Put(2, FServerInfo.ServerVersionString);
  405. Put(3, FServerInfo.UserName);
  406. end;
  407. except
  408. FServerInfo.ServerVersion:='';
  409. FServerInfo.ServerVersionString:='';
  410. FServerInfo.UserName:='';
  411. end;
  412. Free;
  413. end;
  414. end;
  415. procedure TMSSQLConnection.DoInternalDisconnect;
  416. begin
  417. inherited DoInternalDisconnect;
  418. dbclose(FDBProc);
  419. dbfreelogin(FDBLogin);
  420. ReleaseDBLib;
  421. end;
  422. function TMSSQLConnection.AllocateCursorHandle: TSQLCursor;
  423. begin
  424. Result:=TDBLibCursor.Create(Self);
  425. end;
  426. procedure TMSSQLConnection.DeAllocateCursorHandle(var cursor: TSQLCursor);
  427. begin
  428. FreeAndNil(cursor);
  429. end;
  430. function TMSSQLConnection.StrToStatementType(s: string): TStatementType;
  431. begin
  432. s:=LowerCase(s);
  433. if s = 'exec' then
  434. Result:=stExecProcedure
  435. else
  436. Result:=inherited StrToStatementType(s);
  437. end;
  438. function TMSSQLConnection.AllocateTransactionHandle: TSQLHandle;
  439. begin
  440. Result:=nil;
  441. end;
  442. function TMSSQLConnection.GetTransactionHandle(trans: TSQLHandle): pointer;
  443. begin
  444. Result:=nil;
  445. end;
  446. function TMSSQLConnection.StartDBTransaction(trans: TSQLHandle; AParams: string): boolean;
  447. begin
  448. Result := not AutoCommit;
  449. if Result then
  450. Execute(SBeginTransaction);
  451. end;
  452. function TMSSQLConnection.Commit(trans: TSQLHandle): boolean;
  453. begin
  454. Execute('COMMIT');
  455. Result:=true;
  456. end;
  457. function TMSSQLConnection.Rollback(trans: TSQLHandle): boolean;
  458. begin
  459. Execute('IF @@TRANCOUNT>0 ROLLBACK');
  460. Result:=true;
  461. end;
  462. procedure TMSSQLConnection.CommitRetaining(trans: TSQLHandle);
  463. begin
  464. if Commit(trans) then
  465. Execute(SBeginTransaction);
  466. end;
  467. procedure TMSSQLConnection.RollbackRetaining(trans: TSQLHandle);
  468. begin
  469. if Rollback(trans) then
  470. Execute(SBeginTransaction);
  471. end;
  472. function TMSSQLConnection.AutoCommit: boolean;
  473. begin
  474. Result := StrToBoolDef(Params.Values[SAutoCommit], False);
  475. end;
  476. function TMSSQLConnection.ClientCharset: TClientCharset;
  477. begin
  478. {$IF (FPC_VERSION>=2) AND (FPC_RELEASE>4)}
  479. case CharSet of
  480. '' : Result := ccNone;
  481. 'UTF-8' : Result := ccUTF8;
  482. 'ISO-8859-1' : Result := ccISO88591;
  483. else Result := ccUnknown;
  484. end;
  485. {$ELSE}
  486. if CharSet = '' then
  487. Result := ccNone
  488. else if CharSet = 'UTF-8' then
  489. Result := ccUTF8
  490. else if CharSet = 'ISO-8859-1' then
  491. Result := ccISO88591
  492. else
  493. Result := ccUnknown;
  494. {$ENDIF}
  495. end;
  496. procedure TMSSQLConnection.PrepareStatement(cursor: TSQLCursor;
  497. ATransaction: TSQLTransaction; buf: string; AParams: TParams);
  498. begin
  499. (cursor as TDBLibCursor).Prepare(buf, AParams);
  500. end;
  501. procedure TMSSQLConnection.UnPrepareStatement(cursor: TSQLCursor);
  502. begin
  503. if assigned(FDBProc) and (Fstatus <> NO_MORE_ROWS) then
  504. dbcanquery(FDBProc);
  505. end;
  506. procedure TMSSQLConnection.Execute(const cmd: string);
  507. begin
  508. DBErrorStr:='';
  509. DBMsgStr :='';
  510. CheckError( dbcmd(FDBProc, PChar(cmd)) );
  511. CheckError( dbsqlexec(FDBProc) );
  512. CheckError( dbresults(FDBProc) );
  513. end;
  514. procedure TMSSQLConnection.Execute(cursor: TSQLCursor; ATransaction: TSQLTransaction; AParams: TParams);
  515. var c: TDBLibCursor;
  516. cmd: string;
  517. res: RETCODE;
  518. begin
  519. c:=cursor as TDBLibCursor;
  520. cmd := c.ReplaceParams(AParams);
  521. Execute(cmd);
  522. res := SUCCEED;
  523. repeat
  524. c.FSelectable := dbcmdrow(FDBProc)=SUCCEED;
  525. c.FRowsAffected := dbcount(FDBProc);
  526. if assigned(dbiscount) and not dbiscount(FDBProc) then
  527. c.FRowsAffected := -1;
  528. if not c.FSelectable then //Sybase stored proc.
  529. begin
  530. repeat until dbnextrow(FDBProc) = NO_MORE_ROWS;
  531. res := CheckError( dbresults(FDBProc) );
  532. // stored procedure information (return status and output parameters)
  533. // are available only after normal results are processed
  534. //if res = NO_MORE_RESULTS then GetParameters(cursor, AParams);
  535. end;
  536. until c.FSelectable or (res = NO_MORE_RESULTS) or (res = FAIL);
  537. if res = NO_MORE_RESULTS then
  538. Fstatus := NO_MORE_ROWS
  539. else
  540. Fstatus := MORE_ROWS;
  541. end;
  542. procedure TMSSQLConnection.GetParameters(cursor: TSQLCursor; AParams: TParams);
  543. var Param: TParam;
  544. begin
  545. // Microsoft SQL Server no more returns OUTPUT parameters as a special result row
  546. // so we can not use dbret*() functions, but instead we must use dbrpc*() functions
  547. // only procedure return status number is returned
  548. if dbhasretstat(FDBProc) = 1 then
  549. begin
  550. Param := AParams.FindParam('RETURN_STATUS');
  551. if not assigned(Param) then
  552. Param := AParams.CreateParam(ftInteger, 'RETURN_STATUS', ptOutput);
  553. Param.AsInteger := dbretstatus(FDBProc);
  554. end;
  555. end;
  556. function TMSSQLConnection.RowsAffected(cursor: TSQLCursor): TRowsCount;
  557. begin
  558. if assigned(cursor) then
  559. Result := (cursor as TDBLibCursor).FRowsAffected
  560. else
  561. Result := inherited RowsAffected(cursor);
  562. end;
  563. function TMSSQLConnection.RefreshLastInsertID(Query: TCustomSQLQuery; Field: TField): boolean;
  564. var Identity: int64;
  565. begin
  566. // global variable @@IDENTITY is NUMERIC(38,0)
  567. Result:=False;
  568. if dbcmd(FDBProc, 'SELECT @@IDENTITY') = FAIL then Exit;
  569. if dbsqlexec(FDBProc) = FAIL then Exit;
  570. if dbresults(FDBProc) = FAIL then Exit;
  571. if dbnextrow(FDBProc) = FAIL then Exit;
  572. if dbconvert(FDBProc, dbcoltype(FDBProc,1), dbdata(FDBProc,1), -1, SYBINT8, @Identity, sizeof(Identity)) = -1 then Exit;
  573. // by default identity columns are ReadOnly
  574. Field.AsLargeInt := Identity;
  575. Result:=True;
  576. end;
  577. function TMSSQLConnection.TranslateFldType(SQLDataType: integer): TFieldType;
  578. begin
  579. case SQLDataType of
  580. SQLCHAR: Result:=ftFixedChar;
  581. SQLVARCHAR: Result:=ftString;
  582. SQLINT1: Result:=ftWord;
  583. SQLINT2: Result:=ftSmallInt;
  584. SQLINT4, SQLINTN: Result:=ftInteger;
  585. SYBINT8: Result:=ftLargeInt;
  586. SQLFLT4, SQLFLT8,
  587. SQLFLTN: Result:=ftFloat;
  588. SQLMONEY4, SQLMONEY,
  589. SQLMONEYN: Result:=ftCurrency;
  590. SYBMSDATE: Result:=ftDate;
  591. SYBMSTIME: Result:=ftTime;
  592. SQLDATETIM4, SQLDATETIME,
  593. SQLDATETIMN,
  594. SYBMSDATETIME2,
  595. SYBMSDATETIMEOFFSET: Result:=ftDateTime;
  596. SYBMSXML,
  597. SQLTEXT: Result:=ftMemo;
  598. SQLIMAGE: Result:=ftBlob;
  599. SQLDECIMAL, SQLNUMERIC: Result:=ftBCD;
  600. SQLBIT: Result:=ftBoolean;
  601. SQLBINARY: Result:=ftBytes;
  602. SQLVARBINARY: Result:=ftVarBytes;
  603. SYBUNIQUE: Result:=ftGuid;
  604. SYBVARIANT: Result:=ftBlob;
  605. else
  606. DatabaseErrorFmt('Unsupported SQL DataType %d "%s"', [SQLDataType, dbprtype(SQLDataType)]);
  607. Result:=ftUnknown;
  608. end;
  609. end;
  610. procedure TMSSQLConnection.AddFieldDefs(cursor: TSQLCursor; FieldDefs: TFieldDefs);
  611. var i, FieldSize: integer;
  612. FieldName: string;
  613. FieldType: TFieldType;
  614. col: DBCOL;
  615. begin
  616. col.SizeOfStruct:=sizeof(col);
  617. for i:=1 to dbnumcols(FDBProc) do
  618. begin
  619. if dbtablecolinfo(FDBProc, i, @col) = FAIL then continue;
  620. FieldName := col.Name;
  621. FieldType := TranslateFldType(col.Typ);
  622. case FieldType of
  623. ftString, ftFixedChar:
  624. begin
  625. FieldSize := col.MaxLength;
  626. if FieldSize >= $3FFFFFFF then // varchar(max)
  627. FieldType := ftMemo;
  628. end;
  629. ftBytes, ftVarBytes:
  630. begin
  631. FieldSize := col.MaxLength;
  632. if FieldSize >= $3FFFFFFF then // varbinary(max)
  633. FieldType := ftBlob;
  634. end;
  635. ftBCD:
  636. begin
  637. FieldSize := col.Scale;
  638. if (FieldSize > MaxBCDScale) or (col.Precision-col.Scale > MaxBCDPrecision-MaxBCDScale) then
  639. FieldType := ftFmtBCD;
  640. end;
  641. ftGuid:
  642. FieldSize := 38;
  643. else
  644. FieldSize := 0;
  645. if col.Identity and (FieldType = ftInteger) then
  646. FieldType := ftAutoInc;
  647. end;
  648. with FieldDefs.Add(FieldDefs.MakeNameUnique(FieldName), FieldType, FieldSize, (col.Null=0) and (not col.Identity), i) do
  649. begin
  650. // identity, timestamp and calculated column are not updatable
  651. if col.Updatable = 0 then Attributes := Attributes + [faReadonly];
  652. case FieldType of
  653. ftBCD,
  654. ftFmtBCD: Precision := col.Precision;
  655. end;
  656. end;
  657. end;
  658. end;
  659. function TMSSQLConnection.Fetch(cursor: TSQLCursor): boolean;
  660. begin
  661. // Compute rows resulting from the COMPUTE clause are not processed
  662. repeat
  663. Fstatus := dbnextrow(FDBProc);
  664. Result := Fstatus=REG_ROW;
  665. until Result or (Fstatus = NO_MORE_ROWS);
  666. if Fstatus = NO_MORE_ROWS then
  667. while dbresults(FDBProc) <> NO_MORE_RESULTS do // process remaining results if there are any
  668. repeat until dbnextrow(FDBProc) = NO_MORE_ROWS;
  669. end;
  670. function TMSSQLConnection.LoadField(cursor: TSQLCursor; FieldDef: TFieldDef;
  671. buffer: pointer; out CreateBlob: boolean): boolean;
  672. var i: integer;
  673. data, dest: PByte;
  674. datalen, destlen: DBINT;
  675. srctype, desttype: INT;
  676. dbdt: DBDATETIME;
  677. dbdr: DBDATEREC;
  678. dbdta: DBDATETIMEALL;
  679. bcdstr: array[0..MaxFmtBCDFractionSize+2] of char;
  680. begin
  681. CreateBlob:=false;
  682. i:=FieldDef.FieldNo;
  683. srctype:=dbcoltype(FDBProc,i);
  684. data:=dbdata(FDBProc,i);
  685. datalen:=dbdatlen(FDBProc,i);
  686. Result:=assigned(data) and (datalen<>0);
  687. if not Result then
  688. Exit;
  689. dest:=buffer;
  690. destlen:=FieldDef.Size;
  691. case FieldDef.DataType of
  692. ftString, ftFixedChar:
  693. desttype:=SQLCHAR;
  694. ftBytes:
  695. desttype:=SQLBINARY;
  696. ftVarBytes:
  697. begin
  698. PWord(dest)^:=datalen;
  699. inc(dest, sizeof(Word));
  700. desttype:=SQLBINARY;
  701. end;
  702. ftSmallInt, ftWord:
  703. begin
  704. desttype:=SQLINT2;
  705. destlen:=sizeof(DBSMALLINT); //smallint
  706. end;
  707. ftAutoInc,
  708. ftInteger:
  709. begin
  710. desttype:=SQLINT4;
  711. destlen:=sizeof(DBINT); //integer
  712. end;
  713. ftLargeInt:
  714. begin
  715. desttype:=SYBINT8;
  716. destlen:=sizeof(int64);
  717. end;
  718. ftCurrency,
  719. ftFloat:
  720. begin
  721. desttype:=SQLFLT8;
  722. destlen:=sizeof(DBFLT8); //double
  723. end;
  724. ftDate, ftTime,
  725. ftDateTime:
  726. if srctype in [SYBMSDATE, SYBMSTIME, SYBMSDATETIME2, SYBMSDATETIMEOFFSET] then // dbwillconvert(srctype, SYBMSDATETIME2)
  727. begin
  728. dest:=@dbdta;
  729. desttype:=SYBMSDATETIME2;
  730. destlen:=sizeof(dbdta);
  731. end
  732. else
  733. begin
  734. dest:=@dbdt;
  735. desttype:=SQLDATETIME;
  736. destlen:=sizeof(dbdt);
  737. end;
  738. ftBCD:
  739. begin
  740. // FreeTDS 0.91 does not support converting from numeric to money
  741. //desttype:=SQLMONEY;
  742. desttype:=SQLFLT8;
  743. destlen:=sizeof(currency);
  744. end;
  745. ftFmtBCD:
  746. begin
  747. {
  748. dbnum.precision:=FieldDef.Precision;
  749. dbnum.scale :=FieldDef.Size;
  750. dest:=@dbnum;
  751. desttype:=SQLNUMERIC;
  752. destlen:=sizeof(dbnum);
  753. }
  754. dest:=@bcdstr[0];
  755. desttype:=SQLCHAR;
  756. destlen:=sizeof(bcdstr);
  757. fillchar(bcdstr, destlen, 0); //required when used ntwdblib.dll
  758. end;
  759. ftBoolean:
  760. begin
  761. desttype:=SQLBIT;
  762. destlen:=sizeof(WordBool);
  763. end;
  764. ftGuid:
  765. begin
  766. desttype:=SQLCHAR;
  767. end;
  768. ftMemo,
  769. ftBlob:
  770. begin
  771. CreateBlob:=true;
  772. Exit;
  773. end
  774. else
  775. //DatabaseErrorFmt('Tried to load field of unsupported field type %s',[FieldTypeNames[FieldDef.DataType]]);
  776. Result:=false;
  777. end;
  778. dbconvert(FDBProc, srctype, data , datalen, desttype, dest, destlen);
  779. case FieldDef.DataType of
  780. ftString, ftFixedChar:
  781. begin
  782. PChar(dest + datalen)^ := #0; //strings must be null-terminated
  783. if ((Ftds = 0) and (ClientCharset = ccUTF8)) {hack: MS DB-Lib used} or
  784. (ClientCharset = ccISO88591) {hack: FreeTDS} then
  785. StrPLCopy(PChar(dest), UTF8Encode(PChar(dest)), destlen);
  786. end;
  787. ftDate, ftTime, ftDateTime:
  788. if desttype = SYBMSDATETIME2 then
  789. PDateTime(buffer)^ := dbdatetimeallcrack(@dbdta)
  790. else
  791. begin
  792. //detect DBDATEREC version by pre-setting dbdr
  793. dbdr.millisecond := -1;
  794. if dbdatecrack(FDBProc, @dbdr, @dbdt) = SUCCEED then
  795. begin
  796. if dbdr.millisecond = -1 then
  797. PDateTime(buffer)^ := composedatetime(
  798. encodedate(dbdr.oldyear, dbdr.oldmonth, dbdr.oldday),
  799. encodetime(dbdr.oldhour, dbdr.oldminute, dbdr.oldsecond, dbdr.oldmillisecond))
  800. else
  801. PDateTime(buffer)^ := composedatetime(
  802. encodedate(dbdr.year, dbdr.month, dbdr.day),
  803. encodetime(dbdr.hour, dbdr.minute, dbdr.second, dbdr.millisecond));
  804. end;
  805. end;
  806. ftBCD:
  807. PCurrency(buffer)^ := FloatToCurr(PDouble(buffer)^); //PCurrency(buffer)^ := dbmoneytocurr(buffer);
  808. ftFmtBCD:
  809. PBCD(buffer)^:=StrToBCD(bcdstr, FSQLFormatSettings); //PBCD(buffer)^:=dbnumerictobcd(dbnum);
  810. end;
  811. end;
  812. procedure TMSSQLConnection.LoadBlobIntoBuffer(FieldDef: TFieldDef;
  813. ABlobBuf: PBufBlobField; cursor: TSQLCursor; ATransaction: TSQLTransaction);
  814. var data: PByte;
  815. datalen: DBINT;
  816. begin
  817. // see also LoadField
  818. data:=dbdata(FDBProc, FieldDef.FieldNo);
  819. datalen:=dbdatlen(FDBProc, FieldDef.FieldNo);
  820. ReAllocMem(ABlobBuf^.BlobBuffer^.Buffer, datalen);
  821. Move(data^, ABlobBuf^.BlobBuffer^.Buffer^, datalen);
  822. ABlobBuf^.BlobBuffer^.Size := datalen;
  823. end;
  824. procedure TMSSQLConnection.FreeFldBuffers(cursor: TSQLCursor);
  825. begin
  826. inherited FreeFldBuffers(cursor);
  827. end;
  828. procedure TMSSQLConnection.UpdateIndexDefs(IndexDefs: TIndexDefs; TableName: string);
  829. const INDEXES_QUERY: array[boolean] of string=(
  830. //MS SQL Server; TODO: we can use "execute dbo.sp_helpindex 'TableName'" when Open on Execute will fully work
  831. 'select i.name, i.indid, c.name as col_name,'+
  832. 'indexproperty(i.id, i.name, ''IsUnique''),'+
  833. 'objectproperty(o.id, ''IsPrimaryKey'') '+
  834. 'from sysindexes i '+
  835. ' join sysindexkeys k on i.id=k.id and i.indid=k.indid '+
  836. ' join syscolumns c on k.id=c.id and k.colid=c.colid '+
  837. ' left join sysobjects o on i.name=o.name and i.id=o.parent_obj '+
  838. 'where i.id=object_id(''%s'') '+
  839. 'order by k.indid, k.keyno'
  840. ,
  841. //Sybase; http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.help.ase.15.7/title.htm
  842. 'select i.name, i.indid,' +
  843. 'index_col(object_name(i.id),i.indid,c.colid) as col_name,' +
  844. '(i.status & 2)/2 as IsUnique,' +
  845. '(i.status & 2048)/2048 as IsPrimaryKey ' +
  846. 'from sysindexes i '+
  847. ' join syscolumns c on c.id=i.id and c.colid<=i.keycnt-case i.indid when 1 then 0 else 1 end ' +
  848. 'where i.id=object_id(''%s'') '+
  849. ' and i.indid between 1 and 254 '+ // indid 0 is the table name, 255 is TEXT,IMAGE
  850. 'order by i.indid, c.colid'
  851. );
  852. var qry : TSQLQuery;
  853. begin
  854. //if not assigned(Transaction) then
  855. // DatabaseError(SErrConnTransactionnSet);
  856. qry := TSQLQuery.Create(nil);
  857. qry.Transaction := Transaction;
  858. qry.Database := Self;
  859. with qry do
  860. begin
  861. ReadOnly := True;
  862. SQL.Text := format(INDEXES_QUERY[IsSybase], [TableName]);
  863. Open;
  864. end;
  865. while not qry.Eof do with IndexDefs.AddIndexDef do
  866. begin
  867. Name := trim(qry.Fields[0].AsString);
  868. Fields := trim(qry.Fields[2].AsString);
  869. if qry.Fields[3].AsInteger=1 then Options := Options + [ixUnique];
  870. if qry.Fields[4].AsInteger=1 then Options := Options + [ixPrimary];
  871. qry.Next;
  872. while (Name = trim(qry.Fields[0].AsString)) and (not qry.Eof) do
  873. begin
  874. Fields := Fields + ';' + trim(qry.Fields[2].AsString);
  875. qry.Next;
  876. end;
  877. end;
  878. qry.Close;
  879. qry.Free;
  880. end;
  881. function TMSSQLConnection.GetSchemaInfoSQL(SchemaType: TSchemaType; SchemaObjectName, SchemaObjectPattern: string): string;
  882. const SCHEMA_QUERY='select id as RECNO, db_name() as CATALOG_NAME, user_name(uid) as SCHEMA_NAME, name as %s '+
  883. 'from sysobjects '+
  884. 'where type in (%s) '+
  885. 'order by name';
  886. begin
  887. // for simplicity are used only system tables and columns, common to both MS SQL Server and Sybase
  888. case SchemaType of
  889. stTables : Result := format(SCHEMA_QUERY, ['TABLE_NAME, 1 as TABLE_TYPE', '''U''']);
  890. stSysTables : Result := format(SCHEMA_QUERY, ['TABLE_NAME, 4 as TABLE_TYPE', '''S''']);
  891. stProcedures : Result := format(SCHEMA_QUERY, ['PROCEDURE_NAME , case type when ''P'' then 1 else 2 end as PROCEDURE_TYPE', '''P'',''FN'',''IF'',''TF''']);
  892. stColumns : Result := 'select colid as RECNO, db_name() as CATALOG_NAME, user_name(uid) as SCHEMA_NAME, o.name as TABLE_NAME,'+
  893. 'c.name as COLUMN_NAME,'+
  894. 'colid as COLUMN_POSITION,'+
  895. '0 as COLUMN_TYPE,'+
  896. 'c.type as COLUMN_DATATYPE,'+
  897. ''''' as COLUMN_TYPENAME,'+
  898. 'usertype as COLUMN_SUBTYPE,'+
  899. 'prec as COLUMN_PRECISION,'+
  900. 'scale as COLUMN_SCALE,'+
  901. 'length as COLUMN_LENGTH,'+
  902. 'case when c.status&8=8 then 1 else 0 end as COLUMN_NULLABLE '+
  903. 'from syscolumns c join sysobjects o on c.id=o.id '+
  904. 'where c.id=object_id(''' + SchemaObjectName + ''') '+
  905. 'order by colid';
  906. else Result := inherited;
  907. end;
  908. end;
  909. function TMSSQLConnection.GetConnectionInfo(InfoType: TConnInfoType): string;
  910. const
  911. SERVER_TYPE: array[boolean] of string = ('Microsoft SQL Server', 'ASE'); // product_name returned in TDS login token; same like ODBC SQL_DBMS_NAME
  912. begin
  913. Result:='';
  914. try
  915. InitialiseDBLib(DBLibLibraryName);
  916. case InfoType of
  917. citServerType:
  918. Result:=SERVER_TYPE[IsSybase];
  919. citServerVersion:
  920. if Connected then
  921. Result:=FServerInfo.ServerVersion;
  922. citServerVersionString:
  923. if Connected then
  924. Result:=FServerInfo.ServerVersionString;
  925. citClientName:
  926. Result:=TMSSQLConnectionDef.LoadedLibraryName;
  927. else
  928. Result:=inherited GetConnectionInfo(InfoType);
  929. end;
  930. finally
  931. ReleaseDBLib;
  932. end;
  933. end;
  934. { TMSSQLConnectionDef }
  935. class function TMSSQLConnectionDef.TypeName: String;
  936. begin
  937. Result:='MSSQLServer';
  938. end;
  939. class function TMSSQLConnectionDef.ConnectionClass: TSQLConnectionClass;
  940. begin
  941. Result:=TMSSQLConnection;
  942. end;
  943. class function TMSSQLConnectionDef.Description: String;
  944. begin
  945. Result:='Connect to MS SQL Server via Microsoft client library or via FreeTDS db-lib';
  946. end;
  947. class function TMSSQLConnectionDef.DefaultLibraryName: String;
  948. begin
  949. Result:=DBLibLibraryName;
  950. end;
  951. class function TMSSQLConnectionDef.LoadFunction: TLibraryLoadFunction;
  952. begin
  953. Result:=@InitialiseDBLib;
  954. end;
  955. class function TMSSQLConnectionDef.UnLoadFunction: TLibraryUnLoadFunction;
  956. begin
  957. Result:=@ReleaseDBLib;
  958. end;
  959. class function TMSSQLConnectionDef.LoadedLibraryName: string;
  960. begin
  961. Result:=DBLibLoadedLibrary;
  962. end;
  963. { TSybaseConnectionDef }
  964. class function TSybaseConnectionDef.TypeName: String;
  965. begin
  966. Result:='Sybase';
  967. end;
  968. class function TSybaseConnectionDef.ConnectionClass: TSQLConnectionClass;
  969. begin
  970. Result:=TSybaseConnection;
  971. end;
  972. class function TSybaseConnectionDef.Description: String;
  973. begin
  974. Result:='Connect to Sybase SQL Server via FreeTDS db-lib';;
  975. end;
  976. initialization
  977. RegisterConnection(TMSSQLConnectionDef);
  978. RegisterConnection(TSybaseConnectionDef);
  979. finalization
  980. UnRegisterConnection(TMSSQLConnectionDef);
  981. UnRegisterConnection(TSybaseConnectionDef);
  982. end.