# 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 (?, ?, …)` 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 well over a thousand stored procedures and hundreds of 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 (eight files in the live tree): - `sSaveProName.sql` - `sDeleteProName.sql` - `sProcName.sql` - `sSaveProNameBefore.sql` - `sButtonParam.sql` - `sProTempleByDb.sql` - `sp_btn_calc.sql` - `sSqlStr.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](sql-templates.md) 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`).