From 722f7ed73f3b32b4c3fbb1a2d4b2fbc10342f0fe Mon Sep 17 00:00:00 2001 From: EdwinBetanc0urt Date: Wed, 8 Jul 2026 17:16:59 -0400 Subject: [PATCH] fix: harden table-ID sequence names in SequenceCheck (invalid names, typo auto-fix, save validation) ## Problem Running the Sequence Check process fails with a PostgreSQL syntax error for sequences flagged IsTableID='Y' whose name is not a valid identifier: CREATE SEQUENCE ATAJO DE COMANDOS PDV_SEQ INCREMENT 1 MINVALUE 0 MAXVALUE 99999999 START 1000000 org.postgresql.util.PSQLException: ERROR: syntax error at or near "DE" A table-ID sequence name must be a valid SQL identifier because the native sequence is created/consumed as _SEQ. Some records are document sequences mis-flagged as IsTableID='Y', and their names contain spaces. The failed CREATE SEQUENCE also aborts the transaction, so the corrected AD_Sequence record is not saved. ## Changes 1. Skip invalid names when creating the native sequence (createMissingNativeSequence): validate the name is a SQL identifier before issuing CREATE SEQUENCE; otherwise log a warning (with Name + AD_Sequence_ID) and skip. 2. Auto-fix typos (fixTableSequenceName): if an invalid name's canonical form (whitespace -> '_') matches a real table (case-insensitive) that has a _ID column, correct the stored name to the real AD_Table.TableName (exact casing). This rescues records that were simple typos so they validate and get their native sequence. 3. Prevent bad data at the source (MSequence.beforeSave): reject saving a sequence flagged IsTableID='Y' when its name is not a valid identifier. Document sequences (IsTableID='N') are unaffected and may keep spaces/accents. ## Notes - Behavior-preserving for valid table-ID sequence names and for document sequences. - Touches SequenceCheck.java and MSequence.java. Co-Authored-By: Claude Opus 4.8 --- .../java/org/compiere/model/MSequence.java | 23 +++++++ .../org/compiere/process/SequenceCheck.java | 62 ++++++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/org/compiere/model/MSequence.java b/base/src/main/java/org/compiere/model/MSequence.java index 0e607742e4..5e5bd6e8a5 100644 --- a/base/src/main/java/org/compiere/model/MSequence.java +++ b/base/src/main/java/org/compiere/model/MSequence.java @@ -1066,6 +1066,29 @@ public MSequence(Properties ctx, int AD_Client_ID, String sequenceName, int Star setCurrentNextSys(StartNo/10); } // MSequence; + /** + * Before Save + * @param newRecord new record + * @return true if it can be saved + */ + @Override + protected boolean beforeSave(boolean newRecord) + { + // A Table ID sequence (IsTableID='Y') name must be a valid SQL identifier, because the + // native sequence is created as _SEQ. A name with spaces or special chars + // (typically a document sequence mis-flagged as IsTableID='Y') breaks "CREATE SEQUENCE" + // and would never be consumed. Reject it to keep the data correct. + if (isTableID()) { + String name = getName(); + if (name == null || !name.matches("[A-Za-z_][A-Za-z0-9_]*")) { + throw new AdempiereException( + Msg.parseTranslation(getCtx(), "@Name@ (@IsTableID@='Y'): '" + name + "'") + ); + } + } + return true; + } // beforeSave + /************************************************************************** * Get Next No and increase current next diff --git a/base/src/main/java/org/compiere/process/SequenceCheck.java b/base/src/main/java/org/compiere/process/SequenceCheck.java index 9262042e0f..df6a950d3c 100644 --- a/base/src/main/java/org/compiere/process/SequenceCheck.java +++ b/base/src/main/java/org/compiere/process/SequenceCheck.java @@ -225,6 +225,16 @@ private static void checkTableID (Properties ctx, SvrProcess sp, s_log.warning("AD_Sequence_ID=" + sequenceId + " could not be loaded, skipping"); return; } + // If the name is invalid (e.g. spaces) but its canonical form (spaces -> '_') + // matches a real table, it was a typo: fix the stored name so it becomes a + // valid table-ID sequence and gets validated/created below. + String fixedName = fixTableSequenceName(seq); + if (fixedName != null) { + seq.saveEx(); + if (sp != null) { + sp.addLog(0, null, null, "Sequence name fixed => " + fixedName); + } + } int old = seq.getCurrentNext(); int oldSys = seq.getCurrentNextSys(); // Created may be null for some sequences; guard against NPE so the @@ -266,6 +276,44 @@ private static void checkTableID (Properties ctx, SvrProcess sp, s_log.fine("#" + counterValue); } // checkTableID + /** + * Fix the name of a table-ID sequence stored with an invalid identifier (e.g. spaces). + * If the canonical form (whitespace collapsed to '_') matches a real table that has a + * <canonical>_ID column, the name was a typo: it is corrected on the PO (not saved + * here) and the new name is returned. Otherwise (name already valid, or no matching + * table) returns null and the caller leaves it for the skip+warning path. + * @param sequence sequence to inspect/fix + * @return the corrected name, or null if no correction applies + */ + private static String fixTableSequenceName(MSequence sequence) { + if (!sequence.isTableID()) { + return null; + } + String name = sequence.getName(); + if (name == null || name.matches("[A-Za-z_][A-Za-z0-9_]*")) { + return null; // null or already a valid identifier + } + String canonical = name.trim().replaceAll("\\s+", "_"); + if (!canonical.matches("[A-Za-z_][A-Za-z0-9_]*")) { + return null; // still invalid after normalization (other special chars) + } + // Match the table case-insensitively but fetch the REAL TableName so the stored name + // matches AD_Table exactly: validateTableIDValue() and the native sequence rely on the + // exact (case-sensitive) name. + String realTableName = DB.getSQLValueString( + sequence.get_TrxName(), + "SELECT t.TableName FROM AD_Table t" + + " INNER JOIN AD_Column c ON (t.AD_Table_ID=c.AD_Table_ID)" + + " WHERE UPPER(t.TableName)=UPPER(?) AND UPPER(c.ColumnName)=UPPER(?)", + canonical, canonical + "_ID" + ); + if (realTableName == null || realTableName.isEmpty()) { + return null; // not a real table -> leave for skip+warn + } + sequence.setName(realTableName); + return realTableName; + } + /** * Create Native sequence if not exists */ @@ -273,9 +321,21 @@ private static boolean createMissingNativeSequence(MSequence sequence, String tr if(!sequence.isTableID()) { return false; } + String name = sequence.getName(); + // A table-ID sequence name must be a valid SQL identifier (a real table name), because + // the native sequence is created as _SEQ. Names with spaces or special chars + // (typically a document sequence mis-flagged as IsTableID='Y') break "CREATE SEQUENCE" + // and would never be consumed, so skip them and surface the misconfiguration. + if(name == null || !name.matches("[A-Za-z_][A-Za-z0-9_]*")) { + s_log.warning( + "Native sequence skipped, invalid name for IsTableID='Y': '" + + name + "' (AD_Sequence_ID=" + sequence.getAD_Sequence_ID() + ")" + ); + return false; + } boolean SYSTEM_NATIVE_SEQUENCE = MSysConfig.getBooleanValue("SYSTEM_NATIVE_SEQUENCE",false); if(SYSTEM_NATIVE_SEQUENCE) { - CConnection.get().getDatabase().createSequence(sequence.getName()+"_SEQ", 1, 0 , 99999999, sequence.getNextID(), transactionName); + CConnection.get().getDatabase().createSequence(name+"_SEQ", 1, 0 , 99999999, sequence.getNextID(), transactionName); } return true; }