Apophis

 view release on metacpan or  search on metacpan

PLAN.md  view on Meta::CPAN

horus_stamp_version_variant(uuid, 5);
```

### `apophis_path_for(store_dir, id) -> path string`
2-level hex sharding: `a3bb189e-...` → `store/a3/bb/a3bb189e-...`

### `apophis_mkdir_p(path)`
Recursive directory creation in C using `mkdir()`.

### `apophis_store_file(path, content, content_len)`
Atomic write: write to `path.tmp.$$`, then `rename()`.

### `apophis_meta_write(path, meta_hv)`
Simple key=value sidecar format (no JSON dependency). Writes `path.meta`.

### `apophis_meta_read(path) -> HV*`
Reads `.meta` sidecar back into a hash.

## XSUBs (Perl-visible API)

### `Apophis->new(namespace => $ns, store_dir => $dir)`

PLAN.md  view on Meta::CPAN


### 2-Level Hex Sharding
```
/store/a3/bb/a3bb189e-8bf9-5f18-b3f6-1b2f5f5c1e3a
```
256 x 256 = 65,536 shard directories. ~15 entries per leaf at 1M objects.

### Atomic Writes
```
write to: /store/a3/bb/a3bb189e-...tmp.PID
rename:   /store/a3/bb/a3bb189e-...
```

### Metadata Sidecars
```
/store/a3/bb/a3bb189e-....meta
```
Simple `key=value\n` format. No JSON dependency.

## Implementation Phases

lib/Apophis.pm  view on Meta::CPAN


Apophis is a B<100% XS> content-addressable storage library built on the
B<Horus> UUID library (RFC 9562).  It generates deterministic UUID v5
identifiers for arbitrary content using SHA-1 namespace hashing.

Same content always produces the same UUID.  Different namespaces produce
different UUIDs for the same content.

Stored objects are sharded across a 2-level hex directory tree (65,536
directories) for efficient filesystem access at scale.  Writes are atomic
(temp + rename).  CAS is naturally idempotent — no locking required.

All logic is implemented in C for maximum performance.  The Perl layer
is just C<XSLoader> — every method is an XSUB.

=head1 METHODS

=head2 new

    my $ca = Apophis->new(
        namespace => 'myapp-files',    # required

lib/Apophis.pm  view on Meta::CPAN

C<identify()> would for the same content.

=head2 store

    my $id = $ca->store(\$content);
    my $id = $ca->store(\$content, store_dir => '/other');
    my $id = $ca->store(\$content, meta => { mime_type => 'image/png' });

Identifies the content and writes it to the sharded store.  Returns the
UUID.  If the content already exists, returns immediately (CAS dedup).
Writes are atomic via temp file + rename.

=head2 fetch

    my $data_ref = $ca->fetch($id);

Returns a scalar reference to the stored content, or C<undef> if not found.

=head2 exists

    if ($ca->exists($id)) { ... }

lib/Apophis.xs  view on Meta::CPAN

/*
 * Windows compatibility:
 * perl.h on threaded Windows redefines mkdir, stat, unlink etc. as macros
 * requiring the interpreter context (my_perl).  Our static C helper functions
 * don't have pTHX, so we undo those overrides and use the real libc calls.
 */
#ifdef WIN32
#  undef mkdir
#  undef stat
#  undef unlink
#  undef rename
#  undef open
#  undef close
#  undef read
#  undef write
   /* Windows mkdir takes only one arg */
#  define apophis_mkdir(p, m) _mkdir(p)
#else
#  define apophis_mkdir(p, m) mkdir(p, m)
#endif

lib/Apophis.xs  view on Meta::CPAN


    memcpy(buf, file_path, len + 1);
    last_slash = strrchr(buf, '/');
    if (last_slash) {
        *last_slash = '\0';
        apophis_mkdir_p(buf);
    }
}

/* ------------------------------------------------------------------ */
/* Internal: atomic file write (temp + rename)                         */
/* ------------------------------------------------------------------ */

static void
apophis_atomic_write(pTHX_ const char *path,
                     const char *content, STRLEN content_len)
{
    char tmp_path[APOPHIS_PATH_MAX];
    PerlIO *fh;
    SSize_t written;

lib/Apophis.xs  view on Meta::CPAN

        croak("Apophis: cannot write '%s': %s", tmp_path, strerror(errno));

    written = PerlIO_write(fh, content, content_len);
    PerlIO_close(fh);

    if (written != (SSize_t)content_len) {
        unlink(tmp_path);
        croak("Apophis: short write to '%s'", tmp_path);
    }

    if (rename(tmp_path, path) != 0) {
        unlink(tmp_path);
        croak("Apophis: cannot rename '%s' -> '%s': %s",
              tmp_path, path, strerror(errno));
    }
}

/* ------------------------------------------------------------------ */
/* Internal: metadata sidecar (key=value\n format)                     */
/* ------------------------------------------------------------------ */

static void
apophis_meta_write(pTHX_ const char *meta_path, HV *meta)

lib/Apophis.xs  view on Meta::CPAN

        STRLEN vlen;
        const char *vstr = SvPV(val, vlen);
        PerlIO_write(fh, key, (SSize_t)klen);
        PerlIO_write(fh, "=", 1);
        PerlIO_write(fh, vstr, (SSize_t)vlen);
        PerlIO_write(fh, "\n", 1);
    }

    PerlIO_close(fh);

    if (rename(tmp_path, meta_path) != 0) {
        unlink(tmp_path);
        croak("Apophis: cannot rename metadata '%s': %s",
              tmp_path, strerror(errno));
    }
}

static HV *
apophis_meta_read(pTHX_ const char *meta_path)
{
    PerlIO *fh;
    HV *meta;
    apophis_stat_t st;

lib/Apophis.xs  view on Meta::CPAN

 *
 * Stack input:  self_sv, content_ref_sv
 * Stack output: uuid_string_sv
 *
 * Fuses the entire store pipeline into a single op:
 *   1. Extract namespace bytes from object
 *   2. SHA-1 hash content → UUID v5
 *   3. Compute 2-level sharded path
 *   4. stat() for CAS dedup check
 *   5. mkdir -p parent directories
 *   6. Atomic write (temp + rename)
 *   7. Format and return UUID string
 */
static OP *
pp_apophis_store(pTHX) {
    dSP;
    SV *content_ref_sv = POPs;
    SV *self_sv = POPs;
    HV *hv;
    const unsigned char *ns;
    SV *content_sv;

ppport.h  view on Meta::CPAN

KEY_qx|5.003007||Viu
KEY_rand|5.003007||Viu
KEY_read|5.003007||Viu
KEY_readdir|5.003007||Viu
KEY_readline|5.003007||Viu
KEY_readlink|5.003007||Viu
KEY_readpipe|5.003007||Viu
KEY_recv|5.003007||Viu
KEY_redo|5.003007||Viu
KEY_ref|5.003007||Viu
KEY_rename|5.003007||Viu
KEY_require|5.003007||Viu
KEY_reset|5.003007||Viu
KEY_return|5.003007||Viu
KEY_reverse|5.003007||Viu
KEY_rewinddir|5.003007||Viu
KEY_rindex|5.003007||Viu
KEY_rmdir|5.003007||Viu
KEY_s|5.003007||Viu
KEY_say|5.009003||Viu
KEY_scalar|5.003007||Viu

ppport.h  view on Meta::CPAN

M_PAT_MODS|5.009005||Viu
MPH_BUCKETS|5.027011||Viu
MPH_RSHIFT|5.027011||Viu
MPH_VALt|5.027011||Viu
mPUSHi|5.009002|5.003007|p
mPUSHn|5.009002|5.003007|p
mPUSHp|5.009002|5.003007|p
mPUSHs|5.010001|5.003007|p
mPUSHu|5.009002|5.003007|p
mro_clean_isarev|5.013007||Viu
mro_gather_and_rename|5.013007||Viu
mro_get_from_name|5.010001|5.010001|u
mro_get_linear_isa|5.009005|5.009005|
mro_get_linear_isa_dfs|5.009005||Vi
mro_get_private_data|5.010001|5.010001|
MRO_GET_PRIVATE_DATA|5.010001||Viu
mro_isa_changed_in|5.009005||Vi
mro_meta_dup|5.009005||Viu
mro_meta_init|||ciu
mro_method_changed_in|5.009005|5.009005|
mro_package_moved|5.013006||Vi

ppport.h  view on Meta::CPAN

PerlLIO_isatty|5.005000||Viu
PerlLIO_link|5.006000||Viu
PerlLIO_lseek|5.005000||Viu
PerlLIO_lstat|5.005000||Viu
PerlLIO_mktemp|5.005000||Viu
PerlLIO_open3|5.005000||Viu
PerlLIO_open3_cloexec|5.027008||Viu
PerlLIO_open|5.005000||Viu
PerlLIO_open_cloexec|5.027008||Viu
PerlLIO_read|5.005000||Viu
PerlLIO_rename|5.005000||Viu
PerlLIO_setmode|5.005000||Viu
PerlLIO_stat|5.005000||Viu
PerlLIO_tmpnam|5.005000||Viu
PerlLIO_umask|5.005000||Viu
PerlLIO_unlink|5.005000||Viu
PerlLIO_utime|5.005000||Viu
PerlLIO_write|5.005000||Viu
PERL_LOADMOD_DENY|5.006000|5.003007|
PERL_LOADMOD_IMPORT_OPS|5.006000|5.003007|
PERL_LOADMOD_NOIMPORT|5.006000|5.003007|

ppport.h  view on Meta::CPAN

reg_temp_copy|5.009005||cViu
REG_TOP_LEVEL_BRANCHES_SEEN|5.019009||Viu
regtry|5.005000||Viu
REG_UNBOUNDED_QUANTIFIER_SEEN|5.019009||Viu
REG_UNFOLDED_MULTI_SEEN|5.019009||Viu
REG_VERBARG_SEEN|5.019009||Viu
REG_ZERO_LEN_SEEN|5.019009||Viu
re_indentf|5.023009||vViu
re_intuit_start|5.006000||cVu
re_intuit_string|5.006000||cVu
rename|5.005000||Viu
Renew|5.003007|5.003007|
Renewc|5.003007|5.003007|
RENUM|5.005000||Viu
re_op_compile|5.017001||Viu
repeatcpy|5.003007|5.003007|nu
REPLACEMENT_CHARACTER_UTF8|5.025005|5.003007|p
report_evil_fh|5.006001||Viu
report_redefined_cv|5.015006||Viu
report_uninit|5.006000||cVi
report_wrongway_fh|5.013009||Viu

ppport.h  view on Meta::CPAN

    return IS_NUMBER_IN_UV;
  }
  return 0;
}
#endif
#endif

/*
 * The grok_* routines have been modified to use warn() instead of
 * Perl_warner(). Also, 'hexdigit' was the former name of PL_hexdigit,
 * which is why the stack variable has been renamed to 'xdigit'.
 */

#ifndef grok_bin
#if defined(NEED_grok_bin)
static UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
static
#else
extern UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
#endif



( run in 2.031 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )