Skip to content

Commit 48299c8

Browse files
authored
Merge pull request #2424 from flow-php/postgresql-table-options
feat(flow-php/postgresql): table options support
2 parents 3419a9b + 20c98fd commit 48299c8

10 files changed

Lines changed: 627 additions & 6 deletions

File tree

src/adapter/etl-adapter-postgresql/src/Flow/ETL/Adapter/PostgreSql/SchemaConverter.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Flow\PostgreSql\Schema\IdentityGeneration;
1616
use Flow\PostgreSql\Schema\Index;
1717
use Flow\PostgreSql\Schema\Table;
18+
use Flow\PostgreSql\Schema\TableOptions;
1819
use Flow\Types\Type;
1920

2021
use function array_keys;
@@ -49,8 +50,12 @@ public function toFlowSchema(Table $table): Schema
4950
return new Schema(...$definitions);
5051
}
5152

52-
public function toPostgreSqlTable(Schema $schema, string $tableName, string $databaseSchema = 'public'): Table
53-
{
53+
public function toPostgreSqlTable(
54+
Schema $schema,
55+
string $tableName,
56+
string $databaseSchema = 'public',
57+
?TableOptions $options = null,
58+
): Table {
5459
$columns = [];
5560
$position = 1;
5661

@@ -65,14 +70,14 @@ public function toPostgreSqlTable(Schema $schema, string $tableName, string $dat
6570
);
6671
}
6772

68-
return new Table(
73+
return (new Table(
6974
schema: $databaseSchema,
7075
name: $tableName,
7176
columns: $columns,
7277
primaryKey: $this->primaryKey($schema),
7378
indexes: $this->indexes($schema),
7479
uniqueConstraints: $this->uniqueConstraints($schema),
75-
);
80+
))->withOptions($options ?? new TableOptions());
7681
}
7782

7883
/**

src/adapter/etl-adapter-postgresql/src/Flow/ETL/Adapter/PostgreSql/functions.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Flow\PostgreSql\Client\Client;
1919
use Flow\PostgreSql\QueryBuilder\Sql;
2020
use Flow\PostgreSql\Schema\Table;
21+
use Flow\PostgreSql\Schema\TableOptions;
2122

2223
/**
2324
* Create a PostgreSQL cursor extractor using server-side cursors for memory-efficient extraction.
@@ -157,15 +158,17 @@ function pgsql_delete_options(array $primaryKeys): DeleteOptions
157158
* Convert a Flow Schema into a PostgreSQL table definition.
158159
*
159160
* @param string $databaseSchema PostgreSQL schema (namespace) the table belongs to
161+
* @param ?TableOptions $options table-level options the Flow Schema cannot express (e.g. UNLOGGED)
160162
*/
161163
#[DocumentationDSL(module: Module::POSTGRESQL, type: DSLType::HELPER)]
162164
function to_pgsql_schema_table(
163165
Schema $schema,
164166
string $tableName,
165167
string $databaseSchema = 'public',
166168
?EntryTypesMap $typesMap = null,
169+
?TableOptions $options = null,
167170
): Table {
168-
return (new SchemaConverter($typesMap))->toPostgreSqlTable($schema, $tableName, $databaseSchema);
171+
return (new SchemaConverter($typesMap))->toPostgreSqlTable($schema, $tableName, $databaseSchema, $options);
169172
}
170173

171174
/**

src/adapter/etl-adapter-postgresql/tests/Flow/ETL/Adapter/PostgreSql/Tests/Integration/SchemaConverterIntegrationTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use function Flow\ETL\DSL\str_schema;
2121
use function Flow\PostgreSql\DSL\client_catalog_provider;
2222
use function Flow\PostgreSql\DSL\drop;
23+
use function Flow\PostgreSql\DSL\schema_table_options;
2324

2425
final class SchemaConverterIntegrationTest extends IntegrationTestCase
2526
{
@@ -71,4 +72,27 @@ public function test_creates_table_from_flow_schema_and_reads_it_back(): void
7172
static::assertInstanceOf(JsonType::class, $flowSchema->get('payload')->type());
7273
static::assertFalse($flowSchema->get('id')->isNullable());
7374
}
75+
76+
public function test_creates_unlogged_table_from_options(): void
77+
{
78+
$table = to_pgsql_schema_table(
79+
schema(
80+
int_schema('id', metadata: PostgreSqlMetadata::primaryKey('pk_' . $this->tableName)),
81+
str_schema('payload'),
82+
),
83+
$this->tableName,
84+
options: schema_table_options(unlogged: true),
85+
);
86+
87+
foreach ($table->toSql() as $sql) {
88+
$this->client->execute($sql);
89+
}
90+
91+
$introspected = client_catalog_provider($this->client, ['public'])
92+
->get()
93+
->get('public')
94+
->table($this->tableName);
95+
96+
static::assertTrue($introspected->unlogged);
97+
}
7498
}

src/adapter/etl-adapter-postgresql/tests/Flow/ETL/Adapter/PostgreSql/Tests/Unit/SchemaConverterTest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
use Flow\PostgreSql\Schema\Constraint\PrimaryKey;
1313
use Flow\PostgreSql\Schema\Constraint\UniqueConstraint;
1414
use Flow\PostgreSql\Schema\IdentityGeneration;
15+
use Flow\PostgreSql\Schema\PartitionStrategy;
1516
use Flow\PostgreSql\Schema\Table;
17+
use Flow\PostgreSql\Schema\TriggerEvent;
18+
use Flow\PostgreSql\Schema\TriggerTiming;
1619
use Flow\Types\Type\Logical\DateTimeType;
1720
use Flow\Types\Type\Logical\JsonType;
1821
use Flow\Types\Type\Logical\UuidType;
@@ -32,6 +35,11 @@
3235
use function Flow\ETL\DSL\str_schema;
3336
use function Flow\ETL\DSL\uuid_schema;
3437
use function Flow\ETL\DSL\xml_schema;
38+
use function Flow\PostgreSql\DSL\schema_check;
39+
use function Flow\PostgreSql\DSL\schema_exclude;
40+
use function Flow\PostgreSql\DSL\schema_foreign_key;
41+
use function Flow\PostgreSql\DSL\schema_table_options;
42+
use function Flow\PostgreSql\DSL\schema_trigger;
3543

3644
final class SchemaConverterTest extends TestCase
3745
{
@@ -278,4 +286,62 @@ public function test_unique_constraint(): void
278286
static::assertSame(['email'], $table->uniqueConstraints[0]->columns);
279287
static::assertSame('uq_email', $table->uniqueConstraints[0]->name);
280288
}
289+
290+
public function test_no_options_keeps_defaults(): void
291+
{
292+
$table = (new SchemaConverter())->toPostgreSqlTable(schema(int_schema('id')), 'events');
293+
294+
static::assertFalse($table->unlogged);
295+
static::assertNull($table->tablespace);
296+
static::assertSame([], $table->inherits);
297+
static::assertSame([], $table->foreignKeys);
298+
static::assertSame([], $table->checkConstraints);
299+
static::assertSame([], $table->excludeConstraints);
300+
static::assertSame([], $table->triggers);
301+
static::assertNull($table->partitionStrategy);
302+
}
303+
304+
public function test_options_collections_thread_into_table(): void
305+
{
306+
$table = (new SchemaConverter())->toPostgreSqlTable(
307+
schema(int_schema('id'), int_schema('user_id')),
308+
'orders',
309+
'public',
310+
schema_table_options(
311+
foreignKeys: [schema_foreign_key(['user_id'], 'users', ['id'])],
312+
checkConstraints: [schema_check('id > 0')],
313+
excludeConstraints: [schema_exclude('USING gist (tsrange WITH &&)')],
314+
triggers: [schema_trigger('trg', 'orders', TriggerTiming::AFTER, [TriggerEvent::INSERT], 'fn')],
315+
),
316+
);
317+
318+
static::assertCount(1, $table->foreignKeys);
319+
static::assertSame('users', $table->foreignKeys[0]->referenceTable);
320+
static::assertCount(1, $table->checkConstraints);
321+
static::assertCount(1, $table->excludeConstraints);
322+
static::assertCount(1, $table->triggers);
323+
static::assertSame('trg', $table->triggers[0]->name);
324+
}
325+
326+
public function test_options_thread_into_table(): void
327+
{
328+
$table = (new SchemaConverter())->toPostgreSqlTable(
329+
schema(int_schema('id')),
330+
'events',
331+
'public',
332+
schema_table_options(
333+
unlogged: true,
334+
partitionStrategy: PartitionStrategy::RANGE,
335+
partitionColumns: ['id'],
336+
inherits: ['parent'],
337+
tablespace: 'fast_storage',
338+
),
339+
);
340+
341+
static::assertTrue($table->unlogged);
342+
static::assertSame(PartitionStrategy::RANGE, $table->partitionStrategy);
343+
static::assertSame(['id'], $table->partitionColumns);
344+
static::assertSame(['parent'], $table->inherits);
345+
static::assertSame('fast_storage', $table->tablespace);
346+
}
281347
}

src/lib/postgresql/src/Flow/PostgreSql/DSL/schema.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
use Flow\PostgreSql\Schema\Schema;
118118
use Flow\PostgreSql\Schema\Sequence as SchemaSequence;
119119
use Flow\PostgreSql\Schema\Table as SchemaTable;
120+
use Flow\PostgreSql\Schema\TableOptions as SchemaTableOptions;
120121
use Flow\PostgreSql\Schema\Trigger as SchemaTrigger;
121122
use Flow\PostgreSql\Schema\TriggerEvent;
122123
use Flow\PostgreSql\Schema\TriggerTiming;
@@ -1452,6 +1453,39 @@ function schema_table(
14521453
);
14531454
}
14541455

1456+
/**
1457+
* @param list<SchemaForeignKey> $foreignKeys
1458+
* @param list<SchemaCheckConstraint> $checkConstraints
1459+
* @param list<SchemaExcludeConstraint> $excludeConstraints
1460+
* @param list<SchemaTrigger> $triggers
1461+
* @param list<string> $partitionColumns
1462+
* @param list<string> $inherits
1463+
*/
1464+
#[DocumentationDSL(module: Module::PG_QUERY, type: DSLType::HELPER)]
1465+
function schema_table_options(
1466+
array $foreignKeys = [],
1467+
array $checkConstraints = [],
1468+
array $excludeConstraints = [],
1469+
array $triggers = [],
1470+
bool $unlogged = false,
1471+
?PartitionStrategy $partitionStrategy = null,
1472+
array $partitionColumns = [],
1473+
array $inherits = [],
1474+
?string $tablespace = null,
1475+
): SchemaTableOptions {
1476+
return new SchemaTableOptions(
1477+
$foreignKeys,
1478+
$checkConstraints,
1479+
$excludeConstraints,
1480+
$triggers,
1481+
$unlogged,
1482+
$partitionStrategy,
1483+
$partitionColumns,
1484+
$inherits,
1485+
$tablespace,
1486+
);
1487+
}
1488+
14551489
#[DocumentationDSL(module: Module::PG_QUERY, type: DSLType::HELPER)]
14561490
function schema_column(
14571491
string $name,

0 commit comments

Comments
 (0)