Readme

NAME

Roaring::Tags - High-level tag system built on CRoaring bitmaps

SYNOPSIS


use Roaring::Tags;

my $tags = Roaring::Tags.new;

# Boolean tags
$tags.tag('nsfw', 42);
$tags.tag-many('landscape', [1, 2, 3, 4, 5]);
$tags.untag('nsfw', 42);
$tags.has-tag('landscape', 3);     # True

# Categorical tags (field:value)
$tags.tag('genre:fantasy', 42);
$tags.tag('genre', 'romance', 42);   # Two-arg form

# Numeric properties with range queries
$tags.set-value('score', 42, 150);
$tags.set-values('score', ([1, 100], [2, 200]));

# Query — returns owned CRoaring bitmaps
my $results = $tags.query('landscape');
my $fantasy = $tags.query('genre:fantasy');
my $high    = $tags.range('score', :gt(100));

# Compose with CRoaring set operations
my $filtered = $tags.query('landscape')
    .and($tags.range('score', :gte(50)))
    .andnot($tags.query('violent'));

# Search with booru-style query syntax
my $results = $tags.search('landscape, hdr, score:>100, -violent');
my $complex = $tags.search('(landscape, hdr) OR (portrait, bokeh), score:>50');
my $negonly = $tags.search('-nsfw, -violent');  # Uses universe bitmap

# Pagination (newest first)
my @page1 = $results.slice-reverse(0, 20);
my @page2 = $results.slice-reverse(20, 20);

# Introspection
$tags.tags;                        # All tag keys
$tags.boolean-tags;                # Keys without ':'
$tags.fields;                      # Categorical + numeric field names
$tags.field-values('genre');       # ('fantasy', 'romance')
$tags.numeric-fields;              # ('score',)
$tags.count('landscape');          # Cardinality
$tags.doc-tags(42);                # Tags for a document
$tags.doc-values(42);              # Numeric values for a document

# Document removal
$tags.remove-doc(42);              # Removes from all tags, indices, and universe

# Persistence
$tags.save('my-tags'.IO);
my $loaded = Roaring::Tags.load('my-tags'.IO);

DESCRIPTION

Roaring::Tags provides a high-level tag system for millions of documents with sub-millisecond set operations. Built on CRoaring compressed bitmaps.

Tag types

  • Boolean — nsfw, landscape — document either has it or doesn't

  • Categorical — genre:fantasy, rating:safe — field:value pairs, multiple values per field

  • Numeric — score, token_count — integer values with range queries

Query syntax

The search method accepts booru-style query strings:


landscape                   # Boolean tag (AND)
-violent                    # Negation (ANDNOT)
genre:fantasy               # Categorical tag
score:>100                  # Numeric: greater than
score:>=100                 # Numeric: greater than or equal
score:<50                   # Numeric: less than
score:<=50                  # Numeric: less than or equal
score:100..500              # Numeric: inclusive range
(a, b) OR (c, d)           # OR groups with parentheses
a OR b                      # Bare OR
a OR b, c                   # AND binds tighter: a OR (b AND c)
(a OR b), c                 # Parens override: (a OR b) AND c

Terms are comma-separated (AND). OR binds tighter than comma. Negative-only queries (-nsfw, -violent) use the universe bitmap to return everything except the excluded tags.

The execution planner sorts positive terms by ascending cardinality (smallest bitmap first) to minimize intermediate set sizes.

Universe bitmap

Every document that is tagged or given a numeric value is automatically tracked in a universe bitmap. This enables negative-only queries and is persisted alongside the tag data.

Persistence

save writes a directory with:

  • meta.json — tag keys, numeric fields, filename mappings

  • bitmaps/*.crb — CRoaring serialized bitmaps

  • numeric/*.json — sorted index data

  • bitmaps/__universe__.crb — universe bitmap

load restores the full state. Compatible with v1 format (rebuilds universe from bitmaps).

Query explain


my $q = Roaring::Tags::Query.new(:tags($db), :query-string('(a, b) OR c, -d'));
say $q.explain;
# Query: (a, b) OR c, -d
#
# Execution plan:
#   AND
#     OR
#       AND
#         a (cardinality: 500)
#         b (cardinality: 200)
#       c (cardinality: 1000)
#     -d (cardinality: 50)

AUTHOR

Matt Doughty <[email protected]>

COPYRIGHT AND LICENSE

Copyright 2026 Matt Doughty

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.

Roaring::Tags v0.2.2

High-level tag system built on CRoaring bitmaps with boolean, categorical, and numeric range query support

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

CRoaring:auth<zef:apogee>JSON::Fast:ver<0.19>:auth<cpan:TIMOTIMO>

Test Dependencies

Provides

  • Roaring::Tags
  • Roaring::Tags::NumericIndex
  • Roaring::Tags::Query
  • Roaring::Tags::Query::Term
  • Roaring::Tags::Storage

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.