Skip to content
This repository was archived by the owner on Sep 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ on:
jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Build development container
run: docker compose build dev

- name: Install dependencies
run: docker compose run --rm dev composer install

- name: Run tests and CI checks
run: |
docker compose run --rm \
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

# Environment files (contains sensitive information)
.env
.env.local

# PHPUnit
.phpunit.result.cache
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ RUN apt-get update -q \
&& apt-get install git unzip \
-y --no-install-recommends

RUN git config --global --add safe.directory /code
RUN echo "memory_limit = -1" >> /usr/local/etc/php/php.ini
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin/ --filename=composer

Expand Down
11 changes: 4 additions & 7 deletions phpcs.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
<?xml version="1.0"?>
<ruleset name="Keboola">
<description>The Keboola coding standard.</description>

<file>src</file>
<file>tests</file>

<rule ref="./vendor/keboola/coding-standard/src/ruleset.xml"/>
<ruleset name="Project">
<rule ref="vendor/keboola/coding-standard/src/ruleset.xml">
</rule>
<exclude-pattern>*/src/Kernel.php</exclude-pattern>
</ruleset>
6 changes: 2 additions & 4 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,9 @@ public function executeWorkspaceQuery(
$finalStatus = $this->waitForJobCompletion($queryJobId, $maxWaitSeconds);

if (!isset($finalStatus['status']) || $finalStatus['status'] !== 'completed') {
/** @var string $status */
$status = $finalStatus['status'] ?? 'unknown';
throw new ClientException(
sprintf('Query job failed with status: %s', $status),
$status === 'failed' ? 500 : 0,
sprintf('Query job failed with error: %s', ResultHelper::extractAllStatementErrors($finalStatus)),
400,
);
}

Expand Down
24 changes: 24 additions & 0 deletions src/ResultHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,28 @@ public static function mapColumnNamesIntoData(array $responseData): array
$responseData['data'] = $transformedData;
return $responseData;
}

/**
* @param array<string, mixed> $responseData
*/
public static function extractAllStatementErrors(array $responseData): string
{
$errors = [];
if (isset($responseData['statements']) && is_array($responseData['statements'])) {
foreach ($responseData['statements'] as $statement) {
if (is_array($statement) && isset($statement['error']) && is_string($statement['error'])) {
$err = trim($statement['error']);
if ($err !== '') {
$errors[] = $err;
}
}
}
}

if (!$errors) {
return 'Unknown error';
}

return implode("\n", $errors);
}
}
51 changes: 34 additions & 17 deletions tests/Functional/BasicQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Keboola\QueryApi\Tests\Functional;

use Keboola\QueryApi\ClientException;

class BasicQueryTest extends BaseFunctionalTestCase
{
public function testSubmitSimpleSelectQuery(): void
Expand All @@ -20,7 +22,7 @@ public function testSubmitSimpleSelectQuery(): void

self::assertArrayHasKey('queryJobId', $response);
$queryJobId = $response['queryJobId'];
assert(is_string($queryJobId));
self::assertIsString($queryJobId);
self::assertNotEmpty($queryJobId);

// Wait for job completion
Expand All @@ -30,11 +32,11 @@ public function testSubmitSimpleSelectQuery(): void
self::assertEquals($queryJobId, $finalStatus['queryJobId']);
self::assertArrayHasKey('statements', $finalStatus);
$statements = $finalStatus['statements'];
assert(is_array($statements));
self::assertIsArray($statements);
self::assertCount(1, $statements);

$statement = $statements[0];
assert(is_array($statement));
self::assertIsArray($statement);
self::assertEquals('completed', $statement['status']);

// Get job results
Expand All @@ -47,10 +49,10 @@ public function testSubmitSimpleSelectQuery(): void
// Verify we got a timestamp result
self::assertArrayHasKey('data', $results);
$data = $results['data'];
assert(is_array($data));
self::assertIsArray($data);
self::assertCount(1, $data);
$row = $data[0];
assert(is_array($row));
self::assertIsArray($row);
self::assertCount(1, $row);
// Query API returns indexed arrays, not associative arrays with column names
self::assertArrayHasKey(0, $row);
Expand All @@ -77,7 +79,7 @@ public function testSubmitInformationSchemaQuery(): void

self::assertArrayHasKey('queryJobId', $response);
$queryJobId = $response['queryJobId'];
assert(is_string($queryJobId));
self::assertIsString($queryJobId);
self::assertNotEmpty($queryJobId);

// Wait for job completion
Expand All @@ -87,11 +89,11 @@ public function testSubmitInformationSchemaQuery(): void
self::assertEquals($queryJobId, $finalStatus['queryJobId']);
self::assertArrayHasKey('statements', $finalStatus);
$statements = $finalStatus['statements'];
assert(is_array($statements));
self::assertIsArray($statements);
self::assertCount(1, $statements);

$statement = $statements[0];
assert(is_array($statement));
self::assertIsArray($statement);
self::assertEquals('completed', $statement['status']);

// Get job results
Expand All @@ -104,13 +106,13 @@ public function testSubmitInformationSchemaQuery(): void
// Verify we got a count result
self::assertArrayHasKey('data', $results);
$data = $results['data'];
assert(is_array($data));
self::assertIsArray($data);
self::assertCount(1, $data);
$row = $data[0];
assert(is_array($row));
self::assertIsArray($row);
self::assertCount(1, $row);
// Query API returns indexed arrays, not associative arrays with column names
assert(isset($row[0]));
self::assertArrayHasKey(0, $row);
self::assertIsNumeric($row[0]);
self::assertGreaterThanOrEqual(0, (int) $row[0]);
}
Expand Down Expand Up @@ -139,29 +141,29 @@ public function testExecuteWorkspaceQuery(): void

// Verify statements
$statements = $response['statements'];
assert(is_array($statements));
self::assertIsArray($statements);
self::assertCount(1, $statements);

$statement = $statements[0];
assert(is_array($statement));
self::assertIsArray($statement);
self::assertEquals('completed', $statement['status']);

// Verify results
$results = $response['results'];
assert(is_array($results));
self::assertIsArray($results);
self::assertCount(1, $results);

$result = $results[0];
assert(is_array($result));
self::assertIsArray($result);
self::assertEquals('completed', $result['status']);

// Verify we got timestamp data
self::assertArrayHasKey('data', $result);
$data = $result['data'];
assert(is_array($data));
self::assertIsArray($data);
self::assertCount(1, $data);
$row = $data[0];
assert(is_array($row));
self::assertIsArray($row);
self::assertCount(1, $row);
// Query API returns indexed arrays, not associative arrays with column names
self::assertArrayHasKey(0, $row);
Expand All @@ -170,4 +172,19 @@ public function testExecuteWorkspaceQuery(): void
// Verify it's a valid timestamp (numeric string)
self::assertMatchesRegularExpression('/^\d+\.\d+$/', $row[0]);
}

public function testExecuteInvalidWorkspaceQuery(): void
{
self::expectException(ClientException::class);
self::expectExceptionMessage('\'COOTIES\' does not exist or not authorized');
self::expectExceptionCode(400);
$this->queryClient->executeWorkspaceQuery(
$this->getTestBranchId(),
$this->getTestWorkspaceId(),
[
'statements' => ['SELECT 1', 'SELECT * FROM Cooties', 'SELECT 2'],
'transactional' => false,
],
);
}
}
44 changes: 44 additions & 0 deletions tests/Phpunit/ResultHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,48 @@ public function testMapColumnNamesIntoData(): void
// Data rows should be mapped by column names
self::assertSame($expected['data'], $actual['data']);
}

public function testExtractAllStatementErrorsSingle(): void
{
$responseData = [
'queryJobId' => '74001bf0-c79c-49f7-bad9-ef96db5ff28e',
'status' => 'failed',
'statements' => [
[
'id' => 'b0065ce1-00d5-4723-8897-0c5a902ae446',
'query' => 'SELECT 1',
'status' => 'completed',
],
[
'id' => '4feb6663-c98c-401d-a875-5bb72438e2cc',
'query' => 'SELECT * FROM Cooties',
'status' => 'failed',
'error' => 'COOTIES does not exist or not authorized',
],
[
'id' => '4feb6663-c98c-401d-a875-5bb72438e2cc',
'query' => 'a spacey query',
'status' => 'failed',
'error' => ' there is also a lot of space ',
],
[
'id' => 'a57bc659-c9f7-45d4-a011-d6f10cc0e757',
'query' => 'SELECT 2',
'status' => 'notExecuted',
],
],
];

$actual = ResultHelper::extractAllStatementErrors($responseData);
self::assertEquals(
"COOTIES does not exist or not authorized\nthere is also a lot of space",
$actual,
);
}

public function testExtractAllStatementErrorsInvalidArray(): void
{
$actual = ResultHelper::extractAllStatementErrors([]);
self::assertSame('Unknown error', $actual);
}
}
11 changes: 3 additions & 8 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@

require_once __DIR__ . '/../vendor/autoload.php';

// Load environment variables from .env file if it exists (for local development)
if (class_exists('Symfony\Component\Dotenv\Dotenv')) {
$envFile = __DIR__ . '/../.env';
if (file_exists($envFile)) {
$dotenv = new Dotenv();
$dotenv->load($envFile);
}
}
$dotEnv = new Dotenv();
$dotEnv->usePutenv();
$dotEnv->bootEnv(dirname(__DIR__) . '/.env', 'dev', []);