SQLAnywhereStatement.php 9.9 KB

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