SQLSrvStatement.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. <?php
  2. namespace Doctrine\DBAL\Driver\SQLSrv;
  3. use Doctrine\DBAL\Driver\FetchUtils;
  4. use Doctrine\DBAL\Driver\Result;
  5. use Doctrine\DBAL\Driver\SQLSrv\Exception\Error;
  6. use Doctrine\DBAL\Driver\Statement as StatementInterface;
  7. use Doctrine\DBAL\Driver\StatementIterator;
  8. use Doctrine\DBAL\FetchMode;
  9. use Doctrine\DBAL\ParameterType;
  10. use IteratorAggregate;
  11. use PDO;
  12. use function array_key_exists;
  13. use function count;
  14. use function func_get_args;
  15. use function in_array;
  16. use function is_int;
  17. use function is_numeric;
  18. use function sqlsrv_errors;
  19. use function sqlsrv_execute;
  20. use function sqlsrv_fetch;
  21. use function sqlsrv_fetch_array;
  22. use function sqlsrv_fetch_object;
  23. use function sqlsrv_get_field;
  24. use function sqlsrv_next_result;
  25. use function sqlsrv_num_fields;
  26. use function SQLSRV_PHPTYPE_STREAM;
  27. use function SQLSRV_PHPTYPE_STRING;
  28. use function sqlsrv_prepare;
  29. use function sqlsrv_rows_affected;
  30. use function SQLSRV_SQLTYPE_VARBINARY;
  31. use function stripos;
  32. use const SQLSRV_ENC_BINARY;
  33. use const SQLSRV_ENC_CHAR;
  34. use const SQLSRV_ERR_ERRORS;
  35. use const SQLSRV_FETCH_ASSOC;
  36. use const SQLSRV_FETCH_BOTH;
  37. use const SQLSRV_FETCH_NUMERIC;
  38. use const SQLSRV_PARAM_IN;
  39. /**
  40. * SQL Server Statement.
  41. *
  42. * @deprecated Use {@link Statement} instead
  43. */
  44. class SQLSrvStatement implements IteratorAggregate, StatementInterface, Result
  45. {
  46. /**
  47. * The SQLSRV Resource.
  48. *
  49. * @var resource
  50. */
  51. private $conn;
  52. /**
  53. * The SQL statement to execute.
  54. *
  55. * @var string
  56. */
  57. private $sql;
  58. /**
  59. * The SQLSRV statement resource.
  60. *
  61. * @var resource|null
  62. */
  63. private $stmt;
  64. /**
  65. * References to the variables bound as statement parameters.
  66. *
  67. * @var mixed
  68. */
  69. private $variables = [];
  70. /**
  71. * Bound parameter types.
  72. *
  73. * @var int[]
  74. */
  75. private $types = [];
  76. /**
  77. * Translations.
  78. *
  79. * @var int[]
  80. */
  81. private static $fetchMap = [
  82. FetchMode::MIXED => SQLSRV_FETCH_BOTH,
  83. FetchMode::ASSOCIATIVE => SQLSRV_FETCH_ASSOC,
  84. FetchMode::NUMERIC => SQLSRV_FETCH_NUMERIC,
  85. ];
  86. /**
  87. * The name of the default class to instantiate when fetching class instances.
  88. *
  89. * @var string
  90. */
  91. private $defaultFetchClass = '\stdClass';
  92. /**
  93. * The constructor arguments for the default class to instantiate when fetching class instances.
  94. *
  95. * @var mixed[]
  96. */
  97. private $defaultFetchClassCtorArgs = [];
  98. /**
  99. * The fetch style.
  100. *
  101. * @var int
  102. */
  103. private $defaultFetchMode = FetchMode::MIXED;
  104. /**
  105. * The last insert ID.
  106. *
  107. * @var LastInsertId|null
  108. */
  109. private $lastInsertId;
  110. /**
  111. * Indicates whether the statement is in the state when fetching results is possible
  112. *
  113. * @var bool
  114. */
  115. private $result = false;
  116. /**
  117. * Append to any INSERT query to retrieve the last insert id.
  118. *
  119. * @deprecated This constant has been deprecated and will be made private in 3.0
  120. */
  121. public const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;';
  122. /**
  123. * @internal The statement can be only instantiated by its driver connection.
  124. *
  125. * @param resource $conn
  126. * @param string $sql
  127. */
  128. public function __construct($conn, $sql, ?LastInsertId $lastInsertId = null)
  129. {
  130. $this->conn = $conn;
  131. $this->sql = $sql;
  132. if (stripos($sql, 'INSERT INTO ') !== 0) {
  133. return;
  134. }
  135. $this->sql .= self::LAST_INSERT_ID_SQL;
  136. $this->lastInsertId = $lastInsertId;
  137. }
  138. /**
  139. * {@inheritdoc}
  140. */
  141. public function bindValue($param, $value, $type = ParameterType::STRING)
  142. {
  143. if (! is_numeric($param)) {
  144. throw new SQLSrvException(
  145. 'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.'
  146. );
  147. }
  148. $this->variables[$param] = $value;
  149. $this->types[$param] = $type;
  150. return true;
  151. }
  152. /**
  153. * {@inheritdoc}
  154. */
  155. public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
  156. {
  157. if (! is_numeric($param)) {
  158. throw new SQLSrvException(
  159. 'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.'
  160. );
  161. }
  162. $this->variables[$param] =& $variable;
  163. $this->types[$param] = $type;
  164. // unset the statement resource if it exists as the new one will need to be bound to the new variable
  165. $this->stmt = null;
  166. return true;
  167. }
  168. /**
  169. * {@inheritdoc}
  170. *
  171. * @deprecated Use free() instead.
  172. */
  173. public function closeCursor()
  174. {
  175. $this->free();
  176. return true;
  177. }
  178. /**
  179. * {@inheritdoc}
  180. */
  181. public function columnCount()
  182. {
  183. if ($this->stmt === null) {
  184. return 0;
  185. }
  186. return sqlsrv_num_fields($this->stmt) ?: 0;
  187. }
  188. /**
  189. * {@inheritdoc}
  190. *
  191. * @deprecated The error information is available via exceptions.
  192. */
  193. public function errorCode()
  194. {
  195. $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
  196. if ($errors) {
  197. return $errors[0]['code'];
  198. }
  199. return false;
  200. }
  201. /**
  202. * {@inheritdoc}
  203. *
  204. * @deprecated The error information is available via exceptions.
  205. */
  206. public function errorInfo()
  207. {
  208. return (array) sqlsrv_errors(SQLSRV_ERR_ERRORS);
  209. }
  210. /**
  211. * {@inheritdoc}
  212. */
  213. public function execute($params = null)
  214. {
  215. if ($params) {
  216. $hasZeroIndex = array_key_exists(0, $params);
  217. foreach ($params as $key => $val) {
  218. if ($hasZeroIndex && is_int($key)) {
  219. $this->bindValue($key + 1, $val);
  220. } else {
  221. $this->bindValue($key, $val);
  222. }
  223. }
  224. }
  225. if (! $this->stmt) {
  226. $this->stmt = $this->prepare();
  227. }
  228. if (! sqlsrv_execute($this->stmt)) {
  229. throw Error::new();
  230. }
  231. if ($this->lastInsertId) {
  232. sqlsrv_next_result($this->stmt);
  233. sqlsrv_fetch($this->stmt);
  234. $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
  235. }
  236. $this->result = true;
  237. return true;
  238. }
  239. /**
  240. * Prepares SQL Server statement resource
  241. *
  242. * @return resource
  243. *
  244. * @throws SQLSrvException
  245. */
  246. private function prepare()
  247. {
  248. $params = [];
  249. foreach ($this->variables as $column => &$variable) {
  250. switch ($this->types[$column]) {
  251. case ParameterType::LARGE_OBJECT:
  252. $params[$column - 1] = [
  253. &$variable,
  254. SQLSRV_PARAM_IN,
  255. SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY),
  256. SQLSRV_SQLTYPE_VARBINARY('max'),
  257. ];
  258. break;
  259. case ParameterType::BINARY:
  260. $params[$column - 1] = [
  261. &$variable,
  262. SQLSRV_PARAM_IN,
  263. SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY),
  264. ];
  265. break;
  266. case ParameterType::ASCII:
  267. $params[$column - 1] = [
  268. &$variable,
  269. SQLSRV_PARAM_IN,
  270. SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR),
  271. ];
  272. break;
  273. default:
  274. $params[$column - 1] =& $variable;
  275. break;
  276. }
  277. }
  278. $stmt = sqlsrv_prepare($this->conn, $this->sql, $params);
  279. if (! $stmt) {
  280. throw Error::new();
  281. }
  282. return $stmt;
  283. }
  284. /**
  285. * {@inheritdoc}
  286. *
  287. * @deprecated Use one of the fetch- or iterate-related methods.
  288. */
  289. public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
  290. {
  291. $this->defaultFetchMode = $fetchMode;
  292. $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass;
  293. $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
  294. return true;
  295. }
  296. /**
  297. * {@inheritdoc}
  298. *
  299. * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead.
  300. */
  301. public function getIterator()
  302. {
  303. return new StatementIterator($this);
  304. }
  305. /**
  306. * {@inheritdoc}
  307. *
  308. * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
  309. *
  310. * @throws SQLSrvException
  311. */
  312. public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
  313. {
  314. // do not try fetching from the statement if it's not expected to contain result
  315. // in order to prevent exceptional situation
  316. if ($this->stmt === null || ! $this->result) {
  317. return false;
  318. }
  319. $args = func_get_args();
  320. $fetchMode = $fetchMode ?: $this->defaultFetchMode;
  321. if ($fetchMode === FetchMode::COLUMN) {
  322. return $this->fetchColumn();
  323. }
  324. if (isset(self::$fetchMap[$fetchMode])) {
  325. return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]) ?: false;
  326. }
  327. if (in_array($fetchMode, [FetchMode::STANDARD_OBJECT, FetchMode::CUSTOM_OBJECT], true)) {
  328. $className = $this->defaultFetchClass;
  329. $ctorArgs = $this->defaultFetchClassCtorArgs;
  330. if (count($args) >= 2) {
  331. $className = $args[1];
  332. $ctorArgs = $args[2] ?? [];
  333. }
  334. return sqlsrv_fetch_object($this->stmt, $className, $ctorArgs) ?: false;
  335. }
  336. throw new SQLSrvException('Fetch mode is not supported!');
  337. }
  338. /**
  339. * {@inheritdoc}
  340. *
  341. * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
  342. */
  343. public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
  344. {
  345. $rows = [];
  346. switch ($fetchMode) {
  347. case FetchMode::CUSTOM_OBJECT:
  348. while (($row = $this->fetch(...func_get_args())) !== false) {
  349. $rows[] = $row;
  350. }
  351. break;
  352. case FetchMode::COLUMN:
  353. while (($row = $this->fetchColumn()) !== false) {
  354. $rows[] = $row;
  355. }
  356. break;
  357. default:
  358. while (($row = $this->fetch($fetchMode)) !== false) {
  359. $rows[] = $row;
  360. }
  361. }
  362. return $rows;
  363. }
  364. /**
  365. * {@inheritdoc}
  366. *
  367. * @deprecated Use fetchOne() instead.
  368. */
  369. public function fetchColumn($columnIndex = 0)
  370. {
  371. $row = $this->fetch(FetchMode::NUMERIC);
  372. if ($row === false) {
  373. return false;
  374. }
  375. return $row[$columnIndex] ?? null;
  376. }
  377. /**
  378. * {@inheritdoc}
  379. */
  380. public function fetchNumeric()
  381. {
  382. return $this->doFetch(SQLSRV_FETCH_NUMERIC);
  383. }
  384. /**
  385. * {@inheritdoc}
  386. */
  387. public function fetchAssociative()
  388. {
  389. return $this->doFetch(SQLSRV_FETCH_ASSOC);
  390. }
  391. /**
  392. * {@inheritdoc}
  393. */
  394. public function fetchOne()
  395. {
  396. return FetchUtils::fetchOne($this);
  397. }
  398. /**
  399. * {@inheritdoc}
  400. */
  401. public function fetchAllNumeric(): array
  402. {
  403. return FetchUtils::fetchAllNumeric($this);
  404. }
  405. /**
  406. * {@inheritdoc}
  407. */
  408. public function fetchAllAssociative(): array
  409. {
  410. return FetchUtils::fetchAllAssociative($this);
  411. }
  412. /**
  413. * {@inheritdoc}
  414. */
  415. public function fetchFirstColumn(): array
  416. {
  417. return FetchUtils::fetchFirstColumn($this);
  418. }
  419. /**
  420. * {@inheritdoc}
  421. */
  422. public function rowCount()
  423. {
  424. if ($this->stmt === null) {
  425. return 0;
  426. }
  427. return sqlsrv_rows_affected($this->stmt) ?: 0;
  428. }
  429. public function free(): void
  430. {
  431. // not having the result means there's nothing to close
  432. if ($this->stmt === null || ! $this->result) {
  433. return;
  434. }
  435. // emulate it by fetching and discarding rows, similarly to what PDO does in this case
  436. // @link http://php.net/manual/en/pdostatement.closecursor.php
  437. // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075
  438. // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
  439. while (sqlsrv_fetch($this->stmt)) {
  440. }
  441. $this->result = false;
  442. }
  443. /**
  444. * @return mixed|false
  445. */
  446. private function doFetch(int $fetchType)
  447. {
  448. // do not try fetching from the statement if it's not expected to contain the result
  449. // in order to prevent exceptional situation
  450. if ($this->stmt === null || ! $this->result) {
  451. return false;
  452. }
  453. return sqlsrv_fetch_array($this->stmt, $fetchType) ?? false;
  454. }
  455. }