Skip to content

Commit b322ffe

Browse files
authored
Merge pull request #87 from keboola/KBC-3043_SNFLK-ref-view
KBC-3043 SnowflakeTableReflection now works with views
2 parents 63b07c3 + b17dade commit b322ffe

4 files changed

Lines changed: 163 additions & 63 deletions

File tree

src/Table/Snowflake/SnowflakeTableReflection.php

Lines changed: 94 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
use Doctrine\DBAL\Connection;
88
use Keboola\TableBackendUtils\Column\ColumnCollection;
99
use Keboola\TableBackendUtils\Column\Snowflake\SnowflakeColumn;
10-
use Keboola\TableBackendUtils\Escaping\Exasol\ExasolQuote;
1110
use Keboola\TableBackendUtils\Escaping\Snowflake\SnowflakeQuote;
1211
use Keboola\TableBackendUtils\Table\TableDefinitionInterface;
1312
use Keboola\TableBackendUtils\Table\TableReflectionInterface;
1413
use Keboola\TableBackendUtils\Table\TableStats;
1514
use Keboola\TableBackendUtils\Table\TableStatsInterface;
1615
use Keboola\TableBackendUtils\TableNotExistsReflectionException;
16+
use RuntimeException;
1717
use Throwable;
1818

1919
final class SnowflakeTableReflection implements TableReflectionInterface
@@ -27,53 +27,87 @@ final class SnowflakeTableReflection implements TableReflectionInterface
2727

2828
private string $tableName;
2929

30+
private ?bool $isView = null;
31+
3032
private ?bool $isTemporary = null;
3133

34+
private ?int $sizeBytes = null;
35+
36+
private ?int $rowCount = null;
37+
3238
public function __construct(Connection $connection, string $schemaName, string $tableName)
3339
{
3440
$this->tableName = $tableName;
3541
$this->schemaName = $schemaName;
3642
$this->connection = $connection;
3743
}
3844

39-
private function setIsTemporary(): bool
45+
/**
46+
* @throws TableNotExistsReflectionException
47+
*/
48+
private function cacheTableProps(bool $force = false): void
4049
{
41-
$row = $this->connection->fetchAssociative(
50+
if ($force === false && $this->isView !== null && $this->isTemporary !== null) {
51+
return;
52+
}
53+
/** @var array<array{TABLE_TYPE:string,BYTES:string,ROW_COUNT:string}> $row */
54+
$row = $this->connection->fetchAllAssociative(
4255
sprintf(
43-
// STARTS WITH is added because it is case-sensitive
44-
'SHOW TABLES LIKE %s IN %s STARTS WITH %s ',
45-
SnowflakeQuote::quote($this->tableName),
46-
SnowflakeQuote::quoteSingleIdentifier($this->schemaName),
56+
//phpcs:ignore
57+
'SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s;',
58+
SnowflakeQuote::quote($this->schemaName),
4759
SnowflakeQuote::quote($this->tableName)
4860
)
4961
);
50-
51-
if ($row) {
52-
return $row['kind'] === 'TEMPORARY';
62+
if (count($row) === 0) {
63+
throw TableNotExistsReflectionException::createForTable([$this->schemaName, $this->tableName]);
64+
}
65+
$this->sizeBytes = (int) $row[0]['BYTES'];
66+
$this->rowCount = (int) $row[0]['ROW_COUNT'];
67+
switch (strtoupper($row[0]['TABLE_TYPE'])) {
68+
case 'BASE TABLE':
69+
$this->isTemporary = false;
70+
$this->isView = false;
71+
return;
72+
case 'LOCAL TEMPORARY':
73+
case 'TEMPORARY TABLE':
74+
$this->isTemporary = true;
75+
$this->isView = false;
76+
return;
77+
case 'VIEW':
78+
$this->isTemporary = false;
79+
$this->isView = true;
80+
return;
81+
default:
82+
throw new RuntimeException(sprintf(
83+
'Table type "%s" is not known.',
84+
$row[0]['TABLE_TYPE']
85+
));
5386
}
54-
55-
throw new TableNotExistsReflectionException('Cannot detect if table is temporary or not. Table does not exist');
5687
}
5788

5889
/**
5990
* @return string[]
91+
* @throws TableNotExistsReflectionException
6092
*/
6193
public function getColumnsNames(): array
6294
{
63-
/** @var array<array{column_name: string}> $columnsData */
64-
$columnsData = $this->connection->fetchAllAssociative(
65-
sprintf(
66-
// case-sensitive
67-
'SHOW COLUMNS IN %s',
68-
SnowflakeQuote::createQuotedIdentifierFromParts([$this->schemaName, $this->tableName,])
69-
)
70-
);
95+
$columns = $this->getColumnsDefinitions();
7196

72-
return array_values(array_map(fn($column) => $column['column_name'], $columnsData));
97+
$names = [];
98+
/** @var SnowflakeColumn $col */
99+
foreach ($columns as $col) {
100+
$names[] = $col->getColumnName();
101+
}
102+
return $names;
73103
}
74104

105+
/**
106+
* @throws TableNotExistsReflectionException
107+
*/
75108
public function getColumnsDefinitions(): ColumnCollection
76109
{
110+
$this->cacheTableProps();
77111
/** @var array<array{
78112
* name: string,
79113
* kind: string,
@@ -99,28 +133,25 @@ public function getColumnsDefinitions(): ColumnCollection
99133
return new ColumnCollection($columns);
100134
}
101135

102-
103-
136+
/**
137+
* @throws TableNotExistsReflectionException
138+
*/
104139
public function getRowsCount(): int
105140
{
106-
/** @var int|string $result */
107-
$result = $this->connection->fetchOne(sprintf(
108-
'SELECT COUNT(*) AS NumberOfRows FROM %s',
109-
SnowflakeQuote::createQuotedIdentifierFromParts([
110-
$this->schemaName,
111-
$this->tableName,
112-
])
113-
));
114-
return (int) $result;
141+
$this->cacheTableProps(true);
142+
assert($this->rowCount !== null);
143+
return $this->rowCount;
115144
}
116145

117146
/**
118147
* returns list of column names where PK is defined on
119148
*
120149
* @return string[]
150+
* @throws TableNotExistsReflectionException
121151
*/
122152
public function getPrimaryKeysNames(): array
123153
{
154+
$this->cacheTableProps();
124155
/** @var array<array{column_name:string}> $columnsMeta */
125156
$columnsMeta = $this->connection->fetchAllAssociative(
126157
sprintf(
@@ -132,28 +163,24 @@ public function getPrimaryKeysNames(): array
132163
return array_map(fn($pkRow) => $pkRow['column_name'], $columnsMeta);
133164
}
134165

166+
/**
167+
* @throws TableNotExistsReflectionException
168+
*/
135169
public function getTableStats(): TableStatsInterface
136170
{
137-
$sql = sprintf(
138-
'SHOW TABLES LIKE %s IN SCHEMA %s STARTS WITH %s',
139-
ExasolQuote::quote($this->tableName),
140-
ExasolQuote::quoteSingleIdentifier($this->schemaName),
141-
ExasolQuote::quote($this->tableName)
142-
);
143-
/** @var array{bytes:int|string}|null $result */
144-
$result = $this->connection->fetchAssociative($sql);
145-
if (!$result) {
146-
throw new TableNotExistsReflectionException('Table does not exist');
147-
}
148-
149-
return new TableStats((int) $result['bytes'], $this->getRowsCount());
171+
$this->cacheTableProps(true);
172+
assert($this->sizeBytes !== null);
173+
assert($this->rowCount !== null);
174+
return new TableStats($this->sizeBytes, $this->rowCount);
150175
}
151176

177+
/**
178+
* @throws TableNotExistsReflectionException
179+
*/
152180
public function isTemporary(): bool
153181
{
154-
if ($this->isTemporary === null) {
155-
$this->isTemporary = $this->setIsTemporary();
156-
}
182+
$this->cacheTableProps();
183+
assert($this->isTemporary !== null);
157184
return $this->isTemporary;
158185
}
159186

@@ -224,14 +251,20 @@ public static function getDependentViewsForObject(
224251
* schema_name: string,
225252
* name: string
226253
* }[]
254+
* @throws TableNotExistsReflectionException
227255
*/
228256
public function getDependentViews(): array
229257
{
258+
$this->cacheTableProps();
230259
return self::getDependentViewsForObject($this->connection, $this->tableName, $this->schemaName, 'TABLE');
231260
}
232261

262+
/**
263+
* @throws TableNotExistsReflectionException
264+
*/
233265
public function getTableDefinition(): TableDefinitionInterface
234266
{
267+
$this->cacheTableProps();
235268
return new SnowflakeTableDefinition(
236269
$this->schemaName,
237270
$this->tableName,
@@ -243,18 +276,19 @@ public function getTableDefinition(): TableDefinitionInterface
243276

244277
public function exists(): bool
245278
{
246-
$row = $this->connection->fetchAssociative(
247-
sprintf(
248-
"SELECT *
249-
FROM information_schema.tables
250-
WHERE TABLE_TYPE = 'BASE TABLE'
251-
AND TABLE_NAME = %s AND TABLE_SCHEMA = %s
252-
",
253-
SnowflakeQuote::quote($this->tableName),
254-
SnowflakeQuote::quote($this->schemaName),
255-
)
256-
);
279+
try {
280+
$this->cacheTableProps(true);
281+
} catch (TableNotExistsReflectionException $e) {
282+
return false;
283+
}
284+
285+
return true;
286+
}
257287

258-
return $row !== false;
288+
public function isView(): bool
289+
{
290+
$this->cacheTableProps();
291+
assert($this->isView !== null);
292+
return $this->isView;
259293
}
260294
}

src/TableNotExistsReflectionException.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,20 @@
66

77
class TableNotExistsReflectionException extends ReflectionException
88
{
9+
/**
10+
* @param string[] $path
11+
*/
12+
public static function createForTable(array $path): self
13+
{
14+
return new self(sprintf(
15+
'Table %s does not exists.',
16+
implode(
17+
',',
18+
array_map(
19+
fn(string $item) => sprintf('"%s"', $item),
20+
$path
21+
)
22+
)
23+
));
24+
}
925
}

tests/Functional/Snowflake/Table/SnowflakeTableQueryBuilderTest.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Keboola\TableBackendUtils\Table\Snowflake\SnowflakeTableDefinition;
1313
use Keboola\TableBackendUtils\Table\Snowflake\SnowflakeTableQueryBuilder;
1414
use Keboola\TableBackendUtils\Table\Snowflake\SnowflakeTableReflection;
15+
use Keboola\TableBackendUtils\TableNotExistsReflectionException;
1516
use Tests\Keboola\TableBackendUtils\Functional\Snowflake\SnowflakeBaseCase;
1617

1718
// TODO we dont use DEFAULT values in columns.
@@ -54,7 +55,7 @@ public function testGetRenameTableCommand(): void
5455
$refNew->getRowsCount();
5556

5657
// test NON existence of old table via counting
57-
$this->expectException(DBALException::class);
58+
$this->expectException(TableNotExistsReflectionException::class);
5859
$refOld->getRowsCount();
5960
}
6061

@@ -98,7 +99,7 @@ public function testGetDropTableCommand(): void
9899
$this->connection->executeQuery($sql);
99100

100101
// test NON existence of old table via counting
101-
$this->expectException(DBALException::class);
102+
$this->expectException(TableNotExistsReflectionException::class);
102103
$ref->getRowsCount();
103104
}
104105

0 commit comments

Comments
 (0)