RequestTrait.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. namespace YdbPlatform\Ydb\Traits;
  3. use Ydb\StatusIds\StatusCode;
  4. use YdbPlatform\Ydb\Issue;
  5. use YdbPlatform\Ydb\Exception;
  6. use YdbPlatform\Ydb\QueryResult;
  7. use YdbPlatform\Ydb\Ydb;
  8. trait RequestTrait
  9. {
  10. /**
  11. * @var string
  12. */
  13. protected $last_request_service;
  14. /**
  15. * @var string
  16. */
  17. protected $last_request_method;
  18. /**
  19. * @var array
  20. */
  21. protected $last_request_data;
  22. /**
  23. * @var int
  24. */
  25. protected $last_request_try_count = 0;
  26. /**
  27. * @var Ydb
  28. */
  29. protected $ydb;
  30. /**
  31. * @var int
  32. */
  33. protected $lastDiscovery = 0;
  34. /**
  35. * Make a request to the service with the given method.
  36. *
  37. * @param string $service
  38. * @param string $method
  39. * @param array $data
  40. * @return bool|mixed|void|null
  41. * @throws Exception
  42. */
  43. protected function doRequest($service, $method, array $data = [])
  44. {
  45. $this->checkDiscovery();
  46. if(!empty($data["skip_get_token"])){
  47. unset($data["skip_get_token"]);
  48. } else {
  49. $this->meta['x-ydb-auth-ticket'] = [$this->credentials->token()];
  50. }
  51. $this->saveLastRequest($service, $method, $data);
  52. $requestClass = '\\Ydb\\' . $service . '\\' . $method . 'Request';
  53. switch ($method) {
  54. case 'BulkUpsert':
  55. case 'CommitTransaction':
  56. case 'RollbackTransaction':
  57. $resultClass = null;
  58. break;
  59. case 'PrepareDataQuery':
  60. $resultClass = '\\Ydb\\' . $service . '\\PrepareQueryResult';
  61. break;
  62. case 'ExecuteDataQuery':
  63. $resultClass = '\\Ydb\\' . $service . '\\ExecuteQueryResult';
  64. break;
  65. case 'ExplainDataQuery':
  66. $resultClass = '\\Ydb\\' . $service . '\\ExplainQueryResult';
  67. break;
  68. default:
  69. $resultClass = '\\Ydb\\' . $service . '\\' . $method . 'Result';
  70. }
  71. $request = new $requestClass($data);
  72. $this->logger()->debug(
  73. 'YDB: Sending API request [' . $requestClass . '].',
  74. json_decode($request->serializeToJsonString(), true)
  75. );
  76. $call = $this->client->$method($request, $this->meta);
  77. if (method_exists($call, 'wait')) {
  78. list($response, $status) = $call->wait();
  79. $this->handleGrpcStatus($service, $method, $status);
  80. return $this->processResponse($service, $method, $response, $resultClass);
  81. }
  82. return null;
  83. }
  84. /**
  85. * Make a stream request to the service with the given method.
  86. *
  87. * @param string $service
  88. * @param string $method
  89. * @param array $data
  90. * @return \Generator
  91. * @throws Exception
  92. */
  93. protected function doStreamRequest($service, $method, $data = [])
  94. {
  95. $this->checkDiscovery();
  96. if(!empty($data["skip_get_token"])){
  97. unset($data["skip_get_token"]);
  98. } else {
  99. $this->meta['x-ydb-auth-ticket'] = [$this->credentials->token()];
  100. }
  101. if (method_exists($this, 'take')) {
  102. $this->take();
  103. }
  104. $requestClass = '\\Ydb\\' . $service . '\\' . $method . 'Request';
  105. switch ($method) {
  106. case 'StreamReadTable':
  107. $requestClass = '\\Ydb\\' . $service . '\\ReadTableRequest';
  108. $resultClass = '\\Ydb\\' . $service . '\\ReadTableResult';
  109. break;
  110. case 'StreamExecuteScanQuery':
  111. $requestClass = '\\Ydb\\' . $service . '\\ExecuteScanQueryRequest';
  112. $resultClass = '\\Ydb\\' . $service . '\\ExecuteScanQueryPartialResult';
  113. break;
  114. default:
  115. $resultClass = '\\Ydb\\' . $service . '\\' . $method . 'Result';
  116. }
  117. $request = new $requestClass($data);
  118. $call = $this->client->$method($request, $this->meta);
  119. if (method_exists($call, 'responses')) {
  120. // $status = $call->getStatus();
  121. // $this->checkStatus($service, $method, $status);
  122. foreach ($call->responses() as $response) {
  123. $result = $this->processResponse($service, $method, $response, $resultClass);
  124. yield $result ? new QueryResult($result) : true;
  125. }
  126. }
  127. if (method_exists($this, 'release')) {
  128. $this->release();
  129. }
  130. }
  131. /**
  132. * Check response status.
  133. *
  134. * @param string $service
  135. * @param string $method
  136. * @param object $status
  137. * @throws Exception
  138. */
  139. protected function handleGrpcStatus($service, $method, $status)
  140. {
  141. if (isset($status->code) && $status->code !== 0) {
  142. $message = 'YDB ' . $service . ' ' . $method . ' (status code GRPC_'.
  143. (isset(self::$grpcExceptions[$status->code])?self::$grpcNames[$status->code]:$status->code)
  144. .' ' . $status->code . '): ' . ($status->details ?? 'no details');
  145. $this->logger->error($message);
  146. if ($this->ydb->needDiscovery()){
  147. try{
  148. $this->ydb->discover();
  149. }catch (\Exception $e){}
  150. }
  151. $endpoint = $this->ydb->endpoint();
  152. if ($this->ydb->needDiscovery() && count($this->ydb->cluster()->all()) > 0){
  153. $endpoint = $this->ydb->cluster()->all()[array_rand($this->ydb->cluster()->all())]->endpoint();
  154. }
  155. $this->client = new $this->client($endpoint,[
  156. 'credentials' => $this->ydb->iam()->getCredentials()
  157. ]);
  158. if (isset(self::$grpcExceptions[$status->code])) {
  159. throw new self::$grpcExceptions[$status->code]($message);
  160. } else {
  161. throw new \Exception($message);
  162. }
  163. }
  164. }
  165. /**
  166. * Process a response from the service.
  167. *
  168. * @param string $service
  169. * @param string $method
  170. * @param object $response
  171. * @param string $resultClass
  172. * @return bool|mixed|void
  173. * @throws Exception
  174. */
  175. protected function processResponse($service, $method, $response, $resultClass)
  176. {
  177. if (method_exists($response, 'getOperation')) {
  178. $response = $response->getOperation();
  179. }
  180. if (!method_exists($response, 'getStatus') || !method_exists($response, 'getResult')) {
  181. return $response;
  182. }
  183. $statusCode = $response->getStatus();
  184. if ($statusCode == StatusCode::SUCCESS) {
  185. $result = $response->getResult();
  186. if ($result === null) {
  187. return true;
  188. }
  189. if (is_object($result)) {
  190. if ($resultClass && class_exists($resultClass)) {
  191. $jsonResult = $result->serializeToJsonString();
  192. $this->logger()->debug('YDB: Received API response [' . $resultClass . '].', json_decode($jsonResult, true));
  193. $result = new $resultClass;
  194. $result->mergeFromJsonString($jsonResult);
  195. }
  196. }
  197. $this->resetLastRequest();
  198. return $result;
  199. }
  200. $statusName = StatusCode::name($statusCode);
  201. $issues = [];
  202. foreach ($response->getIssues() as $issue) {
  203. $issues[] = (new Issue($issue))->toString();
  204. }
  205. $message = implode("\n", $issues);
  206. $this->logger()->error(
  207. 'YDB: Service [' . $service . '] method [' . $method . '] Failed to receive a valid response.',
  208. [
  209. 'status' => $statusCode . ' (' . $statusName . ')',
  210. 'message' => $message,
  211. ]
  212. );
  213. $msg = 'YDB ' . $service . ' ' . $method . ' (YDB_' . $statusCode . ' ' . $statusName . '): ' . $message;
  214. if (isset(self::$ydbExceptions[$statusCode])) {
  215. throw new self::$ydbExceptions[$statusCode]($msg);
  216. } else {
  217. throw new \Exception($msg);
  218. }
  219. }
  220. /**
  221. * Retry the last request.
  222. *
  223. * @param int $sleep
  224. * @throws Exception
  225. */
  226. protected function retryLastRequest($sleep = 100)
  227. {
  228. if ($this->last_request_service && $this->last_request_method) {
  229. $this->logger()->info('Going to retry the last request!');
  230. usleep(max($this->last_request_try_count, 1) * $sleep * 1000); // waiting 100 ms more
  231. return $this->doRequest($this->last_request_service, $this->last_request_method, $this->last_request_data);
  232. }
  233. }
  234. /**
  235. * Save the last request to perform a retry.
  236. *
  237. * @param string $service
  238. * @param method $method
  239. * @param array $data
  240. */
  241. protected function saveLastRequest($service, $method, array $data = [])
  242. {
  243. $this->last_request_service = $service;
  244. $this->last_request_method = $method;
  245. $this->last_request_data = $data;
  246. $this->last_request_try_count++;
  247. }
  248. /**
  249. * Reset the last saved request.
  250. */
  251. protected function resetLastRequest()
  252. {
  253. $this->last_request_service = null;
  254. $this->last_request_method = null;
  255. $this->last_request_data = null;
  256. $this->last_request_try_count = 0;
  257. }
  258. protected function checkDiscovery(){
  259. if ($this->ydb->needDiscovery() && time()-$this->lastDiscovery>$this->ydb->discoveryInterval()){
  260. try{
  261. $this->lastDiscovery = time();
  262. $this->ydb->discover();
  263. } catch (\Exception $e){
  264. }
  265. }
  266. }
  267. public static $ydbExceptions = [
  268. StatusCode::STATUS_CODE_UNSPECIFIED => \YdbPlatform\Ydb\Exceptions\Ydb\StatusCodeUnspecified::class,
  269. StatusCode::BAD_REQUEST => \YdbPlatform\Ydb\Exceptions\Ydb\BadRequestException::class,
  270. StatusCode::UNAUTHORIZED => \YdbPlatform\Ydb\Exceptions\Ydb\UnauthorizedException::class,
  271. StatusCode::INTERNAL_ERROR => \YdbPlatform\Ydb\Exceptions\Ydb\InternalErrorException::class,
  272. StatusCode::ABORTED => \YdbPlatform\Ydb\Exceptions\Ydb\AbortedException::class,
  273. StatusCode::UNAVAILABLE => \YdbPlatform\Ydb\Exceptions\Ydb\UnavailableException::class,
  274. StatusCode::OVERLOADED => \YdbPlatform\Ydb\Exceptions\Ydb\OverloadedException::class,
  275. StatusCode::SCHEME_ERROR => \YdbPlatform\Ydb\Exceptions\Ydb\SchemeErrorException::class,
  276. StatusCode::GENERIC_ERROR => \YdbPlatform\Ydb\Exceptions\Ydb\GenericErrorException::class,
  277. StatusCode::TIMEOUT => \YdbPlatform\Ydb\Exceptions\Ydb\TimeoutException::class,
  278. StatusCode::BAD_SESSION => \YdbPlatform\Ydb\Exceptions\Ydb\BadSessionException::class,
  279. StatusCode::PRECONDITION_FAILED => \YdbPlatform\Ydb\Exceptions\Ydb\PreconditionFailedException::class,
  280. StatusCode::ALREADY_EXISTS => \YdbPlatform\Ydb\Exceptions\Ydb\AlreadyExistsException::class,
  281. StatusCode::NOT_FOUND => \YdbPlatform\Ydb\Exceptions\Ydb\NotFoundException::class,
  282. StatusCode::SESSION_EXPIRED => \YdbPlatform\Ydb\Exceptions\Ydb\SessionExpiredException::class,
  283. StatusCode::CANCELLED => \YdbPlatform\Ydb\Exceptions\Ydb\CancelledException::class,
  284. StatusCode::UNDETERMINED => \YdbPlatform\Ydb\Exceptions\Ydb\UndeterminedException::class,
  285. StatusCode::UNSUPPORTED => \YdbPlatform\Ydb\Exceptions\Ydb\UnsupportedException::class,
  286. StatusCode::SESSION_BUSY => \YdbPlatform\Ydb\Exceptions\Ydb\SessionBusyException::class,
  287. ];
  288. public static $grpcExceptions = [
  289. 1 => \YdbPlatform\Ydb\Exceptions\Grpc\CanceledException::class,
  290. 2 => \YdbPlatform\Ydb\Exceptions\Grpc\UnknownException::class,
  291. 3 => \YdbPlatform\Ydb\Exceptions\Grpc\InvalidArgumentException::class,
  292. 4 => \YdbPlatform\Ydb\Exceptions\Grpc\DeadlineExceededException::class,
  293. 5 => \YdbPlatform\Ydb\Exceptions\Grpc\NotFoundException::class,
  294. 6 => \YdbPlatform\Ydb\Exceptions\Grpc\AlreadyExistsException::class,
  295. 7 => \YdbPlatform\Ydb\Exceptions\Grpc\PermissionDeniedException::class,
  296. 8 => \YdbPlatform\Ydb\Exceptions\Grpc\ResourceExhaustedException::class,
  297. 9 => \YdbPlatform\Ydb\Exceptions\Grpc\FailedPreconditionException::class,
  298. 10 => \YdbPlatform\Ydb\Exceptions\Grpc\AbortedException::class,
  299. 11 => \YdbPlatform\Ydb\Exceptions\Grpc\OutOfRangeException::class,
  300. 12 => \YdbPlatform\Ydb\Exceptions\Grpc\UnimplementedException::class,
  301. 13 => \YdbPlatform\Ydb\Exceptions\Grpc\InternalException::class,
  302. 14 => \YdbPlatform\Ydb\Exceptions\Grpc\UnavailableException::class,
  303. 15 => \YdbPlatform\Ydb\Exceptions\Grpc\DataLossException::class,
  304. 16 => \YdbPlatform\Ydb\Exceptions\Grpc\UnauthenticatedException::class
  305. ];
  306. public static $grpcNames = [
  307. 1 => "CANCELLED",
  308. 2 => "UNKNOWN",
  309. 3 => "INVALID_ARGUMENT",
  310. 4 => "DEADLINE_EXCEEDED",
  311. 5 => "NOT_FOUND",
  312. 6 => "ALREADY_EXISTS",
  313. 7 => "PERMISSION_DENIED",
  314. 8 => "RESOURCE_EXHAUSTED",
  315. 9 => "FAILED_PRECONDITION",
  316. 10 => "ABORTED",
  317. 11 => "OUT_OF_RANGE",
  318. 12 => "UNIMPLEMENTED",
  319. 13 => "INTERNAL",
  320. 14 => "UNAVAILABLE",
  321. 15 => "DATA_LOSS",
  322. 16 => "UNAUTHENTICATED"
  323. ];
  324. }