diff --git a/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromABSCopyIntoAdapterTest.php b/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromABSCopyIntoAdapterTest.php index b562102cd..72793aa61 100644 --- a/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromABSCopyIntoAdapterTest.php +++ b/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromABSCopyIntoAdapterTest.php @@ -42,11 +42,15 @@ public function testGetCopyCommands(): void ); // @codingStandardsIgnoreEnd $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); @@ -92,11 +96,15 @@ public function testGetCopyCommandsNullIfMultiple(): void ); // @codingStandardsIgnoreEnd $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); @@ -144,11 +152,15 @@ public function testGetCopyCommandsNullIfEmpty(): void ); // @codingStandardsIgnoreEnd $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); @@ -197,11 +209,15 @@ public function testGetCopyCommandsRowSkip(): void // @codingStandardsIgnoreEnd $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 7, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 7, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 7, ], ]); @@ -259,11 +275,15 @@ public function testGetCopyCommandWithMoreChunksOfFiles(): void $conn->expects(self::exactly(2))->method('executeStatement')->withConsecutive([$q1], [$q2]); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 7, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 7, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 7, ], ]); diff --git a/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromGCSCopyIntoAdapterTest.php b/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromGCSCopyIntoAdapterTest.php index 3cc090f78..2812facf8 100644 --- a/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromGCSCopyIntoAdapterTest.php +++ b/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromGCSCopyIntoAdapterTest.php @@ -42,11 +42,15 @@ public function testGetCopyCommands(): void ); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); @@ -92,11 +96,15 @@ public function testGetCopyCommandsNullIfMultiple(): void ); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); @@ -144,11 +152,15 @@ public function testGetCopyCommandsNullIfEmpty(): void ); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); @@ -196,11 +208,15 @@ public function testGetCopyCommandsRowSkip(): void ); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 7, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 7, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 7, ], ]); @@ -258,11 +274,15 @@ public function testGetCopyCommandWithMoreChunksOfFiles(): void $conn->expects(self::exactly(2))->method('executeStatement')->withConsecutive([$q1], [$q2]); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 7, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 7, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 7, ], ]); diff --git a/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromS3CopyIntoAdapterTest.php b/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromS3CopyIntoAdapterTest.php index 920276982..3e28ca7f5 100644 --- a/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromS3CopyIntoAdapterTest.php +++ b/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromS3CopyIntoAdapterTest.php @@ -42,11 +42,15 @@ public function testGetCopyCommands(): void ); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); @@ -92,11 +96,15 @@ public function testGetCopyCommandsNullIfMultiple(): void ); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); @@ -144,11 +152,15 @@ public function testGetCopyCommandsNullIfEmpty(): void ); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); @@ -196,11 +208,15 @@ public function testGetCopyCommandsRowSkip(): void ); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 7, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 7, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 7, ], ]); @@ -258,11 +274,15 @@ public function testGetCopyCommandWithMoreChunksOfFiles(): void $conn->expects(self::exactly(2))->method('executeStatement')->withConsecutive([$q1], [$q2]); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 7, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 7, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 7, ], ]); diff --git a/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromTableInsertIntoAdapterTest.php b/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromTableInsertIntoAdapterTest.php index 51b81523e..18b463fcf 100644 --- a/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromTableInsertIntoAdapterTest.php +++ b/packages/php-db-import-export/tests/unit/Backend/Snowflake/ToStage/FromTableInsertIntoAdapterTest.php @@ -27,11 +27,15 @@ public function testGetCopyCommands(): void 'INSERT INTO "test_schema"."stagingTable" ("col1", "col2") SELECT "col1", "col2" FROM "test_schema"."test_table"' ); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'test_schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); @@ -74,11 +78,15 @@ public function testGetCopyCommandsSelectSource(): void [1], ); $conn->expects(self::once())->method('fetchAllAssociative') - // phpcs:ignore - ->with("SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = 'test_schema' AND TABLE_NAME = 'stagingTable';") ->willReturn([ [ - 'TABLE_TYPE' => 'BASE TABLE', 'BYTES' => 0, 'ROW_COUNT' => 10, + 'TABLE_TYPE' => 'BASE TABLE', + 'BYTES' => 0, + 'ROW_COUNT' => 10, + 'name' => 'stagingTable', + 'kind' => 'BASE TABLE', + 'bytes' => 0, + 'rows' => 10, ], ]); diff --git a/packages/php-table-backend-utils/src/Table/Snowflake/SnowflakeTableReflection.php b/packages/php-table-backend-utils/src/Table/Snowflake/SnowflakeTableReflection.php index 7e8386d1c..0a692fd26 100644 --- a/packages/php-table-backend-utils/src/Table/Snowflake/SnowflakeTableReflection.php +++ b/packages/php-table-backend-utils/src/Table/Snowflake/SnowflakeTableReflection.php @@ -17,6 +17,10 @@ use RuntimeException; use Throwable; +/** + * @phpstan-type SHOW_TABLE_ROW array{name:string,kind:string,bytes:string,rows:string,is_external:'Y'|'N'} + * @phpstan-type SHOW_VIEW_ROW array{name:string,kind:string} + */ final class SnowflakeTableReflection implements TableReflectionInterface { public const DEPENDENT_OBJECT_TABLE = 'TABLE'; @@ -57,45 +61,101 @@ private function cacheTableProps(bool $force = false): void if ($force === false && $this->isTemporary !== null) { return; } - /** @var array $row */ - $row = $this->connection->fetchAllAssociative( - sprintf( - //phpcs:ignore - 'SELECT TABLE_TYPE,BYTES,ROW_COUNT FROM information_schema.tables WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s;', - SnowflakeQuote::quote($this->schemaName), - SnowflakeQuote::quote($this->tableName), - ), - ); - if (count($row) === 0) { - throw TableNotExistsReflectionException::createForTable([$this->schemaName, $this->tableName]); + + try { + /** @var array $rows */ + $rows = $this->connection->fetchAllAssociative( + sprintf( + //phpcs:ignore + 'SHOW TABLES LIKE %s IN SCHEMA %s', + SnowflakeQuote::quote($this->tableName), + SnowflakeQuote::quoteSingleIdentifier($this->schemaName), + ), + );// show tables is case insensitive on table names so we need to check if we got any exact match + /** @var SHOW_TABLE_ROW|null $table */ + $table = $this->getTableByNameFromShow($rows); + if ($table === null) { + // if table is actually a view, fetch view info + /** @var array $rows */ + $rows = $this->connection->fetchAllAssociative( + sprintf( + //phpcs:ignore + 'SHOW VIEWS LIKE %s IN SCHEMA %s', + SnowflakeQuote::quote($this->tableName), + SnowflakeQuote::quoteSingleIdentifier($this->schemaName), + ), + ); + $view = $this->getTableByNameFromShow($rows); + if ($view === null) { + throw TableNotExistsReflectionException::createForTable([$this->schemaName, $this->tableName]); + } + $this->sizeBytes = 0; + $this->rowCount = 0; + $this->isTemporary = false; + $this->tableType = TableType::VIEW; + return; + } + } catch (Throwable $e) { + if ($e instanceof TableNotExistsReflectionException) { + throw $e; + } + if (str_contains($e->getMessage(), 'Cannot access object or it does not exist')) { + throw TableNotExistsReflectionException::createForTable([$this->schemaName, $this->tableName], $e); + } + + throw $e; + } + + $this->sizeBytes = (int) $table['bytes']; + $this->rowCount = (int) $table['rows']; + + if (array_key_exists('is_external', $table) && $table['is_external'] === 'Y') { + $this->isTemporary = false; + $this->tableType = TableType::SNOWFLAKE_EXTERNAL; + return; } - $this->sizeBytes = (int) $row[0]['BYTES']; - $this->rowCount = (int) $row[0]['ROW_COUNT']; - switch (strtoupper($row[0]['TABLE_TYPE'])) { + switch (strtoupper($table['kind'])) { case 'BASE TABLE': + case 'TABLE': $this->isTemporary = false; return; case 'EXTERNAL TABLE': $this->isTemporary = false; $this->tableType = TableType::SNOWFLAKE_EXTERNAL; return; + case 'TEMPORARY': case 'LOCAL TEMPORARY': case 'TEMPORARY TABLE': $this->isTemporary = true; return; case 'VIEW': + case 'MATERIALIZED_VIEW': $this->isTemporary = false; $this->tableType = TableType::VIEW; return; default: throw new RuntimeException(sprintf( 'Table type "%s" is not known.', - $row[0]['TABLE_TYPE'], + $table['kind'], )); } } + /** + * @param array $rows + * @return SHOW_TABLE_ROW|SHOW_VIEW_ROW|null + */ + private function getTableByNameFromShow(array $rows): ?array + { + foreach ($rows as $row) { + if ($row['name'] === $this->tableName) { + return $row; + } + } + return null; + } + /** * @return string[] * @throws TableNotExistsReflectionException diff --git a/packages/php-table-backend-utils/src/TableNotExistsReflectionException.php b/packages/php-table-backend-utils/src/TableNotExistsReflectionException.php index 91b46b267..99dd0029e 100644 --- a/packages/php-table-backend-utils/src/TableNotExistsReflectionException.php +++ b/packages/php-table-backend-utils/src/TableNotExistsReflectionException.php @@ -4,22 +4,27 @@ namespace Keboola\TableBackendUtils; +use Throwable; + class TableNotExistsReflectionException extends ReflectionException { /** * @param string[] $path */ - public static function createForTable(array $path): self + public static function createForTable(array $path, ?Throwable $previous = null): self { - return new self(sprintf( - 'Table %s does not exists.', - implode( - ',', - array_map( - fn(string $item) => sprintf('"%s"', $item), - $path, + return new self( + message: sprintf( + 'Table %s does not exists.', + implode( + ',', + array_map( + fn(string $item) => sprintf('"%s"', $item), + $path, + ), ), ), - )); + previous: $previous, + ); } }