Skip to content

SemVer Range Support - #15

Description

@rsantmyer

SemVer Range Support

Purpose

Core should provide database-native safety checks that prevent unsafe
application deployments even when dbpm is not used.

Today, pkg_application stores application versions as major, minor, and
patch components and exposes several APIs that require callers to pass those
components separately. That works, but it becomes awkward for deployment
preconditions and dependency ranges.

For example, a deployment script that should run only when an application is
currently at 1.6.x should not need separate minimum and maximum major, minor,
and patch parameters. A single range expression is easier to read, easier to
generate, and less error-prone.

Core should move toward SemVer range syntax while preserving the existing
serialized numeric comparison model internally.

Goals

  • Keep Core as the authoritative in-database guard against unsafe deployments.
  • Let deployment scripts express required installed versions using one readable
    range string.
  • Let dependency registration use the same range syntax.
  • Preserve the existing APP_DEPENDENCY.VERSION_MIN and VERSION_MAX columns
    for the first implementation.
  • Preserve existing package procedures for backward compatibility.
  • Avoid a full package-manager range language until there is a clear need for
    it.

Non-Goals

  • Core will not resolve package repositories or choose upgrade paths.
  • Core will not replace dbpm dependency solving.
  • Core will not initially support disjunctive expressions such as
    >=1.6.0 <1.8.0 || >=2.0.0.
  • Core will not initially store the original range expression unless a later
    schema change is explicitly planned.

Initial Range Syntax

The first implementation should support a small, documented subset.

Expression Meaning
* Any version
1.6.0 Exactly 1.6.0
1.6.x >=1.6.0 and <=1.6.9999
1.x >=1.0.0 and <=1.9999.9999
>=1.6.0 >=1.6.0
<=1.7.0 <=1.7.0
1.6.0 - 1.6.x Inclusive closed range
~1.6.0 >=1.6.0 and <1.7.0
^1.6.0 >=1.6.0 and <2.0.0

Because Core currently stores inclusive numeric bounds, exclusive upper bounds
should be converted to the largest representable previous version:

Expression Stored minimum Stored maximum
~1.6.0 1.6.0 1.6.9999
^1.6.0 1.6.0 1.9999.9999

Core currently supports version components from 0 through 9999, serialized
as MMMMIIIIPPPP. Range parsing should preserve those bounds.

API Plan

Range Parsing

Add a parser that converts a range expression into serialized minimum and
maximum values.

Because PL/SQL functions cannot easily return multiple scalar values without
adding SQL object types, the first implementation should favor a procedure:

pkg_application.parse_version_range_p(
   ip_version_range => '1.6.x',
   op_version_min   => l_version_min,
   op_version_max   => l_version_max
);

Both output values should use the same serialized format produced by
serialize_version_f.

Version Satisfaction

Add a function for callers and tests that need a direct boolean-style answer:

pkg_application.version_satisfies_f(
   ip_version       => '1.6.3',
   ip_version_range => '1.6.x'
);

Return values should follow Core's existing style:

  • Y
  • N

Invalid version or range input should raise an assertion error rather than
returning N.

Deployment Preconditions

Add a preferred range-based deployment guard:

pkg_application.check_app_version_p(
   ip_application_name => 'MY_APP',
   ip_version_range    => '1.6.x'
);

This procedure should:

  • read the currently registered version from APPLICATION
  • parse ip_version_range
  • assert that the current version is within the range
  • produce an error message that includes the installed version and the required
    range

Example deployment precondition:

BEGIN
   pkg_application.check_app_version_p(
      ip_application_name => 'MY_APP',
      ip_version_range    => '1.6.x'
   );

   pkg_application.begin_deployment_p(
      ip_application_name => 'MY_APP',
      ip_major_version    => 1,
      ip_minor_version    => 7,
      ip_patch_version    => 0,
      ip_deployment_type  => pkg_application.c_deploy_type_minor
   );
END;
/

This blocks a direct 1.5.0 to 1.7.0 deployment when the 1.7.0 script
requires the database to already be at 1.6.x.

Dependency Registration

Add an overload for dependency registration:

pkg_application.add_dependency_p(
   ip_application_name => 'MY_APP',
   ip_depends_on       => 'OTHER_APP',
   ip_version_range    => '^2.3.0'
);

The overload should parse the range and store the resulting serialized minimum
and maximum in APP_DEPENDENCY.VERSION_MIN and VERSION_MAX.

The existing numeric-bound overload should remain available.

Compatibility

Existing procedures should remain in place:

  • check_min_app_version_p
  • check_already_deployed_p
  • add_dependency_p with ip_version_min and ip_version_max
  • serialize_version_f
  • deserialize_version_f

The range-based APIs should become the recommended interface for new deployment
scripts and documentation.

Parser Rules

The parser should normalize whitespace before evaluation.

Suggested validation rules:

  • Versions must be major.minor.patch unless using a supported wildcard form.
  • Numeric components must be integers from 0 through 9999.
  • Leading zeroes should remain invalid, matching serialize_version_f.
  • Wildcards should be accepted as x, X, or * in range expressions.
  • Open-ended comparison expressions should use Core's any-version bounds:
    0 and 999999999999.
  • Minimum must be less than or equal to maximum after serialization.

Suggested implementation order:

  1. exact version
  2. wildcard version
  3. comparison expression
  4. hyphen range
  5. tilde range
  6. caret range

Documentation Examples

Sequential minor release:

pkg_application.check_app_version_p('MY_APP', '1.6.x');

Cumulative release that supports any previous 1.x version from 1.4.0
forward:

pkg_application.check_app_version_p('MY_APP', '1.4.0 - 1.x');

Strict one-step patch release:

pkg_application.check_app_version_p('MY_APP', '1.6.2');

Dependency compatible with a major version:

pkg_application.add_dependency_p('MY_APP', 'OTHER_APP', '^2.3.0');

Test Plan

Add SQL tests that cover:

  • exact version satisfaction
  • wildcard ranges
  • comparison ranges
  • hyphen ranges
  • tilde ranges
  • caret ranges
  • invalid syntax
  • invalid numeric bounds
  • deployment precondition success and failure
  • dependency registration via range syntax
  • dependency validation using stored min/max bounds

Important deployment safety cases:

  • installed 1.6.0, required 1.6.x: success
  • installed 1.6.5, required 1.6.x: success
  • installed 1.5.0, required 1.6.x: failure
  • installed 1.7.0, required 1.6.x: failure
  • installed 1.5.0, required ^1.5.0: success
  • installed 2.0.0, required ^1.5.0: failure

Rollout Plan

Phase 1: Add Range Helpers

  • Add parse_version_range_p.
  • Add version_satisfies_f.
  • Add tests for parsing and satisfaction.

Phase 2: Add Deployment Guard

  • Add check_app_version_p.
  • Add deployment precondition tests.
  • Document the range-based guard as preferred for new scripts.

Phase 3: Add Dependency Overload

  • Add add_dependency_p overload with ip_version_range.
  • Keep storing serialized numeric min/max values.
  • Add dependency registration and validation tests.

Phase 4: Adopt in Core Wrappers

  • Update future Core release manifests to use check_app_version_p before
    begin_deployment_p where appropriate.
  • Prefer explicit range guards for non-initial deployment scripts.

Phase 5: Optional Schema Enhancement

If operational visibility requires preserving the original expression, add an
optional VERSION_RANGE column to APP_DEPENDENCY in a later release.

That should be additive only. The serialized min/max columns should remain the
authoritative validation fields unless a broader range model is introduced.

Open Questions

  • Should exact versions be treated as exact by default, or should bare 1.6
    ever be allowed as shorthand for 1.6.x? Initial recommendation: no.
  • Should Core support prerelease or build metadata? Initial recommendation: no.
  • Should ^0.x.y follow npm SemVer semantics exactly? Initial recommendation:
    defer 0.x caret support rules until there is a real Core use case, or keep
    the first implementation conservative and documented.
  • Should invalid dependency ranges fail during registration only, or also be
    revalidated during dependency checks? Initial recommendation: fail during
    registration and rely on stored numeric bounds during validation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions