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`/`_wperm` ACL 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 ‘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 `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 `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 |