mssqlconn.pp 27 KB

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