From 2f0e5c9768eadafed845713ecf6d005bf1044189 Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Sat, 1 Apr 2023 10:06:42 -0500 Subject: [PATCH] Added v1 Queries --- src/AzureDataExplorerApi.php | 35 ++++++++++- src/Connectors/DataExplorerConnector.php | 4 +- src/Data/QueryResultsDTO.php | 32 +++++++++- src/Requests/QueryV1Request.php | 76 ++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 src/Requests/QueryV1Request.php diff --git a/src/AzureDataExplorerApi.php b/src/AzureDataExplorerApi.php index 82ab835..a14cae0 100755 --- a/src/AzureDataExplorerApi.php +++ b/src/AzureDataExplorerApi.php @@ -14,6 +14,7 @@ use ReedTech\AzureDataExplorer\Interfaces\IngestModelInterface; use ReedTech\AzureDataExplorer\Requests\FetchToken; use ReedTech\AzureDataExplorer\Requests\QueryRequest; +use ReedTech\AzureDataExplorer\Requests\QueryV1Request; use ReedTech\AzureDataExplorer\Requests\StreamingIngestRequest; use ReflectionException; use Saloon\Http\Response; @@ -99,7 +100,7 @@ public function __construct( public function fetchToken(bool $force = false): string { // TODO - Temporary 'in memory' caching of the token - if (! $force && $this->token !== null) { + if (!$force && $this->token !== null) { return $this->token; } @@ -150,7 +151,35 @@ public function query(string|array $query): ?QueryResultsDTO // Run the Data Explorer query $response = $this->queryConnector->send(new QueryRequest($this->database, $query)); - // dd($response->json()); + // Handle Successful Response + try { + /** @var QueryResultsDTO $results */ + $results = $response->dto(); + + return $results; + } catch (Exception $e) { + throw new DTOException('Unable to parse response into DTO'); + } + } + + /** + * Query Azure Data Explorer + * + * @param string|array $query + * @return QueryResultsDTO + * + * @throws Exception + * @throws ReflectionException + * @throws GuzzleException + * @throws SaloonException + */ + public function queryV1(string|array $query): ?QueryResultsDTO + { + // Returns true if ready to query, otherwise throws an exception + $this->validateSetup(); + + // Run the Data Explorer query + $response = $this->queryConnector->send(new QueryV1Request($this->database, $query)); // Handle Successful Response try { /** @var QueryResultsDTO $results */ @@ -187,7 +216,7 @@ public function ingest(IngestModelInterface $deModel): Response private function validateSetup(): ?bool { if ($this->token === null) { - if (! $this->fetchToken()) { + if (!$this->fetchToken()) { throw new AuthException('Failed to fetch token'); } } diff --git a/src/Connectors/DataExplorerConnector.php b/src/Connectors/DataExplorerConnector.php index 3bc48f1..07a33c1 100644 --- a/src/Connectors/DataExplorerConnector.php +++ b/src/Connectors/DataExplorerConnector.php @@ -38,7 +38,7 @@ public function resolveBaseUrl(): string // $cluster = config('services.data_explorer.cluster'); // $region = config('services.data_explorer.region'); - return 'https://'.$this->generateBaseURL(); + return 'https://' . $this->generateBaseURL(); } protected array $requests = [ @@ -69,8 +69,6 @@ public function defaultAuth(): Authenticator // $tokenResponse = (new AuthenticationRequest())->send(); // $token = $tokenResponse->json('access_token'); - // dd($token); - return new TokenAuthenticator($this->token); } diff --git a/src/Data/QueryResultsDTO.php b/src/Data/QueryResultsDTO.php index 762edd5..5a7042b 100644 --- a/src/Data/QueryResultsDTO.php +++ b/src/Data/QueryResultsDTO.php @@ -9,7 +9,7 @@ class QueryResultsDTO public function __construct( public array $columns, public array $data, - public float $executionTime, + public float $executionTime = -1, ) { } @@ -47,4 +47,34 @@ public static function fromSaloon(Response $response): self return new static($columns, $rows, $executionTime); } + + public static function parseV1(Response $response): self + { + $data = $response->json(); + + // Raw Data Extraction + $rawColumns = $data['Tables'][0]['Columns']; + $rawRows = $data['Tables'][0]['Rows']; + // $executionTime = $data['Statistics']['Query']['ExecutionTime']; + + // Morph to intermediate state + $columns = collect($rawColumns)->pluck('ColumnName')->map(function ($column) { + // Column name manipulation + // $column = str_replace('ID', 'Id', $column); + // $column = Str::snake($column); + + return trim($column); + })->all(); + + // Loop through each row and map the column names to the values + $rows = collect($rawRows)->map(function ($row) use ($columns) { + // Loop through each column and map the column name to the value + return collect($row)->map(function ($value, $key) use ($columns) { + // return [$columnName => $value]; // Map the column name to the value + return [$columns[$key] => trim($value)]; // Map the column name to the value + })->collapse()->all(); // Collapse the array to a single level + })->all(); + + return new static($columns, $rows); + } } diff --git a/src/Requests/QueryV1Request.php b/src/Requests/QueryV1Request.php new file mode 100644 index 0000000..bcca0ab --- /dev/null +++ b/src/Requests/QueryV1Request.php @@ -0,0 +1,76 @@ + 'application/json', + // 'Host' => "$cluster.$region.kusto.windows.net", + ]; + } + + protected function defaultBody(): array + { + // Allows the user to pass in a single query string or an array of strings (multiple line queries) + $query = is_array($this->kustoQuery) ? implode("\n", $this->kustoQuery) : $this->kustoQuery; + + return [ + 'csl' => $query, + // 'db' => config('services.data_explorer.db'), + 'db' => $this->database, + ]; + } + + // protected function castToDto(Response $response): object + public function createDtoFromResponse(ContractsResponse $response): mixed + { + return QueryResultsDTO::parseV1($response); + } +}