PageRenderTime 26ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/core/src/main/php/rdbms/tds/TdsV5Protocol.class.php

https://github.com/treuter/xp-framework
PHP | 263 lines | 193 code | 15 blank | 55 comment | 35 complexity | b02931fc751ed2acf1c46deff4d0d7ed MD5 | raw file
  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. uses('rdbms.tds.TdsProtocol');
  7. /**
  8. * TDS V5 protocol implementation
  9. *
  10. * @see https://github.com/mono/mono/blob/master/mcs/class/Mono.Data.Tds/Mono.Data.Tds.Protocol/Tds50.cs
  11. */
  12. class TdsV5Protocol extends TdsProtocol {
  13. protected $servercs= 'cp850';
  14. static function __static() { }
  15. /**
  16. * Setup record handlers
  17. *
  18. * @see http://infocenter.sybase.com/help/topic/com.sybase.dc35823_1500/html/uconfig/uconfig111.htm
  19. * @see http://infocenter.sybase.com/help/topic/com.sybase.dc38421_1500/html/ntconfig/ntconfig80.htm
  20. * @return [:rdbms.tds.TdsRecord] handlers
  21. */
  22. protected function setupRecords() {
  23. $records[self::T_NUMERIC]= newinstance('rdbms.tds.TdsRecord', array(), '{
  24. public function unmarshal($stream, $field, $records) {
  25. if (-1 === ($len= $stream->getByte()- 1)) return NULL;
  26. $pos= $stream->getByte();
  27. $bytes= $stream->read($len);
  28. if ($i= ($len % 4)) {
  29. $bytes= str_repeat("\0", 4 - $i).$bytes;
  30. $len+= 4 - $i;
  31. }
  32. for ($n= 0, $m= $pos ? -1 : 1, $i= $len- 4; $i >= 0; $i-= 4, $m= bcmul($m, "4294967296", 0)) {
  33. $n= bcadd($n, bcmul(sprintf("%u", current(unpack("N", substr($bytes, $i, 4)))), $m, 0), 0);
  34. }
  35. return $this->toNumber($n, $field["scale"], $field["prec"]);
  36. }
  37. }');
  38. $records[self::T_DECIMAL]= $records[self::T_NUMERIC];
  39. $records[self::T_BINARY]= newinstance('rdbms.tds.TdsRecord', array(), '{
  40. public function unmarshal($stream, $field, $records) {
  41. if (0 === ($len= $stream->getByte())) return NULL;
  42. $string= $stream->read($len);
  43. return iconv($field["conv"], "iso-8859-1", substr($string, 0, strcspn($string, "\0")));
  44. }
  45. }');
  46. $records[self::T_IMAGE]= newinstance('rdbms.tds.TdsRecord', array(), '{
  47. public function unmarshal($stream, $field, $records) {
  48. $has= $stream->getByte();
  49. if ($has !== 16) return NULL; // Seems to always be 16 - obsolete?
  50. $stream->read(24); // Skip 16 Byte TEXTPTR, 8 Byte TIMESTAMP
  51. $len= $stream->getLong();
  52. if (0 === $len) return NULL;
  53. $r= $stream->read($len);
  54. // HACK - cannot figure out why UNITEXT is not being returned as such
  55. // but as IMAGE type with different inside layout!
  56. return iconv(
  57. strlen($r) > 1 && "\0" === $r{1} ? "ucs-2le" : $field["conv"],
  58. "iso-8859-1",
  59. $r
  60. );
  61. }
  62. }');
  63. $records[self::T_VARBINARY]= newinstance('rdbms.tds.TdsRecord', array(), '{
  64. public function unmarshal($stream, $field, $records) {
  65. if (0 === ($len= $stream->getByte())) return NULL;
  66. return iconv($field["conv"], "iso-8859-1", $stream->read($len));
  67. }
  68. }');
  69. $records[self::T_LONGBINARY]= newinstance('rdbms.tds.TdsRecord', array(), '{
  70. public function unmarshal($stream, $field, $records) {
  71. $len= $stream->getLong();
  72. return $stream->getString($len / 2);
  73. }
  74. }');
  75. return $records;
  76. }
  77. /**
  78. * Returns default packet size to use
  79. *
  80. * @return int
  81. */
  82. protected function defaultPacketSize() {
  83. return 512;
  84. }
  85. /**
  86. * Connect
  87. *
  88. * @param string user
  89. * @param string password
  90. * @throws io.IOException
  91. */
  92. protected function login($user, $password) {
  93. if (strlen($password) > 253) {
  94. throw new IllegalArgumentException('Password length must not exceed 253 bytes.');
  95. }
  96. $packetSize= (string)$this->defaultPacketSize();
  97. $packet= pack(
  98. 'a30Ca30Ca30Ca30CCCCCCCCCCx7a30Ca30Cx2a253CCCCCa10CCCCCCCCa30CCnx8nCa30CCa6Cx8',
  99. 'localhost', min(30, strlen('localhost')),
  100. $user, min(30, strlen($user)),
  101. $password, min(30, strlen($password)),
  102. (string)getmypid(), min(30, strlen(getmypid())),
  103. 0x03, // Byte order for 2 byte ints: 2 = <MSB, LSB>, 3 = <LSB, MSB>
  104. 0x01, // Byte order for 4 byte ints: 0 = <MSB, LSB>, 1 = <LSB, MSB>
  105. 0x06, // Character rep (6 = ASCII, 7 = EBCDIC)
  106. 0x0A, // Eight byte floating point rep (10 = IEEE <LSB, ..., MSB>)
  107. 0x09, // Eight byte date format (8 = <MSB, ..., LSB>)
  108. 0x01, // Notify of "use db"
  109. 0x01, // Disallow dump/load and bulk insert
  110. 0x00, // SQL Interface type
  111. 0x00, // Type of network connection
  112. $this->getClassName(), min(30, strlen($this->getClassName())),
  113. 'localhost', min(30, strlen('localhost')),
  114. $password, strlen($password)+ 2, // Remote passwords
  115. 0x05, 0x00, 0x00, 0x00, // TDS Version
  116. 'Ct-Library', strlen('Ct-Library'), // Client library name
  117. 0x06, 0x00, 0x00, 0x00, // Prog version
  118. 0x00, // Auto convert short
  119. 0x0D, // Type of flt4
  120. 0x11, // Type of date4
  121. 'us_english', strlen('us_english'), // Language
  122. 0x00, // Notify on lang change
  123. 0x00, // Security label hierarchy
  124. 0x00, // Security spare
  125. 0x00, // Security login role
  126. 'iso_1', strlen('iso_1'), // Charset
  127. 0x01, // Notify on charset change
  128. $packetSize, strlen($packetSize) // Network packet size (in text!)
  129. );
  130. // Capabilities
  131. $capabilities= pack(
  132. 'CnCCCCCCCCCCCCCCCCCCCCCCCC',
  133. 0xE2, // TDS_CAPABILITY_TOKEN
  134. 24, // Length
  135. 0x01, 0x0C, // TDS_CAP_REQUEST & Length
  136. 0x07, 0x4F, 0xFF, 0x85, 0xEE, 0xEF, 0x65, 0x7F, 0xFF, 0xFF, 0xFF, 0xD6,
  137. 0x02, 0x08, // TDS_CAP_RESPONSE & Length
  138. 0x00, 0x06, 0x80, 0x06, 0x48, 0x00, 0x00, 0x00
  139. );
  140. // Login
  141. $this->stream->write(self::MSG_LOGIN, $packet.$capabilities);
  142. }
  143. /**
  144. * Handle ENVCHANGE
  145. *
  146. * @param int type
  147. * @param string old
  148. * @param string new
  149. * @param bool initial if this ENVCHANGE was part of the login response
  150. */
  151. protected function handleEnvChange($type, $old, $new, $initial= FALSE) {
  152. if ($initial && 3 === $type) {
  153. $this->servercs= strtr($old, array('iso_' => 'iso-8859-', 'utf8' => 'utf-8'));
  154. }
  155. // DEBUG Console::writeLine($initial ? 'I' : 'E', $type, ' ', $old, ' -> ', $new);
  156. }
  157. /**
  158. * Issues a query and returns the results
  159. *
  160. * @param string sql
  161. * @return var
  162. */
  163. public function query($sql) {
  164. $this->stream->write(self::MSG_QUERY, $sql);
  165. $token= $this->read();
  166. // Skip over DONEPROC & DONEINPROC results
  167. do {
  168. if ("\x00" === $token || "\x02" === $token) {
  169. // Tokens encountered in some situations, seem to be inserted after a certain number
  170. // of rows, we need to continue reading in these cases (if we don't, we experience
  171. // issues like https://github.com/xp-framework/xp-framework/issues/305). Examples:
  172. //
  173. // packet header * token * data
  174. // ----------------------- * ----- * -----------
  175. // 04 01 00 0A 00 00 00 00 * 00 00 *
  176. // 04 01 00 0E 00 00 00 00 * 02 00 * 19 00 00 00
  177. $token= $this->read();
  178. continue;
  179. } else if ("\xEE" === $token) { // TDS_ROWFMT
  180. $fields= array();
  181. $this->stream->getShort();
  182. $nfields= $this->stream->getShort();
  183. for ($i= 0; $i < $nfields; $i++) {
  184. $field= array();
  185. if (0 === ($len= $this->stream->getByte())) {
  186. $field= array('name' => NULL);
  187. } else {
  188. $field= array('name' => $this->stream->read($len));
  189. }
  190. $field['status']= $this->stream->getByte();
  191. $this->stream->read(4); // Skip usertype
  192. $field['type']= $this->stream->getByte();
  193. // Handle column.
  194. if (self::T_TEXT === $field['type'] || self::T_IMAGE === $field['type']) {
  195. $field['size']= $this->stream->getLong();
  196. $field['conv']= $this->servercs;
  197. $this->stream->read($this->stream->getShort());
  198. } else if (self::T_NUMERIC === $field['type'] || self::T_DECIMAL === $field['type']) {
  199. $field['size']= $this->stream->getByte();
  200. $field['prec']= $this->stream->getByte();
  201. $field['scale']= $this->stream->getByte();
  202. } else if (self::T_LONGBINARY === $field['type'] || self::XT_CHAR === $field['type']) {
  203. $field['size']= $this->stream->getLong() / 2;
  204. } else if (isset(self::$fixed[$field['type']])) {
  205. $field['size']= self::$fixed[$field['type']];
  206. } else if (self::T_VARBINARY === $field['type'] || self::T_BINARY === $field['type']) {
  207. $field['size']= $this->stream->getByte();
  208. $field['conv']= $this->servercs;
  209. } else {
  210. $field['size']= $this->stream->getByte();
  211. }
  212. $field['locale']= $this->stream->getByte();
  213. $fields[]= $field;
  214. }
  215. return $fields;
  216. } else if ("\xFD" === $token || "\xFF" === $token || "\xFE" === $token) { // DONE
  217. $meta= $this->stream->get('vstatus/vcmd/Vrowcount', 8);
  218. if ($meta['status'] & 0x0001) {
  219. $token= $this->stream->getToken();
  220. continue;
  221. }
  222. $this->done= TRUE;
  223. return $meta['rowcount'];
  224. } else if ("\xE5" === $token) { // EED (messages or errors)
  225. $this->handleExtendedError();
  226. $token= $this->stream->getToken();
  227. } else if ("\xE3" === $token) { // ENVCHANGE, e.g. from "use [db]" queries
  228. $this->envchange();
  229. return NULL;
  230. } else {
  231. throw new TdsProtocolException(
  232. sprintf('Unexpected token 0x%02X', ord($token)),
  233. 0, // Number
  234. 0, // State
  235. 0, // Class
  236. NULL, // Server
  237. NULL, // Proc
  238. -1 // Line
  239. );
  240. }
  241. } while (!$this->done);
  242. }
  243. }
  244. ?>