Skip to content

Commit e466b8a

Browse files
committed
refactor: cover CLI and extensions with mago
1 parent 28d5dd9 commit e466b8a

25 files changed

Lines changed: 202 additions & 173 deletions

Justfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ analyze-mago *args:
106106
src/adapter/etl-adapter-elasticsearch \
107107
src/adapter/etl-adapter-google-sheet \
108108
src/adapter/etl-adapter-logger \
109-
src/adapter/etl-adapter-postgresql
109+
src/adapter/etl-adapter-postgresql \
110+
src/cli \
111+
src/extension/arrow-ext \
112+
src/extension/pg-query-ext
110113

111114
# Auto-fix code style (Mago format + lint --fix) and GitHub Actions findings (zizmor --fix).
112115
fix:

mago.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ includes = [
1818
# Generated by protoc (OTLP protocol definitions)
1919
"src/bridge/telemetry/otlp/src/Opentelemetry",
2020
"src/bridge/telemetry/otlp/src/GPBMetadata",
21+
# C extension stub
22+
"src/extension/pg-query-ext/ext/pg_query.stub.php",
2123
# Symfony
2224
"src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/FlowFilesystemBundle.php",
2325
"src/bridge/symfony/postgresql-bundle/src/Flow/Bridge/Symfony/PostgreSqlBundle/FlowPostgreSqlBundle.php",

src/adapter/etl-adapter-doctrine/src/Flow/ETL/Adapter/Doctrine/SchemaConverter.php

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function toDbalTable(Schema $schema, string $tableName, array $tableOptio
5656
$definition->isNullable(),
5757
$definition->metadata(),
5858
);
59-
$columns[$column->getObjectName()->toString()] = $column;
59+
$columns[$column->getObjectName()->getIdentifier()->getValue()] = $column;
6060
}
6161

6262
$table = new Table($tableName, $columns, options: $tableOptions);
@@ -138,40 +138,42 @@ private function columnToFlow(Column $column, Table $table): Definition
138138

139139
$primaryKeyConstraint = $table->getPrimaryKeyConstraint();
140140
$pkColumnNames = array_map(
141-
static fn(UnqualifiedName $n): string => $n->toString(),
141+
static fn(UnqualifiedName $n): string => $n->getIdentifier()->getValue(),
142142
$primaryKeyConstraint?->getColumnNames() ?? [],
143143
);
144-
$columnName = $column->getObjectName()->toString();
144+
$columnName = $column->getObjectName()->getIdentifier()->getValue();
145145

146146
foreach ($pkColumnNames as $primaryKeyColumn) {
147147
if ($primaryKeyColumn === $columnName) {
148148
$metadata = $metadata->merge(DbalMetadata::primaryKey(
149-
$primaryKeyConstraint?->getObjectName()?->toString() ?? '',
149+
$primaryKeyConstraint?->getObjectName()?->getIdentifier()->getValue() ?? '',
150150
));
151151
$nullable = false;
152152
}
153153
}
154154

155155
foreach ($table->getIndexes() as $index) {
156-
$indexColumnNames = array_map(
157-
static fn(IndexedColumn $c): string => $c->getColumnName()->toString(),
158-
$index->getIndexedColumns(),
159-
);
156+
$indexColumnNames = array_map(static fn(IndexedColumn $c): string => $c
157+
->getColumnName()
158+
->getIdentifier()
159+
->getValue(), $index->getIndexedColumns());
160160

161161
if (
162162
$index->getType() === IndexType::UNIQUE
163163
&& !in_array($columnName, $pkColumnNames, true)
164164
&& in_array($columnName, $indexColumnNames, true)
165165
) {
166-
$metadata = $metadata->merge(DbalMetadata::indexUnique($index->getObjectName()->toString()));
166+
$indexName = $index->getObjectName();
167+
$metadata = $metadata->merge(DbalMetadata::indexUnique($indexName->getIdentifier()->getValue()));
167168
}
168169

169170
if (
170171
$index->getType() === IndexType::REGULAR
171172
&& !in_array($columnName, $pkColumnNames, true)
172173
&& in_array($columnName, $indexColumnNames, true)
173174
) {
174-
$metadata = $metadata->merge(DbalMetadata::index($index->getObjectName()->toString()));
175+
$indexName = $index->getObjectName();
176+
$metadata = $metadata->merge(DbalMetadata::index($indexName->getIdentifier()->getValue()));
175177
}
176178
}
177179

src/cli/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"flow-php/openapi-specification-bridge": "self.version",
2727
"flow-php/parquet-viewer": "self.version",
2828
"flow-php/postgresql": "self.version",
29-
"flow-php/telemetry": "self.version"
29+
"flow-php/telemetry": "self.version",
30+
"flow-php/types": "self.version"
3031
},
3132
"config": {
3233
"optimize-autoloader": true,

src/cli/src/Flow/CLI/Arguments/TypedArgument.php

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
use Symfony\Component\Console\Exception\InvalidArgumentException;
88
use Symfony\Component\Console\Input\InputInterface;
99

10-
use function is_int;
11-
use function is_string;
10+
use function Flow\Types\DSL\type_string;
11+
use function is_numeric;
1212

1313
final readonly class TypedArgument
1414
{
@@ -29,17 +29,15 @@ public function asInt(InputInterface $input): int
2929

3030
public function asIntNullable(InputInterface $input): ?int
3131
{
32-
$option = $input->getArgument($this->name);
33-
34-
if ($option === null) {
32+
if ($input->getArgument($this->name) === null) {
3533
return null;
3634
}
3735

38-
if (!is_int($option)) {
36+
if (!is_numeric($input->getArgument($this->name))) {
3937
throw new InvalidArgumentException("Argument '{$this->name}' must be an integer.");
4038
}
4139

42-
return $option;
40+
return (int) $input->getArgument($this->name);
4341
}
4442

4543
public function asString(InputInterface $input): string
@@ -55,16 +53,10 @@ public function asString(InputInterface $input): string
5553

5654
public function asStringNullable(InputInterface $input): ?string
5755
{
58-
$option = $input->getArgument($this->name);
59-
60-
if ($option === null) {
56+
if ($input->getArgument($this->name) === null) {
6157
return null;
6258
}
6359

64-
if (!is_string($option)) {
65-
throw new InvalidArgumentException("Argument '{$this->name}' must be a string.");
66-
}
67-
68-
return $option;
60+
return type_string()->assert($input->getArgument($this->name));
6961
}
7062
}

src/cli/src/Flow/CLI/Command/DatabaseTableListCommand.php

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
use Doctrine\DBAL\Tools\DsnParser;
1010
use Flow\CLI\Command\Traits\ConfigOptions;
1111
use Flow\CLI\Command\Traits\DBOptions;
12-
use Flow\CLI\Options\ConfigOption;
13-
use Flow\ETL\Config;
12+
use RuntimeException;
1413
use Symfony\Component\Console\Command\Command;
1514
use Symfony\Component\Console\Helper\TableSeparator;
1615
use Symfony\Component\Console\Input\InputInterface;
@@ -21,6 +20,7 @@
2120
use function count;
2221
use function Flow\CLI\option_include_file;
2322
use function Flow\CLI\option_list_of_strings_nullable;
23+
use function Flow\Types\DSL\type_string;
2424
use function in_array;
2525

2626
final class DatabaseTableListCommand extends Command
@@ -30,8 +30,6 @@ final class DatabaseTableListCommand extends Command
3030

3131
private ?Connection $connection = null;
3232

33-
private ?Config $flowConfig = null;
34-
3533
public function configure(): void
3634
{
3735
$this
@@ -52,6 +50,10 @@ public function configure(): void
5250

5351
protected function execute(InputInterface $input, OutputInterface $output): int
5452
{
53+
if ($this->connection === null) {
54+
throw new RuntimeException('Command not properly initialized.');
55+
}
56+
5557
$style = new SymfonyStyle($input, $output);
5658

5759
$table = $style->createTable();
@@ -62,10 +64,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6264
$dbTables = [];
6365
$totalColumns = 0;
6466

65-
foreach ($this->connection->createSchemaManager()->listTables() as $dbTable) {
67+
foreach ($this->connection->createSchemaManager()->introspectTables() as $dbTable) {
68+
$qualifier = $dbTable->getObjectName()->getQualifier();
69+
6670
$dbTables[] = [
67-
$dbTable->getName(),
68-
(string) $dbTable->getNamespaceName() === '' ? 'public' : $dbTable->getNamespaceName(),
71+
$dbTable->getObjectName()->getUnqualifiedName()->getValue(),
72+
$qualifier !== null ? $qualifier->getValue() : 'public',
6973
count($dbTable->getColumns()),
7074
];
7175
$totalColumns += count($dbTable->getColumns());
@@ -94,19 +98,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
9498

9599
protected function initialize(InputInterface $input, OutputInterface $output): void
96100
{
97-
$this->flowConfig = (new ConfigOption('config'))->get($input);
98-
99101
if ($input->getOption('db-connection-file')) {
100102
$this->connection = option_include_file('db-connection-file', $input, Connection::class);
101103
} else {
102104
$style = new SymfonyStyle($input, $output);
103-
$connectionString = $_ENV['FLOW_DB_CONNECTION_STRING'] ?? $style->ask(
104-
"FLOW_DB_CONNECTION_STRING env not found.\n Please provide database connection string, format:\n \"scheme://username:password@host:port/dbname?param1=value1&param2=value2&...\"",
105-
null,
106-
static fn($value) => $value,
105+
$connectionString = type_string()->assert(
106+
$_ENV['FLOW_DB_CONNECTION_STRING'] ?? $style->ask(
107+
"FLOW_DB_CONNECTION_STRING env not found.\n Please provide database connection string, format:\n \"scheme://username:password@host:port/dbname?param1=value1&param2=value2&...\"",
108+
null,
109+
static fn($value) => $value,
110+
),
107111
);
108-
$connectionParameters = (new DsnParser())->parse($connectionString);
109-
$this->connection = DriverManager::getConnection($connectionParameters);
112+
113+
$this->connection = DriverManager::getConnection((new DsnParser())->parse($connectionString));
110114
}
111115
}
112116
}

src/cli/src/Flow/CLI/Command/DatabaseTableSchemaCommand.php

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
use Doctrine\DBAL\Connection;
88
use Doctrine\DBAL\DriverManager;
9+
use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName;
910
use Doctrine\DBAL\Tools\DsnParser;
1011
use Flow\CLI\Command\Traits\ConfigOptions;
1112
use Flow\CLI\Command\Traits\DBOptions;
12-
use Flow\CLI\Options\ConfigOption;
13-
use Flow\ETL\Config;
1413
use Flow\ETL\Row\Formatter\ASCIISchemaFormatter;
1514
use Flow\ETL\Schema\Formatter\PHPSchemaFormatter;
15+
use RuntimeException;
1616
use Symfony\Component\Console\Command\Command;
1717
use Symfony\Component\Console\Input\InputArgument;
1818
use Symfony\Component\Console\Input\InputInterface;
@@ -27,6 +27,7 @@
2727
use function Flow\CLI\option_list_of_strings_nullable;
2828
use function Flow\ETL\Adapter\Doctrine\table_schema_to_flow_schema;
2929
use function Flow\ETL\DSL\schema_to_json;
30+
use function Flow\Types\DSL\type_string;
3031

3132
final class DatabaseTableSchemaCommand extends Command
3233
{
@@ -35,8 +36,6 @@ final class DatabaseTableSchemaCommand extends Command
3536

3637
private ?Connection $connection = null;
3738

38-
private ?Config $flowConfig = null;
39-
4039
public function configure(): void
4140
{
4241
$this
@@ -64,23 +63,28 @@ public function configure(): void
6463

6564
protected function execute(InputInterface $input, OutputInterface $output): int
6665
{
66+
if ($this->connection === null) {
67+
throw new RuntimeException('Command not properly initialized.');
68+
}
69+
6770
$style = new SymfonyStyle($input, $output);
6871

6972
$tableName = argument_string_nullable('input-db-table', $input);
7073

7174
if (!$tableName) {
72-
$question = new ChoiceQuestion(
73-
'Please select table name for which we are going to generate schema: ',
74-
$this->connection->createSchemaManager()->listTableNames(),
75-
);
75+
$question =
76+
new ChoiceQuestion('Please select table name for which we are going to generate schema: ', array_map(
77+
static fn(OptionallyQualifiedName $name): string => $name->getUnqualifiedName()->getValue(),
78+
$this->connection->createSchemaManager()->introspectTableNames(),
79+
));
7680
$question->setErrorMessage('Invalid table: %s');
77-
$tableName = $style->askQuestion($question);
81+
$tableName = type_string()->assert($style->askQuestion($question));
7882
}
7983

8084
$table = null;
8185

82-
foreach ($this->connection->createSchemaManager()->listTables() as $dbTable) {
83-
if ($dbTable->getName() === $tableName) {
86+
foreach ($this->connection->createSchemaManager()->introspectTables() as $dbTable) {
87+
if ($dbTable->getObjectName()->getUnqualifiedName()->getValue() === $tableName) {
8488
$table = $dbTable;
8589

8690
break;
@@ -134,19 +138,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
134138

135139
protected function initialize(InputInterface $input, OutputInterface $output): void
136140
{
137-
$this->flowConfig = (new ConfigOption('config'))->get($input);
138-
139141
if ($input->getOption('db-connection-file')) {
140142
$this->connection = option_include_file('db-connection-file', $input, Connection::class);
141143
} else {
142144
$style = new SymfonyStyle($input, $output);
143-
$connectionString = $_ENV['FLOW_DB_CONNECTION_STRING'] ?? $style->ask(
144-
"FLOW_DB_CONNECTION_STRING env not found.\n Please provide database connection string, format:\n \"scheme://username:password@host:port/dbname?param1=value1&param2=value2&...\"",
145-
null,
146-
static fn($value) => $value,
145+
$connectionString = type_string()->assert(
146+
$_ENV['FLOW_DB_CONNECTION_STRING'] ?? $style->ask(
147+
"FLOW_DB_CONNECTION_STRING env not found.\n Please provide database connection string, format:\n \"scheme://username:password@host:port/dbname?param1=value1&param2=value2&...\"",
148+
null,
149+
static fn($value) => $value,
150+
),
147151
);
148-
$connectionParameters = (new DsnParser())->parse($connectionString);
149-
$this->connection = DriverManager::getConnection($connectionParameters);
152+
153+
$this->connection = DriverManager::getConnection((new DsnParser())->parse($connectionString));
150154
}
151155
}
152156
}

src/cli/src/Flow/CLI/Command/FileAnalyzeCommand.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Flow\ETL\Config;
2222
use Flow\ETL\Rows;
2323
use Flow\Filesystem\Path;
24+
use RuntimeException;
2425
use Symfony\Component\Console\Command\Command;
2526
use Symfony\Component\Console\Input\InputArgument;
2627
use Symfony\Component\Console\Input\InputInterface;
@@ -108,6 +109,10 @@ public function configure(): void
108109

109110
protected function execute(InputInterface $input, OutputInterface $output): int
110111
{
112+
if ($this->flowConfig === null || $this->sourcePath === null || $this->fileFormat === null) {
113+
throw new RuntimeException('Command not properly initialized.');
114+
}
115+
111116
$style = new FlowStyle($input, $output);
112117

113118
$style->title('Analyzing File');
@@ -158,12 +163,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
158163
$progress->advance($rows->count());
159164
}, analyze: $analyze);
160165

161-
if ($report === null) {
162-
$style->error("Couldn't analyze given file.");
163-
164-
return Command::FAILURE;
165-
}
166-
167166
$progress->finish();
168167

169168
$style->newLine(2);

src/cli/src/Flow/CLI/Command/FileConvertCommand.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Flow\CLI\Options\FileFormatOption;
1919
use Flow\ETL\Config;
2020
use Flow\Filesystem\Path;
21+
use RuntimeException;
2122
use Symfony\Component\Console\Command\Command;
2223
use Symfony\Component\Console\Input\InputArgument;
2324
use Symfony\Component\Console\Input\InputInterface;
@@ -133,6 +134,16 @@ public function configure(): void
133134

134135
protected function execute(InputInterface $input, OutputInterface $output): int
135136
{
137+
if (
138+
$this->flowConfig === null
139+
|| $this->inputFile === null
140+
|| $this->inputFileFormat === null
141+
|| $this->outputFile === null
142+
|| $this->outputFileFormat === null
143+
) {
144+
throw new RuntimeException('Command not properly initialized.');
145+
}
146+
136147
$style = new SymfonyStyle($input, $output);
137148

138149
$df = df($this->flowConfig)

src/cli/src/Flow/CLI/Command/FileReadCommand.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Flow\ETL\Formatter\AsciiTableFormatter;
2020
use Flow\ETL\Rows;
2121
use Flow\Filesystem\Path;
22+
use RuntimeException;
2223
use Symfony\Component\Console\Command\Command;
2324
use Symfony\Component\Console\Input\InputArgument;
2425
use Symfony\Component\Console\Input\InputInterface;
@@ -120,6 +121,10 @@ public function configure(): void
120121

121122
protected function execute(InputInterface $input, OutputInterface $output): int
122123
{
124+
if ($this->flowConfig === null || $this->sourcePath === null || $this->fileFormat === null) {
125+
throw new RuntimeException('Command not properly initialized.');
126+
}
127+
123128
$style = new SymfonyStyle($input, $output);
124129

125130
$df = df($this->flowConfig)->read((new ExtractorFactory($this->sourcePath, $this->fileFormat))->get($input));

0 commit comments

Comments
 (0)