Skip to content

Commit e10de8e

Browse files
mmolsmoizpgedgepct960
authored
Move 25.2.1 changes to v25_STABLE (#387)
* prevent spock upgrade with auto ddl is enabled (#382) * Auto ddl off warning added before spock upgrade * ensure custom pg_data directory is supported for autoddl check --------- Co-authored-by: Matthew Mols <matt@pgedge.com> * [ACE] Throw meaningful errors for empty tables (#381) * [ACE] Throw meaningful errors for empty tables * Temp fix in interval merging for single row tables * Minor improvement to error message * ensure spock major version compat checks run when using either install and um install (#386) --------- Co-authored-by: Moiz Ibrar <moiz@pgedge.com> Co-authored-by: Tej Kashi <pct960@gmail.com>
1 parent 9e9102e commit e10de8e

File tree

5 files changed

+186
-100
lines changed

5 files changed

+186
-100
lines changed

cli/scripts/ace_core.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,14 @@ def table_diff(td_task: TableDiffTask, skip_all_checks: bool = False):
701701
)
702702
ref_cur.execute(offsets_query)
703703
raw_offsets = ref_cur.fetchall()
704-
pkey_offsets = ace.process_pkey_offsets(raw_offsets)
704+
705+
if all(
706+
pkey_range is None for offset in raw_offsets for pkey_range in offset
707+
):
708+
pkey_offsets = []
709+
else:
710+
pkey_offsets = ace.process_pkey_offsets(raw_offsets)
711+
705712
except Exception as e:
706713
context = {
707714
"total_rows": total_rows,
@@ -727,6 +734,11 @@ def get_pkey_offsets(conn, pkey_sql, block_rows):
727734
cur.execute(pkey_sql)
728735
rows = cur.fetchmany(block_rows)
729736

737+
if not rows:
738+
cur.close()
739+
conn.close()
740+
return pkey_offsets
741+
730742
if simple_primary_key:
731743
rows[:] = [str(x[0]) for x in rows]
732744
pkey_offsets.append(([None], [str(rows[0])]))
@@ -764,6 +776,12 @@ def get_pkey_offsets(conn, pkey_sql, block_rows):
764776
conn_with_max_rows, pkey_sql, td_task.block_size
765777
)
766778

779+
if not pkey_offsets:
780+
raise AceException(
781+
f"Table {td_task.fields.l_schema}.{td_task.fields.l_table} is empty "
782+
"on all nodes."
783+
)
784+
767785
# We're done with getting table metadata. Closing all connections.
768786
for conn in conn_list:
769787
conn.close()

cli/scripts/ace_mtree.py

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,9 @@ def build_mtree(mtree_task: MerkleTreeTask) -> None:
476476
f"Error creating mtree objects on {node['name']}: {str(e)}"
477477
)
478478

479+
if not ref_node:
480+
raise AceException(f"Table {schema}.{table} is empty on all nodes.")
481+
479482
print(f"Using node {ref_node['name']} as the reference node")
480483

481484
block_ranges = []
@@ -505,6 +508,12 @@ def build_mtree(mtree_task: MerkleTreeTask) -> None:
505508
with conn.cursor() as cur:
506509
cur.execute(offsets_query)
507510
offsets = cur.fetchall()
511+
512+
if all(
513+
pkey_range is None for offset in offsets for pkey_range in offset
514+
):
515+
raise AceException(f"Table {schema}.{table} is empty on all nodes.")
516+
508517
block_ranges = process_block_ranges(offsets)
509518

510519
if mtree_task.write_ranges:
@@ -1422,6 +1431,7 @@ def update_mtree(mtree_task: MerkleTreeTask, skip_all_checks=False) -> None:
14221431
key = mtree_task.fields.key
14231432
key_columns = key.split(",")
14241433
is_composite = len(key_columns) > 1
1434+
block_size = None
14251435

14261436
# we need to first read the metadata to figure out what the
14271437
# block size the tree was built with
@@ -1436,12 +1446,24 @@ def update_mtree(mtree_task: MerkleTreeTask, skip_all_checks=False) -> None:
14361446
table=sql.Literal(table),
14371447
)
14381448
)
1439-
block_size = cur.fetchone()[0]
1449+
result = cur.fetchone()
1450+
1451+
if result is not None:
1452+
block_size = result[0]
1453+
14401454
except Exception as e:
1441-
raise AceException(f"Error getting block size from metadata: {str(e)}")
1455+
raise AceException(
1456+
f"Error getting block size from metadata: {str(e)}\n"
1457+
"Please initialise the Merkle tree objects first with\n"
1458+
f"./pgedge ace mtree init {mtree_task.cluster_name}"
1459+
)
14421460

14431461
if not block_size:
1444-
raise AceException(f"Block size not found for {schema}.{table}")
1462+
raise AceException(
1463+
f"Missing metadata for {schema}.{table}.\nPlease build the tree first "
1464+
f"with ./pgedge ace mtree build {mtree_task.cluster_name} "
1465+
f"{schema}.{table}"
1466+
)
14451467

14461468
mtree_task.block_size = block_size
14471469

@@ -1966,15 +1988,22 @@ def get_pkey_batches(
19661988

19671989
boundaries = sorted(set(boundaries))
19681990

1991+
if not boundaries:
1992+
return []
1993+
19691994
slices = []
1970-
for i in range(len(boundaries) - 1):
1971-
s = boundaries[i]
1972-
e = boundaries[i + 1]
1973-
1974-
# We'll form the half-open interval [s, e)
1975-
# but only keep it if it intersects any mismatch.
1976-
if interval_in_union(s, e, all_ranges):
1977-
slices.append((s, e))
1995+
if len(boundaries) == 1:
1996+
s = boundaries[0]
1997+
slices.append((s, s))
1998+
else:
1999+
for i in range(len(boundaries) - 1):
2000+
s = boundaries[i]
2001+
e = boundaries[i + 1]
2002+
2003+
# We'll form the half-open interval [s, e)
2004+
# but only keep it if it intersects any mismatch.
2005+
if interval_in_union(s, e, all_ranges):
2006+
slices.append((s, e))
19782007

19792008
# TODO: Fix this!
19802009
# # We always need the last boundary to be (max_key, None), otherwise,

cli/scripts/cli.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,8 @@ def read(self, size):
322322
return io.FileIO.read(self, size)
323323

324324

325+
_SPOCK50_RE = re.compile(r"^spock50(?:-pg\d+)?$", re.IGNORECASE)
326+
325327
# Install Component ######################################################
326328
def install_comp(p_app, p_ver=0, p_rver=None, p_re_install=False):
327329
util.message(f"cli.install_comp(p_app={p_app}, p_ver={p_ver}, p_rver={p_rver}, p_re_install={p_re_install})", "debug")
@@ -346,6 +348,10 @@ def install_comp(p_app, p_ver=0, p_rver=None, p_re_install=False):
346348
print(errmsg)
347349
exit_cleanly(1)
348350

351+
# Trigger pre-check ONLY for Spock 5.0 artifacts
352+
if _SPOCK50_RE.match(p_app):
353+
util.validate_spock_upgrade(p_app) # should sys.exit(1) on failure; otherwise just returns
354+
349355
state = util.get_comp_state(p_app)
350356

351357
if state in ["Enabled", "Disabled"]:

cli/scripts/um.py

Lines changed: 0 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import fire, meta, util
66
isJSON = util.isJSON
77
import re
8-
import sqlite3 as _sqlite3
98

109
MY_HOME = util.MY_HOME
1110

@@ -83,20 +82,8 @@ def update():
8382
"""Update with a new list of available components."""
8483
run_cmd("update")
8584

86-
87-
88-
# Match both "spock50" and "spock50-pg17" (and variants like spock5, spock5-pg16)
89-
_SPOCK5_NAME_RE = re.compile(r"^spock5(?:0)?(?:-pg\d+)?$", re.IGNORECASE)
90-
_SPOCK5_VER_RE = re.compile(r"^5\.", re.IGNORECASE)
91-
_SPOCK50_RE = re.compile(r"^spock50(?:-pg\d+)?$", re.IGNORECASE)
92-
9385
def install(component, active=True):
9486
"""Install a component."""
95-
96-
# Trigger pre-check ONLY for Spock 5.0 artifacts
97-
if _SPOCK50_RE.match(component):
98-
validate_spock_upgrade(component) # should sys.exit(1) on failure; otherwise just returns
99-
10087
# Common path (no duplication)
10188
if active not in (True, False):
10289
util.exit_message("'active' parm must be True or False")
@@ -105,8 +92,6 @@ def install(component, active=True):
10592
util.message(f"um.install({cmd} {component})", "debug")
10693
run_cmd(cmd, component)
10794

108-
109-
11095
def remove(component):
11196
"""Uninstall a component."""
11297
installed_comp_list = meta.get_component_list()
@@ -244,78 +229,6 @@ def verify_metadata(Project="", Stage="prod", IsCurrent=0):
244229

245230
meta.pretty_sql(sql)
246231

247-
def validate_spock_upgrade(spock_component):
248-
"""
249-
Validate Spock↔PostgreSQL compatibility for an upcoming Spock 5 install.
250-
"""
251-
252-
DB_PATH = "data/conf/db_local.db"
253-
254-
SPOCK_SQL = """
255-
SELECT version AS spock_ver, component AS spock_comp
256-
FROM components
257-
WHERE component LIKE 'spock%'
258-
ORDER BY CASE
259-
WHEN version LIKE '5.%' THEN 5
260-
WHEN version LIKE '4.%' THEN 4
261-
ELSE 0
262-
END DESC,
263-
version DESC
264-
LIMIT 1;
265-
"""
266-
267-
PG_SQL = """
268-
SELECT version AS pg_ver
269-
FROM components
270-
WHERE component LIKE 'pg__'
271-
ORDER BY CAST(substr(component, 3) AS INTEGER) DESC
272-
LIMIT 1;
273-
"""
274-
275-
try:
276-
with _sqlite3.connect(DB_PATH) as conn:
277-
sp_row = conn.execute(SPOCK_SQL).fetchone()
278-
pg_row = conn.execute(PG_SQL).fetchone()
279-
except _sqlite3.Error as err:
280-
sys.exit(f"ERROR: SQLite query failed: {err}")
281-
282-
if not pg_row:
283-
sys.exit("ERROR: No PostgreSQL version row found.")
284-
285-
existing_pg_ver = pg_row[0]
286-
existing_spock_ver, existing_spock_comp = (sp_row or (None, None))
287-
288-
# Already on Spock 5? No-op.
289-
if existing_spock_ver and (
290-
_SPOCK5_NAME_RE.match(existing_spock_comp) or _SPOCK5_VER_RE.match(existing_spock_ver)
291-
):
292-
return 0
293-
294-
# Trim "spock" off the front to get the version (e.g., "spock50" -> "50")
295-
requested_spock_ver = spock_component
296-
if requested_spock_ver and requested_spock_ver.lower().startswith("spock"):
297-
requested_spock_ver = requested_spock_ver[5:]
298-
299-
# Downtime warning
300-
banner = "=" * 80
301-
302-
if existing_spock_ver:
303-
# Case 2: Spock 4.x installed, this is a major upgrade
304-
print(f"\n{banner}")
305-
print("*** WARNING: This operation will cause downtime! ***")
306-
print(f"{banner}\n")
307-
print(f"Detected existing Spock version {existing_spock_ver} on PostgreSQL {existing_pg_ver}")
308-
309-
try:
310-
util.validate_spock_pg_compat(requested_spock_ver, existing_pg_ver)
311-
except Exception as exc:
312-
sys.exit(f"ERROR: Compatibility check failed: {exc}")
313-
314-
if existing_spock_ver:
315-
print("Compatibility check passed.")
316-
317-
return 0
318-
319232
if __name__ == "__main__":
320233
fire.Fire(
321234
{

0 commit comments

Comments
 (0)