Blog › ICP guides
Uniface developer on retainer: RETRIEVE without DISCARD lock hold, Compuware Uniface, Micro Focus Uniface on monthly retainer
October 8, 2026 · ~15 min read
A Uniface developer was maintaining an inventory allocation application built in Compuware Uniface 9 for a regional food-service distributor. The application managed allocation records that controlled how inbound shipments were distributed across warehouse zones. A “Reallocate Stock” component allowed warehouse supervisors to select an existing allocation record, enter a new zone assignment, and submit the change. The component’s detail-retrieve trigger called retrieve with modification intent to lock and buffer the selected allocation record for editing, read the current zone field value, applied a business-rule validation (the target zone must have sufficient available slot capacity), and on the passing branch called store to commit the zone update and release the database lock. The developer tested the reallocation procedure against allocation records targeting zones with plenty of available capacity. In production, three allocation records were targeting zones that were at or near full slot capacity; the business-rule check determined that the new zone assignment would exceed the available slots, displayed an error message via message, cleared the occurrence buffer with clear, and exited the trigger with escape to return to the form. Uniface’s implicit transaction model held the database-level row locks on all three retrieved records for the duration of the supervisor’s Uniface session: concurrent application instances running on other workstations received lock-wait timeout errors from the database backend when they attempted to open those same allocation records for routine quantity adjustments. Concurrent blocked instances: 3 → 0 after adding discard on the failure branch before escape.
The root cause was a missing discard call on the failure branch of the detail-retrieve trigger. In Uniface’s implicit transaction model, a retrieve operation with modification intent — meaning the Uniface component is in a form or trigger context where the retrieval buffers records for subsequent store — acquires database-level row locks through the Uniface TCC driver layer on the underlying database. The TCC (Transport Control Component) driver layer is Uniface’s database abstraction layer that translates Uniface’s retrieve and store operations into the appropriate SQL dialect and locking statements for Oracle, SQL Server, DB2, Informix, or PostgreSQL. For Oracle TCC, the row lock is acquired via SELECT ... FOR UPDATE; for SQL Server TCC, the lock is acquired via a UPDLOCK hint or via an explicit transaction with serializable isolation; for DB2 TCC, via SELECT ... FOR UPDATE WITH RS. The lock is held in the underlying database until Uniface executes a store (which commits the transaction and releases all locks) or a discard (which rolls back the transaction and releases all locks). Calling clear to empty the occurrence buffer and escape to exit the trigger does not issue a rollback or commit through the TCC driver layer — the database-level lock remains held until the Uniface session terminates or a discard is explicitly called. In the food-service distributor scenario, the three locked allocation records remained locked for the supervisor’s entire Uniface session: all other workstations that attempted to retrieve those records received lock-wait timeout errors from the database.
The Uniface implicit transaction boundary is coarser than most developers expect when coming from explicit-transaction database environments. In SQL-native environments (JDBC, ADO.NET, Delphi TQuery), the developer writes explicit BEGIN TRANSACTION / COMMIT / ROLLBACK statements and sees exactly where the transaction starts and ends. In Uniface, the transaction scope is determined by the component model: the transaction begins when the first modification-intent retrieve occurs within a component operation, and it ends only when store (commit) or discard (rollback) is called — or when the Uniface session ends. A Uniface developer who is not aware of this implicit transaction model may write failure-branch logic using clear (which clears the occurrence buffer in memory), rollback (which rolls back the field-level edit buffer within Uniface’s own change tracking, not the database transaction), and escape (which exits the current trigger or proc), believing that these Uniface-level operations release the database-level lock. They do not. The Uniface-level buffer and the database-level transaction are separate layers; only store or discard reaches through the TCC driver layer to commit or roll back the database transaction and release the underlying row locks.
Uniface’s trigger chain model adds another dimension to lock-hold analysis. A single user action in a Uniface form may fire multiple triggers in sequence: before-retrieve fires before the database retrieve operation; after-retrieve fires after retrieve completes and the occurrence buffer is populated; before-store fires before the store operation begins; after-store fires after store completes. Each trigger is a Uniface proc: a procedural script written in Uniface’s proc language that can call retrieve, store, discard, message, escape, and other Uniface operations. A lock acquired in an after-retrieve trigger proc is held through all subsequent trigger firings in the same operation until store or discard is called. If a before-store trigger applies a business-rule validation and calls escape on the failure branch — without calling discard — the lock acquired during the after-retrieve trigger remains held. The retainer developer must trace the full trigger chain across the component operation to identify all retrieve calls and all failure-branch exit points, confirming that every failure-branch escape path calls discard before exiting.
Uniface, the TCC driver layer, and the implicit transaction model
Compuware (later acquired by MicroFocus, now OpenText) developed Uniface in the late 1980s as a platform-independent 4GL application development environment. Uniface applications are built as components (forms, reports, global procs, service components) defined in a Uniface data dictionary that describes entity schemas (the entities, fields, and relationships that map to database tables), component layouts (the visual form design for GUI components), and trigger procs (the procedural logic attached to component events). Uniface compiles component definitions from the data dictionary into .app files (Uniface application container files that package component bytecode and resources for deployment). The Uniface runtime executes .app files against a configured TCC driver that connects to the target database.
The TCC driver layer is what makes Uniface database-independent: the same Uniface application code and component definitions can run against Oracle, SQL Server, DB2, Informix, or PostgreSQL by simply changing the TCC driver configuration (typically a Uniface configuration parameter in uniface.ini or the Uniface management console). The TCC driver translates all Uniface retrieve, store, and discard operations into the appropriate SQL statements for the target database, including the correct locking syntax (SELECT ... FOR UPDATE for Oracle, WITH (UPDLOCK) hints for SQL Server, FOR UPDATE WITH RS for DB2). A Uniface developer maintaining an application that runs against multiple backends must understand that the lock semantics of the implicit transaction are implemented by the TCC driver for each backend — and that the lock-hold behavior of a RETRIEVE-without-DISCARD bug is present on all backends, though the lock-wait timeout duration and the error messages returned to blocked sessions differ between Oracle, SQL Server, and DB2.
Uniface supports two primary deployment models. The classic Uniface GUI client runs the Uniface runtime as a Windows desktop application, with the Uniface component model rendering forms using Uniface’s proprietary widget set. The classic client connects to the database directly via the TCC driver from the workstation; each workstation runs its own Uniface runtime process and holds its own database connection and transaction context. In the classic client model, a lock-hold bug in one user’s session is visible to all other users on the same database: the database holds the row lock, and any other user’s retrieve-for-update on the same record blocks until the first user’s session calls discard, store, or disconnects. The Uniface Universal deployment model (introduced in Uniface 9.7 and expanded in Uniface 10) runs the Uniface runtime on an application server (the Uniface Universal container, a Java-based servlet container), with users accessing Uniface components through a web browser. In the Universal model, sessions are managed by the application server; a lock-hold bug in one web session holds the database lock for the duration of that HTTP session (which may have a longer timeout than a classic client session, depending on the application server session timeout configuration). The RETRIEVE-without-DISCARD lock-hold pattern is equally present in both deployment models; the Universal model may make lock-hold bugs harder to diagnose because the “locked session” may be a web session with no visible desktop user.
Uniface’s proc language — the scripting language used in component triggers and global procs — provides the following key operations for lock management: retrieve (retrieves records from the database into the Uniface occurrence buffer, with or without modification intent depending on context and component settings); store (commits all buffered changes to the database via the TCC driver, releasing all locks acquired since the last store or discard); discard (rolls back all buffered changes and releases all database locks acquired since the last store or discard, without writing any changes to the database); clear (clears the Uniface occurrence buffer in memory without affecting the database transaction or releasing database locks); rollback (in some Uniface versions, rolls back field-level edits in the Uniface buffer without affecting the database transaction); and escape (exits the current trigger or proc, returning control to the caller). The critical rule is: discard is the only proc operation that releases database locks acquired by retrieve. clear, rollback, and escape do not release database locks.
Typical Uniface retainer work and what it looks like in a work log
RETRIEVE-without-DISCARD lock-hold on business-rule failure branches is the canonical Uniface invisible production lock. The pattern is consistent: a Uniface component trigger or global proc calls retrieve with modification intent to lock and buffer a record for a validation-then-update operation. The developer tests with records that always pass the validation; on the passing branch, store is called, which commits the change and releases the lock. In production, a subset of records hits the failure branch (the zone capacity is exceeded, the referenced entity does not exist, the new value violates a business constraint). The trigger uses message to display an error, clear to empty the occurrence buffer, and escape to return to the calling form or trigger — without calling discard. The database-level row lock acquired by retrieve remains held for the duration of the Uniface session. Three allocation records locked; concurrent instances blocked with lock-wait timeouts. Fix: added discard on every failure branch before escape. Blocked instances: 3 → 0. Work log: “InventoryAllocation detail-retrieve trigger: retrieve with modification intent acquired DB row lock; zone-capacity validation failed; message + clear + escape without discard; 3 allocation records locked for session; concurrent instances: 3 lock-wait timeouts → 0; fix: discard added before escape on failure branch; 1.5h.”
Implicit transaction scope creep across nested trigger chains is the second most common Uniface retainer pattern. In complex Uniface components with deeply nested trigger chains — a before-store trigger that calls a global proc, which calls another global proc, which applies a cross-entity validation using retrieve — the transaction scope spans the entire chain. A retrieve call deep in a nested global proc acquires a database lock that remains held across all subsequent triggers in the chain until store or discard is called at the top level. A developer who fixes a before-store trigger to call discard on its own failure path may inadvertently leave a lock held from a retrieve inside a called global proc if the global proc also has a failure-branch escape path that does not call discard. The retainer developer must trace the full trigger call graph across all global procs to audit every retrieve-failure-branch-escape pattern, not just the top-level trigger. Work log: “OrderProcessing before-store trigger chain: top-level trigger calls ValidateCrossEntity global proc (via call), which calls CheckInventoryAvailability global proc (via call); CheckInventoryAvailability calls retrieve for inventory entity; inventory-availability check fails; escape without discard in CheckInventoryAvailability; inventory entity row lock held for session; fix: discard added in CheckInventoryAvailability before escape on failure branch; 2h.”
TCC driver version mismatch after Uniface runtime upgrade is the third common Uniface retainer pattern. When the Uniface runtime is upgraded to a new minor version (e.g., Uniface 9.6 to 9.7 or Uniface 10.3 to 10.4), the TCC driver DLLs or shared libraries that interface with the database backend may also need to be updated to the version compatible with the new Uniface runtime API. If the Uniface runtime is upgraded but the TCC driver is not updated to the compatible version, symptoms include: retrieve operations that return inconsistent result counts compared to direct database queries; store operations that fail with undocumented error codes from the TCC driver layer; or lock semantics that change subtly (e.g., a TCC driver version that previously used optimistic locking for a certain retrieve pattern now uses pessimistic locking, or vice versa). The fix is to identify the correct TCC driver version for the upgraded Uniface runtime from the Uniface compatibility matrix and update the TCC driver files on all application servers or workstations. Work log: “Uniface runtime upgraded 9.6.G03 → 9.7.B01; Oracle TCC driver remained at 9.6.G03 compatibility version; retrieve on INVENTORY entity returned 0 rows for known-existing records; Oracle TCC driver updated to 9.7.B01 compatible version; retrieve now returns correct results; affected components: 3; 2h.”
Track Uniface developer retainer hours without the status emails
When a 1.5-hour investigation traces 3 concurrent lock-wait timeouts to a missing discard before the escape call on the zone-capacity validation failure branch — Uniface’s implicit transaction model acquires database row locks through the TCC driver layer that persist until store or discard is explicitly called — the work log must name the component, the trigger, the retrieve call, the failure branch, the lock-hold duration, and the concurrent blocked-instance count before and after. HourTab gives your Uniface retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log naming the trigger proc and the discard fix. No client login. No status emails. CSV in, URL out.
How HourTab tracks Uniface developer retainer hours
Uniface RETRIEVE-without-DISCARD lock-hold bugs are invisible by the same mechanism that makes them hard to diagnose: the retrieve call returns successfully with no error or warning when it acquires the database lock; the business-rule validation that follows it executes correctly (the validation logic identifies the capacity violation correctly); the message alert displays correctly; the clear empties the Uniface occurrence buffer in memory; the escape exits the trigger cleanly. The Uniface component shows no error. The database shows no error (the lock was legitimately granted to the session). The application continues operating normally for the supervisor’s session. The only evidence of the problem is on a different workstation: a warehouse operator retrieves one of the three locked allocation records and receives a lock-wait timeout from the database backend. The supervisor’s workstation has no visible indication that it holds any database locks. Connecting the lock-wait timeout on workstation B to the retrieve-without-discard failure branch on workstation A requires either a structured work log that captures every retrieve-to-store-or-discard sequence, or reading through the relevant component triggers and global procs to find every failure-branch escape path that does not call discard before exiting.
The work log must name the mechanism to be auditable: which Uniface component (InventoryAllocation), the trigger that contained the retrieve (detail-retrieve), the retrieve call with modification intent, the business-rule check that triggered the failure branch (zone slot capacity exceeded), the escape path that bypassed discard (message + clear + escape without discard), the number of records locked (3 allocation records), the concurrent lock-wait timeouts before and after fix (3 → 0), the fix (discard added before escape on the failure branch and verified in all nested global proc failure paths), and the verification (tested zone-capacity rejection in a multi-session test environment with two Uniface instances; second instance no longer receives lock-wait timeout after first instance hits the failure branch). A log entry that says “fixed lock issue in allocation component, 1.5h” is not auditable. A log entry that names InventoryAllocation detail-retrieve, the modification-intent retrieve, the zone-capacity failure branch, the 3 locked records, and the discard-before-escape fix is auditable and defensible to the food-service distributor client. HourTab gives Uniface developers a public retainer-hours URL they send to clients — food-service distributors, utilities, logistics companies, government agencies, and manufacturing firms that built Compuware Uniface 7/9/10 applications in the 1990s and 2000s for inventory management, order processing, and ERP integration, maintained today by the original Uniface developer or a successor retainer consultant.
Comparative context: Uniface RETRIEVE-without-DISCARD lock-hold bugs are structurally identical to lock-hold patterns in other 4GL application platforms. Genero BDL retainers cover the FOR EACH / UPDATE pattern in Genero Business Development Language where a batch transaction that omits COMMIT WORK holds Informix share locks on all fetched rows until the session ends. Gupta SQLWindows retainers cover the SqlPrepare with FOR UPDATE cursor pattern in Centura SQLWindows where SqlEndFetch is not called on the failure branch, holding SQL Server or Oracle row locks for the session. Progress OpenEdge retainers cover the NO-LOCK / EXCLUSIVE-LOCK table record buffer pattern in ABL where a missing RELEASE or UNLOCK call on a failure branch holds OpenEdge database record locks across transaction boundaries.
FAQ: Uniface developer retainers
What does a Uniface developer on retainer typically do?
A Uniface developer on monthly retainer covers RETRIEVE-without-DISCARD lock-hold audits for all component triggers and global procs (reviewing every retrieve call to confirm that every failure-branch escape path calls discard before exiting); implicit transaction scope analysis for multi-entity forms with nested trigger chains; Uniface TCC driver configuration and version alignment for Oracle, SQL Server, DB2, Informix, and PostgreSQL backends; Uniface component entity schema changes and recompilation of dependent .app files; Uniface Universal web runtime deployment issues (session management, component rendering in the browser runtime container, HTTP connector configuration); and Uniface 9-to-10 migration path evaluation for applications still on legacy Compuware Uniface 9 releases.
What Uniface RETRIEVE lock work is most commonly underlogged?
Validation procs that call retrieve with modification intent, apply a business-rule check, and exit via message + clear + escape on the failure branch without calling discard are the most systematically underlogged Uniface retainer work. Uniface’s implicit transaction model holds the database row lock for all retrieved records until store or discard is called. clear only empties the Uniface occurrence buffer; rollback only rolls back Uniface-level field edits; escape only exits the trigger. None of these release the database lock. The developer must know the rule independently: every retrieve with modification intent that does not lead to store must be followed by discard on every failure-branch exit path.
What are typical Uniface developer retainer rates?
Entry-level Uniface developers with experience in basic Uniface component model (entities, fields, occurrences), Uniface proc language, and standard retrieve/store operations typically bill at $65 to $120 per hour. Mid-level Uniface programmers with experience in global procs, service components, Uniface Universal web deployment, and multi-backend TCC driver configuration typically bill at $100 to $175 per hour. Senior Uniface developers with deep knowledge of Uniface implicit transaction semantics, data dictionary schema management, Uniface 9/10 upgrade path expertise, and production lock-hold forensics typically bill at $145 to $265 per hour. Monthly retainer ranges: $1,800 to $3,200 per month for advisory engagements covering RETRIEVE lock audits and entity schema changes (14 to 22 hours per month); $2,500 to $5,000 per month for active maintenance including component trigger debugging, Universal deployment issues, and version upgrade work.
What should a Uniface developer retainer agreement include?
A Uniface developer retainer agreement should specify: Uniface version (9, 10, or Universal); the backend database and TCC driver version (Oracle, SQL Server, DB2, Informix, PostgreSQL); whether the Uniface data dictionary manages entity schemas or they are managed externally; whether Uniface service components (SOA/web service integration) and the HTTP connector are in scope; whether Uniface Universal web runtime deployment is in scope; and whether the retainer covers new component development or only bug fixes in existing components. It should also specify the .app file deployment model: whether the retainer developer has access to the Uniface IDE for recompilation and deployment, or delivers component patches for the client’s own deployment team to apply.
How should Uniface developer retainer hours be logged?
Log each Uniface retainer session with the component name, the trigger or global proc, and the lock state outcome. For RETRIEVE-without-DISCARD lock-hold bugs: component name, trigger (detail-retrieve, before-store, or called global proc), the retrieve call with modification intent, the failure-branch check, the clear/escape path that bypassed discard, records locked, concurrent blocked instances before and after fix (3 → 0), fix (discard added before escape on failure branch), and hours. For trigger-chain lock scope issues: full trigger call graph traced, all global procs audited, every failure-branch escape path verified for discard, hours. For TCC driver version mismatch: Uniface runtime version, TCC driver version before and after update, symptom, fix, affected components, hours.