Skip to content

Commit 1bd587e

Browse files
authored
fix: support for explicit schemas in update/merge/delete query builders (#2245)
1 parent a91fa1f commit 1bd587e

12 files changed

Lines changed: 472 additions & 6 deletions

File tree

src/lib/postgresql/src/Flow/PostgreSql/QueryBuilder/Delete/DeleteBuilder.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Flow\PostgreSql\QueryBuilder\Delete;
66

77
use Flow\PostgreSql\Protobuf\AST\{Alias, DeleteStmt, Node, RangeVar, ResTarget};
8-
use Flow\PostgreSql\QueryBuilder\AstToSql;
8+
use Flow\PostgreSql\QueryBuilder\{AstToSql, QualifiedIdentifier};
99
use Flow\PostgreSql\QueryBuilder\Clause\WithClause;
1010
use Flow\PostgreSql\QueryBuilder\Condition\{Condition, ConditionFactory};
1111
use Flow\PostgreSql\QueryBuilder\Exception\InvalidAstException;
@@ -33,6 +33,7 @@
3333
private function __construct(
3434
private ?WithClause $with = null,
3535
private ?string $table = null,
36+
private ?string $schema = null,
3637
private ?string $alias = null,
3738
private array $using = [],
3839
private ?Condition $where = null,
@@ -59,6 +60,12 @@ public static function fromAst(DeleteStmt $deleteStmt) : static
5960
throw InvalidAstException::missingRequiredField('relname', 'RangeVar');
6061
}
6162

63+
$schema = $relation->getSchemaname();
64+
65+
if ($schema === '') {
66+
$schema = null;
67+
}
68+
6269
$alias = $relation->getAlias();
6370
$aliasName = $alias !== null ? $alias->getAliasname() : null;
6471

@@ -125,6 +132,7 @@ public static function fromAst(DeleteStmt $deleteStmt) : static
125132
return new self(
126133
with: $withClause,
127134
table: $tableName,
135+
schema: $schema,
128136
alias: $aliasName,
129137
using: $using,
130138
where: $whereCondition,
@@ -139,9 +147,12 @@ public static function with(WithClause $with) : DeleteFromStep
139147

140148
public function from(string $table, ?string $alias = null) : DeleteUsingStep
141149
{
150+
$identifier = QualifiedIdentifier::parse($table);
151+
142152
return new self(
143153
with: $this->with,
144-
table: $table,
154+
table: $identifier->name(),
155+
schema: $identifier->schema(),
145156
alias: $alias,
146157
using: $this->using,
147158
where: $this->where,
@@ -154,6 +165,7 @@ public function returning(Expression ...$expressions) : DeleteFinalStep
154165
return new self(
155166
with: $this->with,
156167
table: $this->table,
168+
schema: $this->schema,
157169
alias: $this->alias,
158170
using: $this->using,
159171
where: $this->where,
@@ -166,6 +178,7 @@ public function returningAll() : DeleteFinalStep
166178
return new self(
167179
with: $this->with,
168180
table: $this->table,
181+
schema: $this->schema,
169182
alias: $this->alias,
170183
using: $this->using,
171184
where: $this->where,
@@ -186,6 +199,10 @@ public function toAst() : DeleteStmt
186199
'inh' => true,
187200
]);
188201

202+
if ($this->schema !== null) {
203+
$rangeVar->setSchemaname($this->schema);
204+
}
205+
189206
if ($this->alias !== null) {
190207
$alias = new Alias([
191208
'aliasname' => $this->alias,
@@ -242,6 +259,7 @@ public function using(TableReference ...$tables) : DeleteWhereStep
242259
return new self(
243260
with: $this->with,
244261
table: $this->table,
262+
schema: $this->schema,
245263
alias: $this->alias,
246264
using: $tables,
247265
where: $this->where,
@@ -254,6 +272,7 @@ public function where(Condition $condition) : DeleteReturningStep
254272
return new self(
255273
with: $this->with,
256274
table: $this->table,
275+
schema: $this->schema,
257276
alias: $this->alias,
258277
using: $this->using,
259278
where: $condition,

src/lib/postgresql/src/Flow/PostgreSql/QueryBuilder/Merge/MergeBuilder.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ private function __construct(
2727
private ?string $schema = null,
2828
private ?string $tableAlias = null,
2929
private ?string $sourceTable = null,
30+
private ?string $sourceSchema = null,
3031
private ?SelectFinalStep $sourceSelect = null,
3132
private ?string $sourceAlias = null,
3233
private ?Condition $joinCondition = null,
@@ -52,6 +53,7 @@ public function addWhenClause(MergeWhenClauseData $clause) : MergeWhenStep
5253
$this->schema,
5354
$this->tableAlias,
5455
$this->sourceTable,
56+
$this->sourceSchema,
5557
$this->sourceSelect,
5658
$this->sourceAlias,
5759
$this->joinCondition,
@@ -69,6 +71,7 @@ public function into(string $table, ?string $alias = null) : MergeUsingStep
6971
$identifier->schema(),
7072
$alias,
7173
$this->sourceTable,
74+
$this->sourceSchema,
7275
$this->sourceSelect,
7376
$this->sourceAlias,
7477
$this->joinCondition,
@@ -84,6 +87,7 @@ public function on(Condition $condition) : MergeWhenStep
8487
$this->schema,
8588
$this->tableAlias,
8689
$this->sourceTable,
90+
$this->sourceSchema,
8791
$this->sourceSelect,
8892
$this->sourceAlias,
8993
$condition,
@@ -148,6 +152,11 @@ public function toAst() : MergeStmt
148152
'relname' => $this->sourceTable ?? '',
149153
'inh' => true,
150154
]);
155+
156+
if ($this->sourceSchema !== null) {
157+
$sourceRangeVar->setSchemaname($this->sourceSchema);
158+
}
159+
151160
$alias = new Alias();
152161
$alias->setAliasname($this->sourceAlias);
153162
$sourceRangeVar->setAlias($alias);
@@ -187,19 +196,23 @@ public function using(string|SelectFinalStep $source, string $alias) : MergeOnSt
187196
$this->schema,
188197
$this->tableAlias,
189198
null,
199+
null,
190200
$source,
191201
$alias,
192202
$this->joinCondition,
193203
$this->whenClauses,
194204
);
195205
}
196206

207+
$identifier = QualifiedIdentifier::parse($source);
208+
197209
return new self(
198210
$this->with,
199211
$this->table,
200212
$this->schema,
201213
$this->tableAlias,
202-
$source,
214+
$identifier->name(),
215+
$identifier->schema(),
203216
null,
204217
$alias,
205218
$this->joinCondition,

src/lib/postgresql/src/Flow/PostgreSql/QueryBuilder/Update/UpdateBuilder.php

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Flow\PostgreSql\QueryBuilder\Update;
66

77
use Flow\PostgreSql\Protobuf\AST\{Alias, Node, RangeVar, ResTarget, UpdateStmt};
8-
use Flow\PostgreSql\QueryBuilder\AstToSql;
8+
use Flow\PostgreSql\QueryBuilder\{AstToSql, QualifiedIdentifier};
99
use Flow\PostgreSql\QueryBuilder\Clause\WithClause;
1010
use Flow\PostgreSql\QueryBuilder\Condition\{Condition, ConditionFactory};
1111
use Flow\PostgreSql\QueryBuilder\Exception\{InvalidAstException, InvalidExpressionException};
@@ -27,6 +27,7 @@
2727
private function __construct(
2828
private ?WithClause $with = null,
2929
private ?string $table = null,
30+
private ?string $schema = null,
3031
private ?string $alias = null,
3132
private array $assignments = [],
3233
private array $from = [],
@@ -62,6 +63,12 @@ public static function fromAst(UpdateStmt $updateStmt) : static
6263
throw InvalidAstException::missingRequiredField('relname', 'RangeVar');
6364
}
6465

66+
$schema = $relation->getSchemaname();
67+
68+
if ($schema === '') {
69+
$schema = null;
70+
}
71+
6572
$alias = null;
6673

6774
if ($relation->hasAlias()) {
@@ -127,7 +134,7 @@ public static function fromAst(UpdateStmt $updateStmt) : static
127134
}
128135
}
129136

130-
return new self($with, $table, $alias, $assignments, $from, $where, $returning);
137+
return new self($with, $table, $schema, $alias, $assignments, $from, $where, $returning);
131138
}
132139

133140
public static function with(WithClause $with) : UpdateTableStep
@@ -140,6 +147,7 @@ public function from(TableReference ...$tables) : UpdateWhereStep
140147
return new self(
141148
with: $this->with,
142149
table: $this->table,
150+
schema: $this->schema,
143151
alias: $this->alias,
144152
assignments: $this->assignments,
145153
from: $tables,
@@ -153,6 +161,7 @@ public function returning(Expression ...$expressions) : UpdateFinalStep
153161
return new self(
154162
with: $this->with,
155163
table: $this->table,
164+
schema: $this->schema,
156165
alias: $this->alias,
157166
assignments: $this->assignments,
158167
from: $this->from,
@@ -171,6 +180,7 @@ public function set(string $column, Expression $value) : UpdateSetStep
171180
return new self(
172181
with: $this->with,
173182
table: $this->table,
183+
schema: $this->schema,
174184
alias: $this->alias,
175185
assignments: [...$this->assignments, $column => $value],
176186
from: $this->from,
@@ -184,6 +194,7 @@ public function setAll(array $assignments) : UpdateFromStep
184194
return new self(
185195
with: $this->with,
186196
table: $this->table,
197+
schema: $this->schema,
187198
alias: $this->alias,
188199
assignments: [...$this->assignments, ...$assignments],
189200
from: $this->from,
@@ -206,6 +217,10 @@ public function toAst() : UpdateStmt
206217

207218
$rangeVar = new RangeVar(['relname' => $this->table, 'inh' => true]);
208219

220+
if ($this->schema !== null) {
221+
$rangeVar->setSchemaname($this->schema);
222+
}
223+
209224
if ($this->alias !== null) {
210225
$aliasProto = new Alias(['aliasname' => $this->alias]);
211226
$rangeVar->setAlias($aliasProto);
@@ -271,9 +286,12 @@ public function toAst() : UpdateStmt
271286

272287
public function update(string $table, ?string $alias = null) : UpdateSetStep
273288
{
289+
$identifier = QualifiedIdentifier::parse($table);
290+
274291
return new self(
275292
with: $this->with,
276-
table: $table,
293+
table: $identifier->name(),
294+
schema: $identifier->schema(),
277295
alias: $alias,
278296
assignments: $this->assignments,
279297
from: $this->from,
@@ -287,6 +305,7 @@ public function where(Condition $condition) : UpdateReturningStep
287305
return new self(
288306
with: $this->with,
289307
table: $this->table,
308+
schema: $this->schema,
290309
alias: $this->alias,
291310
assignments: $this->assignments,
292311
from: $this->from,

src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/QueryBuilder/Database/DeleteDatabaseTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727

2828
final class DeleteDatabaseTest extends DatabaseTestCase
2929
{
30+
private const SCHEMA_NAME = 'flow_postgres_test_delete_schema';
31+
32+
private const SCHEMA_TABLE = 'flow_postgres_schema_logs';
33+
3034
private const TABLE_ARCHIVE = 'flow_postgres_log_archive';
3135

3236
private const TABLE_LOGS = 'flow_postgres_logs';
@@ -79,6 +83,7 @@ protected function tearDown() : void
7983
{
8084
$this->dropTableIfExists(self::TABLE_ARCHIVE);
8185
$this->dropTableIfExists(self::TABLE_LOGS);
86+
$this->execute('DROP SCHEMA IF EXISTS ' . self::SCHEMA_NAME . ' CASCADE');
8287

8388
parent::tearDown();
8489
}
@@ -159,6 +164,46 @@ public function test_delete_with_returning_all() : void
159164
self::assertArrayHasKey('created_at', $row);
160165
}
161166

167+
public function test_delete_with_schema_qualified_table() : void
168+
{
169+
$this->execute('CREATE SCHEMA IF NOT EXISTS ' . self::SCHEMA_NAME);
170+
171+
$this->execute(
172+
create()->table(self::SCHEMA_TABLE, self::SCHEMA_NAME)
173+
->column(column('id', data_type_serial()))
174+
->column(column('level', data_type_varchar(20))->notNull())
175+
->column(column('message', data_type_text()))
176+
->constraint(primary_key('id'))
177+
->toSql()
178+
);
179+
180+
$this->execute(
181+
insert()
182+
->into(self::SCHEMA_NAME . '.' . self::SCHEMA_TABLE)
183+
->columns('level', 'message')
184+
->values(literal('DEBUG'), literal('Test message'))
185+
->values(literal('INFO'), literal('Info message'))
186+
->toSql()
187+
);
188+
189+
$query = delete()
190+
->from(self::SCHEMA_NAME . '.' . self::SCHEMA_TABLE)
191+
->where(eq(col('level'), literal('DEBUG')));
192+
193+
$result = $this->execute($query->toSql());
194+
195+
self::assertNotFalse($result);
196+
self::assertSame(1, $this->affectedRows($result));
197+
198+
$check = $this->execute(
199+
select(agg_count(star())->as('cnt'))
200+
->from(table(self::SCHEMA_TABLE, self::SCHEMA_NAME))
201+
->toSql()
202+
);
203+
$row = $this->fetchOne($check);
204+
self::assertSame('1', $row['cnt']);
205+
}
206+
162207
public function test_delete_with_using() : void
163208
{
164209
$query = delete()

src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/QueryBuilder/Database/InsertDatabaseTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626

2727
final class InsertDatabaseTest extends DatabaseTestCase
2828
{
29+
private const SCHEMA_NAME = 'flow_postgres_test_insert_schema';
30+
31+
private const SCHEMA_TABLE = 'flow_postgres_schema_products';
32+
2933
private const TABLE_PRODUCTS = 'flow_postgres_products';
3034

3135
protected function setUp() : void
@@ -48,6 +52,7 @@ protected function setUp() : void
4852
protected function tearDown() : void
4953
{
5054
$this->dropTableIfExists(self::TABLE_PRODUCTS);
55+
$this->execute('DROP SCHEMA IF EXISTS ' . self::SCHEMA_NAME . ' CASCADE');
5156

5257
parent::tearDown();
5358
}
@@ -189,4 +194,37 @@ public function test_insert_with_returning_all() : void
189194
self::assertArrayHasKey('price', $row);
190195
self::assertArrayHasKey('stock', $row);
191196
}
197+
198+
public function test_insert_with_schema_qualified_table() : void
199+
{
200+
$this->execute('CREATE SCHEMA IF NOT EXISTS ' . self::SCHEMA_NAME);
201+
202+
$this->execute(
203+
create()->table(self::SCHEMA_TABLE, self::SCHEMA_NAME)
204+
->column(column('id', data_type_serial()))
205+
->column(column('sku', data_type_varchar(50))->notNull())
206+
->column(column('name', data_type_varchar(100))->notNull())
207+
->constraint(primary_key('id'))
208+
->toSql()
209+
);
210+
211+
$query = insert()
212+
->into(self::SCHEMA_NAME . '.' . self::SCHEMA_TABLE)
213+
->columns('sku', 'name')
214+
->values(literal('SCHEMA-SKU'), literal('Schema Product'));
215+
216+
$result = $this->execute($query->toSql());
217+
218+
self::assertNotFalse($result);
219+
self::assertSame(1, $this->affectedRows($result));
220+
221+
$check = $this->execute(
222+
select(col('name'))
223+
->from(table(self::SCHEMA_TABLE, self::SCHEMA_NAME))
224+
->where(eq(col('sku'), literal('SCHEMA-SKU')))
225+
->toSql()
226+
);
227+
$row = $this->fetchOne($check);
228+
self::assertSame('Schema Product', $row['name']);
229+
}
192230
}

0 commit comments

Comments
 (0)