Apophis
view release on metacpan or search on metacpan
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)`
### 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;
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
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
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|
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
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 )