Technical Capabilities
Having trouble finding what you need?
Get in touch with us, so we can answer your specific questions directly.
Get In Touch

VX/SOR – OpenVMS SORT MERGE

Overview

VX/SOR is a complete reimplementation of the OpenVMS SORT/MERGE utility on Linux. It ships two cooperating parts: the callable SOR$ Sort/Merge run-time library that OpenVMS BASIC, COBOL, FORTRAN and C programs call directly, and the SORT and MERGE DCL verbs that parse the VMS command line and drive that same library. Existing calls to SOR$ routines and existing DCL sort scripts move across without changing sort logic.

Under the hood VX/SOR is a classic external merge sort: records accumulate into an in-memory block, each full block is sorted and written to a SORTWORK swap file, then the swap files are combined with a fast buffered k-way merge. The /KEY, /SPECIFICATION, STABLE and NODUPLICATES behaviour matches VMS, and the qualifiers the engine acts on produce identical results to OpenVMS.

What VX/SOR is

VX/SOR is two cooperating things inside the VX/RT runtime. Together they replace the OpenVMS SORT/MERGE utility for migrated applications on Linux, at both the API and the command-line level.

ComponentRoleStatus
SOR$ callable libraryThe Sort/Merge run-time library compiled into libvxrt.a. One public entry point per module. This is the engine every other part sits on, and the API that OpenVMS BASIC, COBOL, FORTRAN and C programs call directly.live
SORT / MERGE DCL verbsThin command front-ends that parse the VMS SORT/MERGE command line and drive the SOR$ API. They are callers of the library, not a separate engine.live

The sort engine

The engine is a classic external merge sort. Records are accumulated into an in-memory logical block; each full block is sorted and written to a SORTWORK swap file; the swap files are then combined with a fast buffered k-way heap merge (a legacy N-way merge remains as a fallback). This is the same shape as the OpenVMS Sort/Merge engine, so result ordering, duplicate handling and stability all match.

SORT or MERGEVX/DCL command front end
Direct SOR$ callsMigrated application API
Both inputs converge
SOR$ library in libvxrt.aAccumulates records into an in-memory logical block
When a logical block is full
Sort the blockWrite the sorted run to work storage
SORTWORK0..9Side output: swap files for sorted runs
After all blocks are written
k-way heap mergeCombines every sorted run in order
Produces
Output fileFinal ordered records with duplicate and stability rules applied

Because both the DCL verbs and direct SOR$ callers reach the same library, a migrated estate can keep its DCL sort scripts and its compiled programs that call the sort API, with no rewrite of either.

The SOR$ callable API

The callable interface is ten public entry points. Each routine is defined once as the real C++ function and wired to its VMS-style aliases, so existing object references resolve and existing source compiles unchanged. Applications that call these routines migrate to Linux without changing their sort logic.

Routine table

RoutinePurposeStatus
SOR$INIT_SORTPrepare a sort, treating key offsets as 1-based, then forward to BEGIN_SORT. This is the only functional difference from BEGIN_SORT.live
SOR$BEGIN_SORTInitialise the context: build the key table, validate options, allocate the logical-block, duplicate and merge buffers, and generate SORTWORK names.live
SOR$PASS_FILESRegister one input file (wildcards expanded through the VMS parse/search path) and optionally the output file. Up to 10 input files per sort.live
SOR$RELEASE_RECPush one record into the sort. RECORD process type only.live
SOR$SORT_MERGERun the back end: file-driven read, sort the trailing block, k-way merge, and optional direct write to the output file.live
SOR$RETURN_RECReturn the next sorted record; end-of-file status is returned once the stream is drained.live
SOR$END_SORTRelease the context, unlink the SORTWORK files, and free the specification tree.live
SOR$SPEC_FILEParse a VMS Sort specification file or string (FIELD, KEY, CONDITION, INCLUDE, OMIT, DATA, COLLATING_SEQUENCE) into a specification node tree.live
SOR$STATReturn one sort statistic selected by a code. A subset of codes is wired; unrecognised codes return zero with a success status.partial: subset of codes
SOR$DTYPERegister a user compare routine for one of ten user-defined key data types.live: see alias note

Name forms and aliasing

Every routine is exposed under several names, so callers from any supported language link against it:

SOR_D_xxx (C++ symbol)
SOR$xxx (upper-case VMS)
sor$xxx (lower-case VMS)
F4_SOR_D_xxx (FORTRAN thunk)

SOR_D_xxx is the canonical internal C++ symbol. SOR$xxx and sor$xxx are the VMS-style upper and lower-case aliases. F4_SOR_D_xxx is a FORTRAN thunk that unpacks trailing string-length arguments, provided for the descriptor-taking routines (PASS_FILES, RELEASE_REC, RETURN_REC and SPEC_FILE).

Alias note for SOR$DTYPE. The user-compare registration routine is currently exported under the upper-case alias SYR$DTYPE rather than SOR$DTYPE. Until this is corrected, call SYR$DTYPE or the lower-case sor$dtype; the routine itself works normally.

Record-interface example

The RECORD interface releases records one at a time and pulls sorted records back until end-of-file. This example sorts on a 10-byte text key at 1-based position 1, the account number in a banking record:

#include "sor$routines.h"
#include "ssdef.h"

U32   ctx = 0;                 /* 0 => allocate a context slot        */
U16   lrl = 80;                /* longest record                      */
U32   opt = SOR$M_STABLE;      /* stable sort                         */
U16   keybuf[1 + 4] = {
    1,                         /* one key descriptor                  */
    DSC$K_DTYPE_T,             /* key_type   = text                   */
    0,                         /* key_order  = ascending              */
    1,                         /* key_offset = 1-based (INIT_SORT)    */
    10                         /* key_length = 10-byte account number */
};

/* 1. initialise (INIT_SORT: offsets are 1-based) */
U32 st = SOR$INIT_SORT(keybuf, &lrl, &opt, 0, 0, 0, 0, 0, &ctx);
if (!(st & 1)) return st;

/* 2. release records (RECORD process only) */
struct dsc$descriptor rec_d;
/* ... fill rec_d for each ACCOUNTS record ... */
st = SOR$RELEASE_REC(&rec_d, &ctx);       /* repeat per input record */

/* 3. run the sort/merge */
st = SOR$SORT_MERGE(&ctx);
if (!(st & 1)) { SOR$END_SORT(&ctx); return st; }

/* 4. pull sorted records back until end-of-file */
char  out[80];
U16   olen;
struct dsc$descriptor out_d;   /* points at out[] */
while ((st = SOR$RETURN_REC(&out_d, &olen, &ctx)) == SS$_NORMAL) {
    /* ... consume out[0..olen) ... */
}
/* st == SS$_ENDOFFILE here */

/* 5. always tear down */
SOR$END_SORT(&ctx);

The file-driven alternative is what the DCL verbs use: call SOR$PASS_FILES once per input file, then SOR$BEGIN_SORT, then SOR$SORT_MERGE (which reads the inputs and writes the output file directly), then SOR$END_SORT, with no release or return loop.

Pluggable sort engine

Each entry point first checks for a replacement engine. On the first call it loads a customer-supplied shared library named by the SORLIB logical or environment variable and looks up the matching lower-case sor$xxx symbol. If it is present, the call is forwarded to that engine and the in-tree engine is bypassed, so a site can drop in an alternative sort implementation without relinking the runtime.

Key and record definitions

A developer driving the callable API needs to understand three things: how keys are described, what options control the sort, and the limits the context enforces. All three match the OpenVMS Sort/Merge data model.

Key buffer

Keys are described by a compact unsigned 16-bit array. The first element is the number of key descriptors; each descriptor is then four elements: data type, order, offset and length.

key_buffer[0]           = N, the number of key descriptors
key_buffer[1 + 4*i + 0] = key_type    (a DSC$K_DTYPE_* code)
key_buffer[1 + 4*i + 1] = key_order   (0 = ascending, 1 = descending)
key_buffer[1 + 4*i + 2] = key_offset  (0-based for BEGIN_SORT, 1-based for INIT_SORT)
key_buffer[1 + 4*i + 3] = key_length

SOR$INIT_SORT treats offsets as 1-based and decrements each one before forwarding to BEGIN_SORT, so an offset of 0 there is a fatal bad-key error. If no key is supplied at all, BEGIN_SORT synthesises a single ascending text key spanning the whole record. A context can hold up to 50 keys.

Per-record header

Inside the work buffer each record is prefixed with a packed header. The sequence number is what makes STABLE sorts stable: records that compare equal on all keys break the tie on insertion order.

typedef struct {
  U32   recnum;                 // duplicate flag = recnum |= 0x80000000
  U32   seqnum;                 // preserves insertion order for STABLE
  U16   ctx_num;
  U16   reclen;
  off_t offset_into_swap_file;
  char  data[0];
} sort_header_t;

Context limits

ConstantValueMeaning
MAX_CONTEXT64Concurrent sort contexts
MAX_INPUT_FILES10Input files per sort
MAX_SORTWORK11SORTWORK swap-file slots
MAX_COND100Conditions per specification node
keys per context50Maximum key descriptors
default page / swap I/O4 MiBIn-memory block and swap-file I/O unit

A context is referenced by a 32-bit handle. Passing a context value of 0 on the first call allocates a free slot from the 64 available.

Options and process types

The options bitmask controls sort behaviour. Only three bits are legal; any other bit is rejected. The default when no options are supplied is STABLE.

OptionMeaning
STABLEPreserve insertion order for records with equal keys (default).
NOSIGNALAccepted for compatibility; the engine returns conditions as values and never signals.
NODUPSDrop records that are equal on all keys.

Setting STABLE and NODUPS together is rejected as an exclusive-option error. The process type can be RECORD (the default), TAG, INDEX or ADDRESS: all four are accepted by BEGIN_SORT, but SOR$RELEASE_REC handles RECORD only, so TAG, INDEX and ADDRESS are reached through the file interface (SOR$PASS_FILES).

Statistics

SOR$STAT returns one statistic selected by a code. The counters below are wired; the remaining OpenVMS codes (nodes, initial runs, merge order, work allocation, merge buffer count) return zero with a success status, so a missing statistic is a zero rather than an error.

StatisticReturned value
SOR$K_IDENTImplementation identifier
SOR$K_REC_INPRecords released to the sort
SOR$K_REC_SORRecords actually sorted
SOR$K_REC_OUTRecords returned
SOR$K_LRL_INPLargest input record length
SOR$K_LRL_OUTLargest output record length
SOR$K_MRG_PASSESNumber of merge passes

SORT and MERGE from the command line

VX/SOR defines SORT and MERGE as VX/DCL verbs that replicate the VMS command syntax. They parse the qualifiers, expand VMS wildcards on the input files, map the output specification, and then drive the same SOR$ library described above. MERGE is identical to SORT except that it opens the output to append or create rather than supersede, and it expects inputs that are already sorted on the same keys.

Command syntax

$ SORT/KEY=(POSITION:1,SIZE:10) [/DESCENDING] [/STABLE] [/NODUPLICATES]
       [/STATISTICS] [/SPECIFICATION=RULES.SPC] ACCOUNTS.DAT SORTED_ACCOUNTS.DAT

$ MERGE/KEY=(POSITION:1,SIZE:10) BRANCH_EAST.DAT,BRANCH_WEST.DAT ACCOUNTS.DAT

Key sub-keywords

The /KEY clause is where most of the sort is defined. The keywords below change the key descriptor and behave exactly as on OpenVMS:

KeywordEffect
POSITION1-based key start position; a value below 1 is a key-position error.
SIZEKey length in bytes.
NUMBERKey ordinal.
ASCENDING / DESCENDINGSets the sort direction.
CHARACTERText key (the default data type).
D_FLOATING, F_FLOATING, G_FLOATING, H_FLOATINGThe VAX floating-point key data types.
PACKED_DECIMALPacked decimal key data type.

For source compatibility, VX/SOR also parses and consumes a further set of /KEY sub-keywords that have no effect on the sort, so existing command lines carry across without edits. These accepted-for-compatibility keywords are:

BINARY
DECIMAL
ZONED
SIGNED
UNSIGNED
LEADING_SIGN
TRAILING_SIGN
OVERPUNCHED_SIGN
SEPARATE_SIGN
ONYOMI
KUNYOMI
BUSHU
SOKAKU
KOKUGO
KANA8BIT
JISCODE

Command-level qualifiers

QualifierBehaviourStatus
/KEY=(...)Builds the key table and drives BEGIN_SORT.acted on
/STABLEPreserves insertion order for equal keys.acted on
/[NO]DUPLICATESThe negated form drops records equal on all keys.acted on
/STATISTICSPrints records read, sorted and output plus elapsed and CPU time.acted on
/SEQUENTIALSets the output file organisation to sequential.acted on
/SPECIFICATION=fileParses a specification file that drives fields, conditions and include/omit rules.acted on
/PROCESS=(...)Parsed into RECORD/TAG/ADDRESS/INDEX but not forwarded to the engine.accepted for compatibility
/FORMAT=(...)FIXED/VARIABLE/CONTROLLED and the size keywords parse but do not change the sort.accepted for compatibility

Specification files

A specification file lets you drive the sort declaratively instead of on the command line. /SPECIFICATION reads FIELD, CONDITION and INCLUDE/OMIT directives (handled by SOR$SPEC_FILE), so you can define fields and record-selection conditions in one place and reuse them across jobs.

Statistics output

The /STATISTICS qualifier prints a banner after the run:

        VX/SOR Sort and Merge Utility v<Version_Id>

Records read:         <n>
Records sorted:       <n>
Records output:       <n>
Elapsed time (Secs)   <n>
Elapsed CPU  (Secs)   <f>

Compatibility and status

VX/SOR targets Linux. The callable API and the DCL verbs are compatible with OpenVMS Sort/Merge for the features an application actually relies on to sort and merge records. The matrix below is an honest account of what is live, what is partial, and the handful of areas that are accepted for compatibility but do not change results.

CapabilityStatusNote
Ten callable SOR$ routinesliveAll built into the VX/RT runtime library.
SORT / MERGE DCL verbsliveFull VMS command-line parsing, driving the same engine.
External merge sort with fast k-way mergeliveLegacy N-way merge retained as a fallback.
/SPECIFICATION file parserliveFIELD, CONDITION and INCLUDE/OMIT directives.
STABLE, NODUPLICATES optionsliveDefault is STABLE; STABLE with NODUPS is rejected as on VMS.
SOR$STAT counterspartialCore record and length counters are wired; other codes return zero.
User data-type compare via DTYPEpartialWorks, but currently exported as SYR$DTYPE (see the callable API section).
TAG / INDEX / ADDRESS process typespartialReachable through the file interface; SOR$RELEASE_REC handles RECORD only.
/PROCESS, /FORMAT qualifiersacceptedParsed for compatibility; values do not change the sort.
NOSIGNAL optionacceptedAccepted; the engine returns conditions as values and never signals.
Windows buildnot offeredVX/SOR runs on Linux.

Behaviour notes for a migrating team

  • Register a user compare routine through SYR$DTYPE or the lower-case sor$dtype until the upper-case SOR$DTYPE alias is corrected.
  • TAG, INDEX and ADDRESS sorts are driven through SOR$PASS_FILES, not through the release/return record loop.
  • The NOSIGNAL option and the disk-allocation arguments are accepted but have no observable effect, because the engine reports every condition as a returned status value.
  • /PROCESS and /FORMAT at the DCL layer parse cleanly and are ignored, so existing command lines run without edits.

Quick reference

Call lifecycle

INIT_SORT or BEGIN_SORTInitialize the sort context
Supply inputRELEASE_REC x N or PASS_FILES x N
SORT_MERGESort records and merge runs
RETURN_REC x NRead until end-of-file, then call END_SORT
Call SPEC_FILE or DTYPE before BEGIN_SORT. Call STAT at any time after initialization.

DCL syntax

$ SORT/KEY=(POSITION:1,SIZE:10) [/DESCENDING] [/STABLE]
       [/NODUPLICATES] [/STATISTICS] [/SPECIFICATION=RULES.SPC] in.dat out.dat
$ MERGE/KEY=(POSITION:1,SIZE:10) in1.dat,in2.dat out.dat

Callable routines

SOR$INIT_SORT
SOR$BEGIN_SORT
SOR$PASS_FILES
SOR$RELEASE_REC
SOR$SORT_MERGE
SOR$RETURN_REC
SOR$END_SORT
SOR$SPEC_FILE
SOR$STAT
SOR$DTYPE

Key buffer

[N, {type, order, offset, length} x N]
INIT_SORT offsets 1-based; BEGIN_SORT offsets 0-based.

Tuning environment

VariableEffect
SOR_PAGE_SIZEIn-memory logical-block size (bytes, above 1024; default 4 MiB).
SOR_SWAP_IO_SIZESwap-file I/O unit and maximum record size (bytes, above 1024; default 4 MiB).
SORTWORK0..9Placement of the SORTWORK swap files.
VXSOR_MERGE_ENGINESet to legacy to force the older N-way merge.
SORLIBNames a replacement sort engine shared library.

Limits

64 contexts
10 input files
11 SORTWORK files
50 keys per context
100 conditions per spec node

Frequently Asked Questions

Curious about how Sector7 can facilitate your application migration? Explore our FAQs for expert insights.

What is VX/SOR and how does it help with OpenVMS migration?

VX/SOR is a complete VMS/OpenVMS SORT MERGE implementation that fully supports the SOR$ API set, VX/DCL command line, and Linux shell execution. It enables organizations to migrate their OpenVMS sorting and merging operations to Linux without modifying existing application logic.

The tool replicates VMS sorting capabilities using multi-file merge sort techniques with quick-sort enhancements for optimal performance. For migration planning assistance, contact us.

Which SOR$ API functions does VX/SOR support?

VX/SOR provides complete support for all essential SOR$ API functions including SOR$BEGIN_SORT, SOR$END_SORT, SOR$INIT_SORT, SOR$MERGE, SOR$PASS_FILES, SOR$RELEASE_REC, SOR$RETURN_REC, SOR$SORT_MERGE, SOR$SPEC_FILE, and SOR$STAT.

Can VX/SOR run SORT commands through VX/DCL?

Yes, VX/SOR integrates fully with VX/DCL, supporting native VMS command syntax for SORT and MERGE operations with complete command parsing and execution support.

How does the VX/SOR migration process work?

VX/SOR enables seamless migration by providing call-compatible APIs that ensure applications using VMS sorting functions continue working without code changes. The tool maintains identical sorting behavior through its comprehensive qualifier support and API compatibility.

Which SORT qualifiers are supported in VX/SOR?

VX/SOR supports all essential VMS SORT qualifiers including ASCENDING, BINARY, CHARACTER, DECIMAL, DESCENDING, KEY, PROCESS, SEQUENTIAL, STATISTICS, and DUPLICATES, ensuring identical sorting behavior across platforms.

Does VX/SOR provide performance optimization features?

VX/SOR leverages multi-file merge sort techniques with quick-sort enhancements to optimize performance. The STATISTICS qualifier provides detailed performance metrics, allowing teams to monitor and optimize sorting operations effectively.

What is VX/SOR?

VX/SOR is a complete reimplementation of the OpenVMS SORT/MERGE utility for Linux. It provides the full SOR$ callable Sort/Merge API and the SORT and MERGE DCL verbs, so migrated applications can sort and merge records on Linux exactly as they did on OpenVMS.

Do I have to change my application code to use VX/SOR?

No. VX/SOR provides call-compatible SOR$ routines and VX/DCL integration, so programs that call the OpenVMS sort routines and DCL scripts that run SORT or MERGE keep working without changes to their sort logic.

Which SOR$ routines does VX/SOR provide?

VX/SOR ships a callable library covering SOR$INIT_SORT, SOR$BEGIN_SORT, SOR$PASS_FILES, SOR$RELEASE_REC, SOR$SORT_MERGE, SOR$RETURN_REC, SOR$END_SORT, SOR$SPEC_FILE, SOR$STAT and SOR$DTYPE, so existing OpenVMS applications that call these routines migrate to Linux without rewriting their sort code.

How do I run a SORT from the command line?

Use the SORT verb through VX/DCL with the same syntax as OpenVMS. It takes one or more input files and an output file, and /KEY defines the sort key by 1-based byte position and size. For example: SORT/KEY=(POSITION:1,SIZE:10) ACCOUNTS.DAT SORTED_ACCOUNTS.DAT.

How do I merge files that are already sorted?

Use the MERGE verb. MERGE combines two or more inputs that are already sorted on the same keys into one ordered output file. It does not re-sort the inputs, which makes it faster than a full sort when the data is already in order.

Which SORT qualifiers does VX/SOR support?

VX/SOR acts on /KEY (position, size, number, ascending or descending, and the CHARACTER, D_FLOATING, F_FLOATING, G_FLOATING, H_FLOATING and PACKED_DECIMAL data types), /STABLE, /STATISTICS, /DUPLICATES and /NODUPLICATES, /SEQUENTIAL and /SPECIFICATION. Some further keywords are accepted for compatibility so existing command lines run without edits.

Does VX/SOR help with sort performance?

Yes. VX/SOR uses an external merge sort with a fast buffered k-way merge, and the /STATISTICS qualifier reports records read, sorted and output plus elapsed and CPU time. Environment variables let you tune the in-memory block size, the swap I/O size, and the placement of the SORTWORK files.

Can I define the sort in a specification file instead of on the command line?

Yes. The /SPECIFICATION qualifier reads FIELD, CONDITION and INCLUDE/OMIT directives from a specification file, so you can define fields and record-selection rules declaratively and reuse them across jobs instead of building long command lines.

Transform Your Legacy Software Today!

Get In Touch
Unlock the potential of your legacy software with our expert migration services.