mssqlconn.pp 30 KB

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