proc-dispatch.md 3.58 KB

Generic procedure dispatch

When gdsmodule.sSaveProName (or sDeleteProName, sCalcProName, sProcName, sSaveProNameBefore) is non-empty, the framework invokes the named stored procedure instead of falling through to its default Add/Update path. The same machinery handles button-press calculations and on-demand custom logic.

The handler is GenericProcedureCallController in xlyEntry/com/xly/web/businessweb/.

Endpoint shape

The frontend POSTs to a URL under /business/genericProcedureCall* with:

  • The proc name (often resolved from gdsmodule or gdsconfigformslave.sButtonParam).
  • The parameter values (frontend supplies them, the framework injects tenant identity).

The handler:

  1. Loads the proc's signature (parameter names and types) from information_schema.PARAMETERS.
  2. Composes CALL <proc>(?, ?, …) with parameters bound positionally.
  3. Augments the call with sBrandsId, sSubsidiaryId, sLoginId, sLanguage from RequestAddParamUtil.
  4. Executes via MyBatis.
  5. Returns either the proc's result-set (for SELECTs inside the proc) or its OUT parameters (for save/calculate procs).

Why dynamic proc dispatch matters

With 1,687 stored procedures and ~177 functions in the schema, hard- wiring each one in Java would be untenable. Dispatch by name lets the framework call any proc the metadata names without a code change. The framework treats the proc as a black box: name in, parameters in, result out.

The downside: the runtime cannot statically know which procs exist or what their effects are. A typo in gdsmodule.sSaveProName produces a runtime "proc not found" error, not a compile error.

The conventions procs follow

xly's procs share a calling convention to make generic dispatch possible:

  • Common parameters in a fixed order: typically (IN sLoginId, IN sCustomerId, IN sBrId, IN sSuId, …). Cross-cutting identity always comes first.
  • Tenant-aware: every proc accepts sBrId (= sBrandsId) and sSuId (= sSubsidiaryId) and uses them in every internal predicate to stay tenant-safe.
  • Output via OUT params: save / calc procs return success / error through OUT sCode INT, OUT sReturn LONGTEXT — see xlyEntry/src/main/resources/templates/templesql/sSaveProName.sql for the canonical scaffold.
  • Dynamic SQL inside: many procs build SQL strings with CONCAT
    • PREPARE + EXECUTE. This is xly's escape hatch for runtime shape variability and the reason jSqlParser is in the dependency tree (the framework occasionally inspects these strings).

The companion file: templesql/

xlyEntry/src/main/resources/templates/templesql/ holds scaffolds for the standard proc kinds:

  • sSaveProName.sql
  • sDeleteProName.sql
  • sProcName.sql
  • sSaveProNameBefore.sql
  • sButtonParam.sql
  • sProTempleByDb.sql
  • sp_btn_calc.sql

These are templates an engineer fills in to author a new proc — they are not used by the runtime to generate procs on the fly. See SQL templates for the loader and the placeholder syntax.

Failure modes to watch

  1. Mismatched parameter order. Generic dispatch binds positionally; a proc whose IN parameters are reordered is a runtime explosion.
  2. Missing tenant filter. A proc that forgets sBrandsId / sSubsidiaryId in any internal predicate is a multi-tenant leak. Procs are NOT auto-filtered by the framework — they have to do it themselves.
  3. Long-running procs blocking the request thread. Long calculations should run on a separate worker (see xlyErpTask, though it's currently disabled in settings.gradle).