Skip to content

Commit df2dcab

Browse files
authored
Merge branch 'main' into wjdejidew
2 parents 852bb0a + 2e27d4c commit df2dcab

File tree

18 files changed

+2652
-194
lines changed

18 files changed

+2652
-194
lines changed

.github/workflows/build-cloudberry-rocky8.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,10 @@ jobs:
339339
},
340340
{"test":"ic-cbdb-parallel",
341341
"make_configs":["src/test/regress:installcheck-cbdb-parallel"]
342+
},
343+
{"test":"ic-recovery",
344+
"make_configs":["src/test/recovery:installcheck"],
345+
"enable_core_check":false
342346
}
343347
]
344348
}'

contrib/pax_storage/src/test/regress/expected/groupingsets_optimizer.out

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -949,21 +949,21 @@ select v.c, (select count(*) from gstest2 group by () having v.c)
949949
explain (costs off)
950950
select v.c, (select count(*) from gstest2 group by () having v.c)
951951
from (values (false),(true)) v(c) order by v.c;
952-
QUERY PLAN
953-
--------------------------------------------------------------------------
954-
Sort
955-
Sort Key: "*VALUES*".column1
956-
-> Values Scan on "*VALUES*"
957-
SubPlan 1
958-
-> Aggregate
959-
Group Key: ()
960-
Filter: "*VALUES*".column1
961-
-> Result
962-
One-Time Filter: "*VALUES*".column1
963-
-> Materialize
964-
-> Gather Motion 3:1 (slice1; segments: 3)
952+
QUERY PLAN
953+
--------------------------------------------------------------------
954+
Result
955+
-> Sort
956+
Sort Key: "Values".column1
957+
-> Values Scan on "Values"
958+
SubPlan 1
959+
-> Result
960+
One-Time Filter: "Values".column1
961+
-> Finalize Aggregate
962+
-> Materialize
963+
-> Gather Motion 3:1 (slice1; segments: 3)
964+
-> Partial Aggregate
965965
-> Seq Scan on gstest2
966-
Optimizer: Postgres query optimizer
966+
Optimizer: GPORCA
967967
(13 rows)
968968

969969
-- HAVING with GROUPING queries

devops/build/automation/cloudberry/scripts/parse-results.pl

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
my @ignored_test_list = ();
111111

112112
while (<$fh>) {
113-
# Match the summary lines
113+
# Match the summary lines (pg_regress format)
114114
if (/All (\d+) tests passed\./) {
115115
$status = 'passed';
116116
$total_tests = $1;
@@ -132,8 +132,22 @@
132132
$status = 'failed';
133133
$failed_tests = $1 - $3;
134134
$ignored_tests = $3;
135-
$total_tests = $2;
136-
$passed_tests = $2 - $1;
135+
136+
# TAP/prove summary format: "Files=N, Tests=N, ..."
137+
} elsif (/^Files=\d+, Tests=(\d+),/) {
138+
$total_tests = $1;
139+
140+
# TAP/prove result: "Result: PASS" or "Result: FAIL"
141+
} elsif (/^Result: PASS/) {
142+
$status = 'passed';
143+
$passed_tests = $total_tests;
144+
$failed_tests = 0;
145+
} elsif (/^Result: FAIL/) {
146+
$status = 'failed';
147+
148+
# TAP individual test failure: " t/xxx.pl (Wstat: ...)"
149+
} elsif (/^\s+(t\/\S+\.pl)\s+\(Wstat:/) {
150+
push @failed_test_list, $1;
137151
}
138152

139153
# Capture failed tests
@@ -150,8 +164,15 @@
150164
# Close the log file
151165
close $fh;
152166

153-
# Validate failed test count matches found test names
154-
if ($status eq 'failed' && scalar(@failed_test_list) != $failed_tests) {
167+
# For TAP format, derive failed/passed counts from collected test names
168+
if ($status eq 'failed' && $failed_tests == 0 && scalar(@failed_test_list) > 0) {
169+
$failed_tests = scalar(@failed_test_list);
170+
$passed_tests = $total_tests - $failed_tests if $total_tests > 0;
171+
}
172+
173+
# Validate failed test count matches found test names (pg_regress format only)
174+
if ($status eq 'failed' && $failed_tests > 0 && scalar(@failed_test_list) > 0
175+
&& scalar(@failed_test_list) != $failed_tests) {
155176
print "Error: Found $failed_tests failed tests in summary but found " . scalar(@failed_test_list) . " failed test names\n";
156177
print "Failed test names found:\n";
157178
foreach my $test (@failed_test_list) {

pom.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,6 @@ code or new licensing patterns.
572572

573573
<exclude>src/backend/gporca/cmake/FindXerces.cmake</exclude>
574574
<exclude>src/backend/gporca/concourse/build_and_test.py</exclude>
575-
<exclude>src/backend/gporca/concourse/xerces-c/build_xerces.py</exclude>
576575
<exclude>src/backend/gporca/concourse/xerces-c/xerces-c-3.1.2.tar.gz.sha256</exclude>
577576
<exclude>src/backend/gporca/server/fixdxl.sh</exclude>
578577
<exclude>src/backend/gporca/server/include/unittest/gpopt/operators/CScalarIsDistinctFromTest.h</exclude>

src/backend/access/transam/xact.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3036,13 +3036,15 @@ CommitTransaction(void)
30363036
DoPendingDbDeletes(true);
30373037

30383038
/*
3039-
* Only QD holds the session level lock this long for a movedb operation.
3040-
* This is to prevent another transaction from moving database objects into
3041-
* the source database oid directory while it is being deleted. We don't
3042-
* worry about aborts as we release session level locks automatically during
3043-
* an abort as opposed to a commit.
3044-
*/
3045-
if(Gp_role == GP_ROLE_DISPATCH || IS_SINGLENODE())
3039+
* Release the session level lock held for a movedb operation. This is to
3040+
* prevent another transaction from moving database objects into the source
3041+
* database oid directory while it is being deleted. We don't worry about
3042+
* aborts as we release session level locks automatically during an abort
3043+
* as opposed to a commit. We must also release in utility mode (e.g.
3044+
* standalone backends used in TAP tests).
3045+
*/
3046+
if (Gp_role == GP_ROLE_DISPATCH || Gp_role == GP_ROLE_UTILITY ||
3047+
IS_SINGLENODE())
30463048
MoveDbSessionLockRelease();
30473049

30483050
/*

src/backend/commands/tablespace.c

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
328328
}
329329

330330
if (!location)
331-
location = pstrdup(stmt->location);
331+
location = pstrdup(stmt->location ? stmt->location : "");
332332

333333
if (stmt->filehandler)
334334
fileHandler = pstrdup(stmt->filehandler);
@@ -1230,14 +1230,19 @@ destroy_tablespace_directories(Oid tablespaceoid, bool redo)
12301230
linkloc = pstrdup(linkloc_with_version_dir);
12311231
get_parent_directory(linkloc);
12321232

1233-
/* Remove the symlink target directory if it exists or is valid. */
1233+
/*
1234+
* Remove the symlink target directory if it exists or is valid.
1235+
* If linkloc is a directory (e.g. in-place tablespace), readlink()
1236+
* will fail with EINVAL, which we can safely skip.
1237+
*/
12341238
rllen = readlink(linkloc, link_target_dir, sizeof(link_target_dir));
12351239
if(rllen < 0)
12361240
{
1237-
ereport(redo ? LOG : ERROR,
1238-
(errcode_for_file_access(),
1239-
errmsg("could not read symbolic link \"%s\": %m",
1240-
linkloc)));
1241+
if (errno != EINVAL)
1242+
ereport(redo ? LOG : ERROR,
1243+
(errcode_for_file_access(),
1244+
errmsg("could not read symbolic link \"%s\": %m",
1245+
linkloc)));
12411246
}
12421247
else if(rllen >= sizeof(link_target_dir))
12431248
{

src/backend/gporca/concourse/xerces-c/build_xerces.py

Lines changed: 0 additions & 93 deletions
This file was deleted.

src/backend/gporca/libgpopt/src/xforms/CSubqueryHandler.cpp

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,93 @@ CSubqueryHandler::FProjectCountSubquery(CExpression *pexprSubquery,
308308
}
309309

310310

311+
//---------------------------------------------------------------------------
312+
// @function:
313+
// FContainsEmptyGbAgg
314+
//
315+
// @doc:
316+
// Return true if pexpr contains a GbAgg with empty grouping columns
317+
// (i.e., GROUP BY ())
318+
//
319+
//---------------------------------------------------------------------------
320+
static BOOL
321+
FContainsEmptyGbAgg(CExpression *pexpr)
322+
{
323+
if (COperator::EopLogicalGbAgg == pexpr->Pop()->Eopid())
324+
{
325+
return 0 == CLogicalGbAgg::PopConvert(pexpr->Pop())->Pdrgpcr()->Size();
326+
}
327+
const ULONG arity = pexpr->Arity();
328+
for (ULONG ul = 0; ul < arity; ul++)
329+
{
330+
CExpression *pexprChild = (*pexpr)[ul];
331+
if (pexprChild->Pop()->FLogical() && FContainsEmptyGbAgg(pexprChild))
332+
{
333+
return true;
334+
}
335+
}
336+
return false;
337+
}
338+
339+
340+
//---------------------------------------------------------------------------
341+
// @function:
342+
// FHasCorrelatedSelectAboveGbAgg
343+
//
344+
// @doc:
345+
// Return true if pexpr has a CLogicalSelect with outer references in its
346+
// filter predicate that sits above a GROUP BY () aggregate. This pattern
347+
// arises when a correlated scalar subquery has a correlated HAVING clause,
348+
// e.g. "SELECT count(*) FROM t GROUP BY () HAVING outer_col".
349+
//
350+
// When such a pattern exists the scalar subquery must NOT be decorrelated
351+
// with COALESCE(count,0) semantics: if the HAVING condition is false the
352+
// subquery should return NULL (no rows), not 0.
353+
//
354+
//---------------------------------------------------------------------------
355+
static BOOL
356+
FHasCorrelatedSelectAboveGbAgg(CExpression *pexpr)
357+
{
358+
// Stop recursion at a GbAgg boundary: we are looking for a Select
359+
// that sits *above* a GbAgg, so once we reach the GbAgg there is
360+
// nothing more to check in this branch.
361+
if (COperator::EopLogicalGbAgg == pexpr->Pop()->Eopid())
362+
{
363+
return false;
364+
}
365+
366+
if (COperator::EopLogicalSelect == pexpr->Pop()->Eopid() &&
367+
pexpr->HasOuterRefs())
368+
{
369+
// The Select has outer references somewhere in its subtree.
370+
// Check whether they originate from the filter (child 1) rather
371+
// than from the logical child (child 0). If the logical child has
372+
// no outer refs but the Select as a whole does, the outer refs must
373+
// come from the filter predicate — exactly the correlated-HAVING
374+
// pattern we want to detect.
375+
CExpression *pexprLogicalChild = (*pexpr)[0];
376+
if (!pexprLogicalChild->HasOuterRefs() &&
377+
FContainsEmptyGbAgg(pexprLogicalChild))
378+
{
379+
return true;
380+
}
381+
}
382+
383+
// Recurse into logical children only.
384+
const ULONG arity = pexpr->Arity();
385+
for (ULONG ul = 0; ul < arity; ul++)
386+
{
387+
CExpression *pexprChild = (*pexpr)[ul];
388+
if (pexprChild->Pop()->FLogical() &&
389+
FHasCorrelatedSelectAboveGbAgg(pexprChild))
390+
{
391+
return true;
392+
}
393+
}
394+
return false;
395+
}
396+
397+
311398
//---------------------------------------------------------------------------
312399
// @function:
313400
// CSubqueryHandler::SSubqueryDesc::SetCorrelatedExecution
@@ -382,6 +469,21 @@ CSubqueryHandler::Psd(CMemoryPool *mp, CExpression *pexprSubquery,
382469
// set flag of correlated execution
383470
psd->SetCorrelatedExecution();
384471

472+
// A correlated scalar subquery of the form
473+
// SELECT count(*) FROM t GROUP BY () HAVING <outer_ref_condition>
474+
// must execute as a correlated SubPlan. After NormalizeHaving() the HAVING
475+
// clause becomes a CLogicalSelect with outer refs sitting above the GbAgg.
476+
// If we decorrelate such a subquery the join filter replaces the HAVING
477+
// condition, but a LEFT JOIN returns 0 (not NULL) for count(*) when no
478+
// rows match — which is semantically wrong. Forcing correlated execution
479+
// preserves the correct NULL-when-no-rows semantics.
480+
if (!psd->m_fCorrelatedExecution && psd->m_fHasCountAgg &&
481+
psd->m_fHasOuterRefs &&
482+
FHasCorrelatedSelectAboveGbAgg(pexprInner))
483+
{
484+
psd->m_fCorrelatedExecution = true;
485+
}
486+
385487
return psd;
386488
}
387489

@@ -753,8 +855,19 @@ CSubqueryHandler::FCreateOuterApplyForScalarSubquery(
753855
*ppexprNewOuter = pexprPrj;
754856

755857
BOOL fGeneratedByQuantified = popSubquery->FGeneratedByQuantified();
858+
859+
// When GROUP BY () has a correlated HAVING clause (now represented as a
860+
// CLogicalSelect with outer refs sitting above the GbAgg), the subquery
861+
// must return NULL — not 0 — when the HAVING condition is false.
862+
// Applying COALESCE(count,0) would incorrectly convert that NULL to 0,
863+
// so we skip the special count(*) semantics in that case.
864+
BOOL fCorrelatedHavingAboveEmptyGby =
865+
(fHasCountAggMatchingColumn && 0 == pgbAgg->Pdrgpcr()->Size() &&
866+
FHasCorrelatedSelectAboveGbAgg((*pexprSubquery)[0]));
867+
756868
if (fGeneratedByQuantified ||
757-
(fHasCountAggMatchingColumn && 0 == pgbAgg->Pdrgpcr()->Size()))
869+
(fHasCountAggMatchingColumn && 0 == pgbAgg->Pdrgpcr()->Size() &&
870+
!fCorrelatedHavingAboveEmptyGby))
758871
{
759872
CMDAccessor *md_accessor = COptCtxt::PoctxtFromTLS()->Pmda();
760873
const IMDTypeInt8 *pmdtypeint8 = md_accessor->PtMDType<IMDTypeInt8>();

0 commit comments

Comments
 (0)