SQLAnywhereStatement.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. <?php
  2. namespace Doctrine\DBAL\Driver\SQLAnywhere;
  3. use Doctrine\DBAL\Driver\Exception;
  4. use Doctrine\DBAL\Driver\FetchUtils;
  5. use Doctrine\DBAL\Driver\Result;
  6. use Doctrine\DBAL\Driver\Statement;
  7. use Doctrine\DBAL\Driver\StatementIterator;
  8. use Doctrine\DBAL\FetchMode;
  9. use Doctrine\DBAL\ParameterType;
  10. use IteratorAggregate;
  11. use PDO;
  12. use ReflectionClass;
  13. use ReflectionObject;
  14. use stdClass;
  15. use function array_key_exists;
  16. use function assert;
  17. use function func_get_args;
  18. use function func_num_args;
  19. use function gettype;
  20. use function is_array;
  21. use function is_int;
  22. use function is_object;
  23. use function is_resource;
  24. use function is_string;
  25. use function sasql_fetch_array;
  26. use function sasql_fetch_assoc;
  27. use function sasql_fetch_object;
  28. use function sasql_fetch_row;
  29. use function sasql_prepare;
  30. use function sasql_stmt_affected_rows;
  31. use function sasql_stmt_bind_param_ex;
  32. use function sasql_stmt_errno;
  33. use function sasql_stmt_error;
  34. use function sasql_stmt_execute;
  35. use function sasql_stmt_field_count;
  36. use function sasql_stmt_reset;
  37. use function sasql_stmt_result_metadata;
  38. use function sprintf;
  39. use const SASQL_BOTH;
  40. /**
  41. * SAP SQL Anywhere implementation of the Statement interface.
  42. */
  43. class SQLAnywhereStatement implements IteratorAggregate, Statement, Result
  44. {
  45. /** @var resource The connection resource. */
  46. private $conn;
  47. /** @var string Name of the default class to instantiate when fetching class instances. */
  48. private $defaultFetchClass = '\stdClass';
  49. /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
  50. private $defaultFetchClassCtorArgs = [];
  51. /** @var int Default fetch mode to use. */
  52. private $defaultFetchMode = FetchMode::MIXED;
  53. /** @var resource|null The result set resource to fetch. */
  54. private $result;
  55. /** @var resource The prepared SQL statement to execute. */
  56. private $stmt;
  57. /** @var mixed[] The references to bound parameter values. */
  58. private $boundValues = [];
  59. /**
  60. * Prepares given statement for given connection.
  61. *
  62. * @internal The statement can be only instantiated by its driver connection.
  63. *
  64. * @param resource $conn The connection resource to use.
  65. * @param string $sql The SQL statement to prepare.
  66. *
  67. * @throws SQLAnywhereException
  68. */
  69. public function __construct($conn, $sql)
  70. {
  71. if (! is_resource($conn)) {
  72. throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn);
  73. }
  74. $this->conn = $conn;
  75. $this->stmt = sasql_prepare($conn, $sql);
  76. if (! is_resource($this->stmt)) {
  77. throw SQLAnywhereException::fromSQLAnywhereError($conn);
  78. }
  79. }
  80. /**
  81. * {@inheritdoc}
  82. *
  83. * @throws SQLAnywhereException
  84. */
  85. public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
  86. {
  87. assert(is_int($param));
  88. switch ($type) {
  89. case ParameterType::INTEGER:
  90. case ParameterType::BOOLEAN:
  91. $type = 'i';
  92. break;
  93. case ParameterType::LARGE_OBJECT:
  94. $type = 'b';
  95. break;
  96. case ParameterType::NULL:
  97. case ParameterType::STRING:
  98. case ParameterType::BINARY:
  99. $type = 's';
  100. break;
  101. default:
  102. throw new SQLAnywhereException('Unknown type: ' . $type);
  103. }
  104. $this->boundValues[$param] =& $variable;
  105. if (! sasql_stmt_bind_param_ex($this->stmt, $param - 1, $variable, $type, $variable === null)) {
  106. throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
  107. }
  108. return true;
  109. }
  110. /**
  111. * {@inheritdoc}
  112. */
  113. public function bindValue($param, $value, $type = ParameterType::STRING)
  114. {
  115. assert(is_int($param));
  116. return $this->bindParam($param, $value, $type);
  117. }
  118. /**
  119. * {@inheritdoc}
  120. *
  121. * @deprecated Use free() instead.
  122. *
  123. * @throws SQLAnywhereException
  124. */
  125. public function closeCursor()
  126. {
  127. if (! sasql_stmt_reset($this->stmt)) {
  128. throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
  129. }
  130. return true;
  131. }
  132. /**
  133. * {@inheritdoc}
  134. */
  135. public function columnCount()
  136. {
  137. return sasql_stmt_field_count($this->stmt);
  138. }
  139. /**
  140. * {@inheritdoc}
  141. *
  142. * @deprecated The error information is available via exceptions.
  143. */
  144. public function errorCode()
  145. {
  146. return sasql_stmt_errno($this->stmt);
  147. }
  148. /**
  149. * {@inheritdoc}
  150. *
  151. * @deprecated The error information is available via exceptions.
  152. */
  153. public function errorInfo()
  154. {
  155. return sasql_stmt_error($this->stmt);
  156. }
  157. /**
  158. * {@inheritdoc}
  159. *
  160. * @throws SQLAnywhereException
  161. */
  162. public function execute($params = null)
  163. {
  164. if (is_array($params)) {
  165. $hasZeroIndex = array_key_exists(0, $params);
  166. foreach ($params as $key => $val) {
  167. if ($hasZeroIndex && is_int($key)) {
  168. $this->bindValue($key + 1, $val);
  169. } else {
  170. $this->bindValue($key, $val);
  171. }
  172. }
  173. }
  174. if (! sasql_stmt_execute($this->stmt)) {
  175. throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
  176. }
  177. $this->result = sasql_stmt_result_metadata($this->stmt);
  178. return true;
  179. }
  180. /**
  181. * {@inheritdoc}
  182. *
  183. * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
  184. *
  185. * @throws SQLAnywhereException
  186. */
  187. public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
  188. {
  189. if (! is_resource($this->result)) {
  190. return false;
  191. }
  192. $fetchMode = $fetchMode ?: $this->defaultFetchMode;
  193. switch ($fetchMode) {
  194. case FetchMode::COLUMN:
  195. return $this->fetchColumn();
  196. case FetchMode::ASSOCIATIVE:
  197. return sasql_fetch_assoc($this->result);
  198. case FetchMode::MIXED:
  199. return sasql_fetch_array($this->result, SASQL_BOTH);
  200. case FetchMode::CUSTOM_OBJECT:
  201. $className = $this->defaultFetchClass;
  202. $ctorArgs = $this->defaultFetchClassCtorArgs;
  203. if (func_num_args() >= 2) {
  204. $args = func_get_args();
  205. $className = $args[1];
  206. $ctorArgs = $args[2] ?? [];
  207. }
  208. $result = sasql_fetch_object($this->result);
  209. if ($result instanceof stdClass) {
  210. $result = $this->castObject($result, $className, $ctorArgs);
  211. }
  212. return $result;
  213. case FetchMode::NUMERIC:
  214. return sasql_fetch_row($this->result);
  215. case FetchMode::STANDARD_OBJECT:
  216. return sasql_fetch_object($this->result);
  217. default:
  218. throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode);
  219. }
  220. }
  221. /**
  222. * {@inheritdoc}
  223. *
  224. * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
  225. */
  226. public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
  227. {
  228. $rows = [];
  229. switch ($fetchMode) {
  230. case FetchMode::CUSTOM_OBJECT:
  231. while (($row = $this->fetch(...func_get_args())) !== false) {
  232. $rows[] = $row;
  233. }
  234. break;
  235. case FetchMode::COLUMN:
  236. while (($row = $this->fetchColumn()) !== false) {
  237. $rows[] = $row;
  238. }
  239. break;
  240. default:
  241. while (($row = $this->fetch($fetchMode)) !== false) {
  242. $rows[] = $row;
  243. }
  244. }
  245. return $rows;
  246. }
  247. /**
  248. * {@inheritdoc}
  249. *
  250. * @deprecated Use fetchOne() instead.
  251. */
  252. public function fetchColumn($columnIndex = 0)
  253. {
  254. $row = $this->fetch(FetchMode::NUMERIC);
  255. if ($row === false) {
  256. return false;
  257. }
  258. return $row[$columnIndex] ?? null;
  259. }
  260. /**
  261. * {@inheritdoc}
  262. *
  263. * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead.
  264. */
  265. public function getIterator()
  266. {
  267. return new StatementIterator($this);
  268. }
  269. /**
  270. * {@inheritDoc}
  271. */
  272. public function fetchNumeric()
  273. {
  274. if (! is_resource($this->result)) {
  275. return false;
  276. }
  277. return sasql_fetch_row($this->result);
  278. }
  279. /**
  280. * {@inheritdoc}
  281. */
  282. public function fetchAssociative()
  283. {
  284. if (! is_resource($this->result)) {
  285. return false;
  286. }
  287. return sasql_fetch_assoc($this->result);
  288. }
  289. /**
  290. * {@inheritdoc}
  291. *
  292. * @throws Exception
  293. */
  294. public function fetchOne()
  295. {
  296. return FetchUtils::fetchOne($this);
  297. }
  298. /**
  299. * @return array<int,array<int,mixed>>
  300. *
  301. * @throws Exception
  302. */
  303. public function fetchAllNumeric(): array
  304. {
  305. return FetchUtils::fetchAllNumeric($this);
  306. }
  307. /**
  308. * @return array<int,array<string,mixed>>
  309. *
  310. * @throws Exception
  311. */
  312. public function fetchAllAssociative(): array
  313. {
  314. return FetchUtils::fetchAllAssociative($this);
  315. }
  316. /**
  317. * @return array<int,mixed>
  318. *
  319. * @throws Exception
  320. */
  321. public function fetchFirstColumn(): array
  322. {
  323. return FetchUtils::fetchFirstColumn($this);
  324. }
  325. /**
  326. * {@inheritdoc}
  327. */
  328. public function rowCount()
  329. {
  330. return sasql_stmt_affected_rows($this->stmt);
  331. }
  332. public function free(): void
  333. {
  334. sasql_stmt_reset($this->stmt);
  335. }
  336. /**
  337. * {@inheritdoc}
  338. *
  339. * @deprecated Use one of the fetch- or iterate-related methods.
  340. */
  341. public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
  342. {
  343. $this->defaultFetchMode = $fetchMode;
  344. $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass;
  345. $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
  346. return true;
  347. }
  348. /**
  349. * Casts a stdClass object to the given class name mapping its' properties.
  350. *
  351. * @param stdClass $sourceObject Object to cast from.
  352. * @param class-string|object $destinationClass Name of the class or class instance to cast to.
  353. * @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance.
  354. *
  355. * @return object
  356. *
  357. * @throws SQLAnywhereException
  358. */
  359. private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
  360. {
  361. if (! is_string($destinationClass)) {
  362. if (! is_object($destinationClass)) {
  363. throw new SQLAnywhereException(sprintf(
  364. 'Destination class has to be of type string or object, %s given.',
  365. gettype($destinationClass)
  366. ));
  367. }
  368. } else {
  369. $destinationClass = new ReflectionClass($destinationClass);
  370. $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
  371. }
  372. $sourceReflection = new ReflectionObject($sourceObject);
  373. $destinationClassReflection = new ReflectionObject($destinationClass);
  374. foreach ($sourceReflection->getProperties() as $sourceProperty) {
  375. $sourceProperty->setAccessible(true);
  376. $name = $sourceProperty->getName();
  377. $value = $sourceProperty->getValue($sourceObject);
  378. if ($destinationClassReflection->hasProperty($name)) {
  379. $destinationProperty = $destinationClassReflection->getProperty($name);
  380. $destinationProperty->setAccessible(true);
  381. $destinationProperty->setValue($destinationClass, $value);
  382. } else {
  383. $destinationClass->$name = $value;
  384. }
  385. }
  386. return $destinationClass;
  387. }
  388. }