mssqlconn.pp 34 KB

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