Module: Parse::Core::Indexing
- Included in:
- Object
- Defined in:
- lib/parse/model/core/indexing.rb
Overview
Model-declarative MongoDB index DSL. Mixed into Parse::Object so
subclasses can declare the indexes they expect to exist on their
collection. Declarations are inert at load time — they only land
on MongoDB when Schema::IndexMigrator reads them and
apply_indexes! is invoked through the writer connection.
SECURITY POSTURE — purely declarative. No network I/O, no class
introspection that could leak data, no LLM-visible surface. The
validation rules below run at declaration time so a typo
surfaces as a load-time error, not a runtime surprise during
rake parse:mongo:indexes:apply in prod.
Constant Summary collapse
- MAX_INDEXES_PER_COLLECTION =
MongoDB limits each collection to 64 indexes total (including the implicit
_id_index). The migrator's plan phase reports remaining capacity using this constant. 64- PARSE_MANAGED_ARRAY_FIELDS =
Parse-managed array columns we can know about without introspecting actual data. Used by #assert_at_most_one_array_field! to catch parallel-array compounds at declaration time even when the parallel field is the
_rperm/_wpermACL array. %w[_rperm _wperm].to_set.freeze
- SENSITIVE_FIELDS =
Wire-format column names that hold Parse-internal secret material (password hashes, session tokens, verification tokens, auth provider blobs). The DSL refuses to declare an index on any of these because a queryable index on bcrypt hashes or session tokens turns
$indexStats/ collection-scan access into a credential-enumeration oracle. Parse Server already manages the legitimate indexes for these columns (see Schema::IndexMigrator::PARSE_MANAGED_INDEX_PATTERNS); this guard exists so a typo or malicious PR can't add a new one. %w[ _hashed_password _session_token _email_verify_token _perishable_token _password_history authData _auth_data ].freeze
Instance Method Summary collapse
-
#apply_indexes!(drop: false) ⇒ Hash
Apply additive index changes via the writer connection.
-
#indexes_plan ⇒ Hash{String=>Hash}
Dry-run reconciliation between declared indexes and what's on the collection.
-
#mongo_geo_index(field, sparse: false, name: nil) ⇒ Object
Sugar for a 2dsphere geospatial index.
-
#mongo_index(*fields, unique: false, sparse: false, partial: nil, expire_after: nil, name: nil) ⇒ Hash
Declare a regular (B-tree) index on one or more fields.
-
#mongo_index_declarations ⇒ Array<Hash>
Storage for declared indexes.
-
#mongo_relation_index(field, bidirectional: false, dedup: false, unique: false) ⇒ Array<Hash>
Declare an index on a Parse Relation's join collection.
Instance Method Details
#apply_indexes!(drop: false) ⇒ Hash
Apply additive index changes via the writer connection. Pass
drop: true to also drop orphan indexes; each drop carries its
own audit log and confirmation envelope.
192 193 194 |
# File 'lib/parse/model/core/indexing.rb', line 192 def apply_indexes!(drop: false) Parse::Schema::IndexMigrator.new(self).apply!(drop: drop) end |
#indexes_plan ⇒ Hash{String=>Hash}
Dry-run reconciliation between declared indexes and what's on the collection. Delegates to Schema::IndexMigrator.
184 185 186 |
# File 'lib/parse/model/core/indexing.rb', line 184 def indexes_plan Parse::Schema::IndexMigrator.new(self).plan end |
#mongo_geo_index(field, sparse: false, name: nil) ⇒ Object
Sugar for a 2dsphere geospatial index. Geopoint columns are
stored in Mongo as GeoJSON { type: "Point", coordinates: [lng, lat] }
which 2dsphere indexes natively.
95 96 97 98 |
# File 'lib/parse/model/core/indexing.rb', line 95 def mongo_geo_index(field, sparse: false, name: nil) register_index([field], key_value: "2dsphere", unique: false, sparse: sparse, partial: nil, expire_after: nil, name: name) end |
#mongo_index(*fields, unique: false, sparse: false, partial: nil, expire_after: nil, name: nil) ⇒ Hash
Declare a regular (B-tree) index on one or more fields. Symbols
in fields are looked up against the class's references table
— pointers auto-rewrite to _p_<field> so callers think in
property names. Use mongo_geo_index for 2dsphere indexes.
86 87 88 89 90 |
# File 'lib/parse/model/core/indexing.rb', line 86 def mongo_index(*fields, unique: false, sparse: false, partial: nil, expire_after: nil, name: nil) register_index(fields, key_value: 1, unique: unique, sparse: sparse, partial: partial, expire_after: expire_after, name: name) end |
#mongo_index_declarations ⇒ Array<Hash>
Storage for declared indexes. Each entry is a frozen Hash with
the keys :keys, :options, :declared_for (the source-of-truth
symbol list from the mongo_index call, for diagnostics).
63 64 65 |
# File 'lib/parse/model/core/indexing.rb', line 63 def mongo_index_declarations @mongo_index_declarations ||= [] end |
#mongo_relation_index(field, bidirectional: false, dedup: false, unique: false) ⇒ Array<Hash>
Declare an index on a Parse Relation's join collection. Relations
are stored in _Join:<field>:<ParentClass> collections — these
have no Ruby model, so an add_index :field against the parent
class would index the wrong collection. This method routes the
declaration to the correct join-collection name, with the
conventional column shape: owningId is the parent-side foreign
key, relatedId is the related-side.
Default: single declaration on {owningId: 1} — the forward
lookup ("what's related to this owner"), which is the dominant
pattern for most Parse Relation queries.
bidirectional: true adds a second declaration on
{relatedId: 1} — the reverse lookup ("which owners contain
this related object"). For high-traffic auth patterns like
Parse::Role.users, the reverse direction is often the
heavier-used index.
Uniqueness on a single-direction relation index is NOT
supported — unique: true on just owningId (or just
relatedId) would assert each owner can hold at most one
related, contradicting has_many. That mistake is rejected at
declaration time.
dedup: true is semantically different and IS supported: it
registers a compound {owningId: 1, relatedId: 1} unique index
on the join collection. The compound key prevents duplicate
(owner, related) pair rows from accumulating (a real failure
mode under concurrent .add calls on a Parse Relation), without
constraining how many distinct relateds an owner may hold or
vice versa. Default off — the index buys correctness at the
cost of a write-time uniqueness check on every relation insert,
and existing collections with duplicate pairs will fail the
migrator's apply step until reconciled.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/parse/model/core/indexing.rb', line 155 def mongo_relation_index(field, bidirectional: false, dedup: false, unique: false) if unique raise ArgumentError, "#{self}.mongo_relation_index does not support unique: — uniqueness on " \ "a single-direction relation column breaks has_many semantics. Use " \ "`dedup: true` for a compound `{owningId, relatedId}` unique index that " \ "prevents duplicate-pair membership without constraining cardinality." end field = field.to_sym unless respond_to?(:relations) && relations.key?(field) raise ArgumentError, "#{self}.mongo_relation_index requires #{field.inspect} to be declared " \ "via `has_many :#{field}, through: :relation`. Got non-relation field." end join_collection = "_Join:#{field}:#{parse_class}" decls = [register_relation_index(join_collection, "owningId", source: field)] decls << register_relation_index(join_collection, "relatedId", source: field) if bidirectional if dedup decls << register_relation_dedup_index(join_collection, source: field) end decls end |