We stopped writing release-specific blog posts after v6.1 - the automated, public release system made them redundant for routine version bumps. But every so often a release lands that changes what the product can actually do, and v7.13 is one of them. Together with 7.12 it completes a piece of the roadmap we have been chipping away at for a while: frontend formulae and regex rules, both configurable purely in MPE_VALIDATIONS.
If you configure data entry in Data Controller, this post is a practical guide: how the rules work, how to set them up, and the process flow from config to grid. All the screenshots below are captured from a live editor session against the demo tables, so what you see is what shipped.
Data Controller's job is to let business users change data safely. A big part of "safely" is catching problems at the point of entry - rather than after an approval, in a batch log, or (worst case) in a report. The validations framework already covered length, type, nullability, primary keys, ranges, casing and dropdowns. Two things were missing:
7.12 delivered regex rules (HARDREGEX / SOFTREGEX). 7.13 delivers formulas (HARDFORMULA / SOFTFORMULA). Both are rows in a config table. That is the whole feature.
Every configurable rule in Data Controller follows the same pipeline, and the new rules plug straight into it:
MPE_VALIDATIONS is itself a Data Controller table - open it from the navigation tree like any other, and add a row with BASE_LIB, BASE_DS and BASE_COL pointing at the column you want to govern, RULE_TYPE set to the new rule, RULE_VALUE holding the formula or pattern, and RULE_ACTIVE=1. Submit, approve, done - it's a config change, not a code release. The MPE_VALIDATIONS table guide has the full column reference.editors/getdata service extracts the active rules for that table - it filters MPE_VALIDATIONS on library, table and RULE_ACTIVE=1 - and returns them in the dqrules object of the response, alongside the table data, schema, and the schema-derived NOTNULL constraints.HARD rules block the submission if violated, SOFT rules warn but allow.Here are the new rule types as they appear in the config - one row per rule, RULE_VALUE holding the formula or the pattern:
For regex there is also a config-time guard, and it's a neat piece of dogfooding: because the rule itself lives in a table, saving an edit to MPE_VALIDATIONS through Data Controller runs the post-edit hook, which passes every HARDREGEX / SOFTREGEX RULE_VALUE through PRXPARSE and rejects the edit if the pattern is invalid - listing the offending columns. A typo in a pattern is caught the moment you save the rule, not the first time a user hits it.
Regex rules validate cell values against a SAS (Perl-style) regular expression. You provide the pattern in RULE_VALUE; whether it blocks or warns depends on the rule type:
Say SOME_CHAR in the demo table must contain either "the" or "data" (case-insensitive). That's one insert:
insert into &lib..MPE_VALIDATIONS set
tx_from=0
,base_lib="&lib"
,base_ds="MPE_X_TEST"
,base_col="SOME_CHAR"
,rule_type='HARDREGEX'
,rule_value='/the|data/i'
,rule_active=1
,tx_to='31DEC5999:23:59:59'dt;
That's a real row from the demo data, by the way - the shipped MPE_X_TEST table carries sample HARDREGEX and SOFTREGEX rules so you can try both without touching your own config.
In the editor it looks like this - an invalid email blocked red by HARDREGEX, an invalid postcode warned yellow by SOFTREGEX, and a value that passes:
Note the third column in that screenshot, REGEX_BOTH_COL. It carries both a HARDREGEX and a SOFTREGEX rule - and shows neither warning. That's the precedence rule in action: only one regex is ever applied per column, and if both exist the SOFTREGEX is ignored entirely, so the column behaves exactly as if it were HARDREGEX-only.
The full list of gotchas is in the regex rules documentation; the ones that matter most in practice:
/pattern/flags, e.g. /^\d+$/ for "integers only", /the|data/i for a case-insensitive contains. The config-time PRXPARSE check enforces this - a bare pattern without delimiters will be rejected when you save the rule. (The frontend tolerates bare patterns for backwards compatibility, but don't author new rules that way.)/the|data/i matches "the" anywhere in the value. If you want the whole value to match, include ^ and $ yourself.^/$ anchors, \d \w \s and friends). Three unambiguous Perl-isms are translated automatically - a leading (?i) modifier, \Q...\E literal sequences, and \A/\z absolute anchors. But Perl-only constructs such as possessive quantifiers (a++) and atomic groups ((?>...)) pass the SAS-side check and then silently do nothing in the frontend - so just don't use them.NOTNULL rule if you also need populated values). On numeric columns the plain SAS missing (.) is also exempt - but special missings (.A-.Z, ._) are not: they're deliberately-set values, so your pattern needs to accommodate them.HARDREGEX wins when both are present, and the column-header info dropdown shows only the rule that is actually applied.RULE_VALUE is 128 characters, which constrains very long patterns.For currency codes, country codes, account number formats, email shapes, or "must be an integer" - a regex is one row of config versus a 1000-value HARDSELECT dropdown. And unlike a dropdown, a regex catches paste operations too, not just typed values.
Formula rules make a column compute itself from other columns in the same row - like a spreadsheet formula, but the formula lives in config and applies to every row. When a user opens the editor, the formula is evaluated live and the result is shown in each cell. Change an input, and every dependent cell recalculates on the spot.
Formulas use column names, not cell references - there is no need to know the grid layout. If you have A_COL and B_COL, a FORMULA_HARD_COL rule is simply:
=A_COL * B_COL
Each row calculates its own result: row 1's values, row 2's values, and so on. Under the hood each column name is translated to a row-relative cell reference (text inside quotes is left untouched) and handed to HyperFormula - so you get the full spreadsheet function library, IF, SUM, ROUND, CONCAT and friends, without writing any code.
The one syntax rule: each column name must be surrounded by spaces. =MATCH( PRICE ) resolves the column reference; =MATCH(PRICE) does not - without the surrounding blanks the token isn't recognised as a column reference, and the formula errors rather than using the column's value. The spaces stop column names clashing with function names.
PROCESSED_BY column.Formulas can reference three runtime values, resolved when the formula is evaluated:
DC.ROW_STATUS - the row's current state: M (Modified), A (Added), D (Deleted), or U (Unchanged). A newly-added row is A from the moment it is created - there is no transient state before that.DC.USER_NAME - the logged-in user id.DC.ORIG_VALUE - the original cell value before the current edit.Our demo formula table puts them all to work. There's a row-status column (=DC.ROW_STATUS), a user column (=DC.USER_NAME), and a CHANGE_SUMMARY_COL whose rule reads like a proper audit sentence:
=IF( DC.ROW_STATUS ="U","unedited",
DC.USER_NAME &" changed from "& DC.ORIG_VALUE )
Here it is live in the editor. We edited B_COL on the second row from 10 to 25 - and the whole row reacted: FORMULA_HARD_COL recomputed to 50 (A_COL * B_COL), FORMULA_SOFT_COL recomputed to 27 (A_COL + B_COL), and the row status flipped from U to M - all instantly, all without touching a single line of SAS:
And here's the payoff of those DC.* references - the audit columns from the same table, same edit. ROW_STATUS_COL flipped to M, and the change summary resolved the IF formula above into a sentence - "sasdemo changed from orig-2":
Because DC.ROW_STATUS is a live reference, it updates as the user works: edit a cell and the stamp flips to your user id; cancel the edit and it reverts.
Two moving parts, and the boundary between them explains most of the gotchas above.
Serving the rules. getdata extracts the active MPE_VALIDATIONS rows for the target table and returns them in dqrules, along with the schema-derived NOTNULL constraints. Formulas and regex are frontend rules - they are evaluated in the browser, not in SAS - which is why they arrive as dqrules rather than as backend hook scripts.
Evaluating them. For formulas, the client translates each column name in RULE_VALUE to a row-relative cell reference and hands it to HyperFormula (wired into Handsontable's formulas plugin). For regex, the client parses the PRX /pattern/flags form, translates the three Perl-isms it can, and constructs a JavaScript RegExp. If a pattern still fails in the browser, the editor treats it as always-valid rather than breaking - a failed pattern never blocks a submission it shouldn't.
Guarding the config. Because MPE_VALIDATIONS is itself a Data Controller table, a post-edit hook validates new rules: PRXPARSE checks every regex RULE_VALUE, and the edit is rejected with the offending columns listed. Invalid rules never reach users.
Beyond the headline features, the usual spread of hardening and polishing shipped along the way: row-header status cells are now colour-coded (with a ± symbol for modified rows), CAS support landed for the REPLACE load type, Viya deploy diagnostics were improved, and a large tranche of dependency upgrades (Angular 20, Handsontable 18 pinned, sasjs core v5) keeps the audit trail clean. As ever, the full commit-by-commit detail is in the release notes.
The frontend changes are included in the 7.13 release assets. The backend additions are data-only, optional migrations:
HARDREGEX / SOFTREGEX to the RULE_TYPE dropdown in MPE_VALIDATIONS (and switches the MPE_SECURITY.LIBREF validation to a hook that lists all libraries).HARDFORMULA / SOFTFORMULA to the same dropdown.Both scripts are in sas/sasjs/db/migrations/ in the source repo, and they're worth running even if you don't plan to use the rules immediately - they only add dropdown values to MPE_SELECTBOX.
The shipped demo data includes the regex rules on MPE_X_TEST, so you can see them working without configuring anything: open the demo library, edit MPE_X_TEST, and try entering a SOME_SHORTNUM between 1 and 5 (blocked red - HARDREGEX), a PRIMARY_KEY_FIELD with a decimal point (warned yellow - SOFTREGEX), or a SOME_CHAR without "the" or "data" in it (blocked - HARDREGEX).
For formulas, add a rule to one of your own tables - the REVENUE = PRICE * VOLUME example above is a two-minute configuration, and the DC.* special values make audit-style columns almost free. Full reference in the validations docs.
As ever - if you'd like to see additional validation types, get in touch. The roadmap is customer-driven, and the validations list keeps growing.
Data Controller is the product of a UK company with a singular focus on SAS Web Apps.
Data Controller source is on our self-hosted Gitea Repository; the underlying SASjs framework is on GitHub.
