pub trait StorageAdapter: Send + Sync {
Show 19 methods
// Required methods
fn all_schemas(
&self,
) -> impl Future<Output = Result<Vec<ClassSchema>, ParseError>> + Send;
fn upsert_schema(
&self,
schema: &ClassSchema,
) -> impl Future<Output = Result<(), ParseError>> + Send;
fn insert_schema(
&self,
schema: &ClassSchema,
) -> impl Future<Output = Result<(), ParseError>> + Send;
fn reserve_field(
&self,
class_name: &str,
field_name: &str,
field_type: &FieldType,
options: Option<&ParseMap>,
) -> impl Future<Output = Result<AddFieldOutcome, ParseError>> + Send;
fn set_field_options(
&self,
class_name: &str,
field_name: &str,
options: &ParseMap,
) -> impl Future<Output = Result<(), ParseError>> + Send;
fn set_indexes(
&self,
class_name: &str,
indexes: &ParseMap,
) -> impl Future<Output = Result<(), ParseError>> + Send;
fn set_class_permissions(
&self,
class_name: &str,
clp: Option<&ClassLevelPermissions>,
) -> impl Future<Output = Result<(), ParseError>> + Send;
fn delete_class(
&self,
schema: &ClassSchema,
) -> impl Future<Output = Result<(), ParseError>> + Send;
fn delete_fields(
&self,
schema: &ClassSchema,
fields: &[String],
) -> impl Future<Output = Result<(), ParseError>> + Send;
fn create(
&self,
schema: &ClassSchema,
row: &Row,
) -> impl Future<Output = Result<WriteResult, ParseError>> + Send;
fn upsert_one(
&self,
schema: &ClassSchema,
query: &Query,
row: &Row,
) -> impl Future<Output = Result<(), ParseError>> + Send;
fn find(
&self,
schema: &ClassSchema,
query: &Query,
options: &QueryOptions,
) -> impl Future<Output = Result<Vec<Row>, ParseError>> + Send;
fn count(
&self,
schema: &ClassSchema,
query: &Query,
) -> impl Future<Output = Result<u64, ParseError>> + Send;
fn update(
&self,
schema: &ClassSchema,
query: &Query,
update: &Update,
) -> impl Future<Output = Result<u64, ParseError>> + Send;
fn update_one_returning(
&self,
schema: &ClassSchema,
query: &Query,
update: &Update,
) -> impl Future<Output = Result<Option<Row>, ParseError>> + Send;
fn delete(
&self,
schema: &ClassSchema,
query: &Query,
) -> impl Future<Output = Result<u64, ParseError>> + Send;
fn ensure_index(
&self,
class_name: &str,
fields: &[&str],
name: Option<&str>,
unique: bool,
case_insensitive: bool,
) -> impl Future<Output = Result<(), ParseError>> + Send;
fn create_indexes(
&self,
class_name: &str,
indexes: &[SchemaIndex],
) -> impl Future<Output = Result<(), ParseError>> + Send;
fn drop_index(
&self,
class_name: &str,
name: &str,
) -> impl Future<Output = Result<(), ParseError>> + Send;
}Expand description
Storage operations.
async fn in trait, so this is not object-safe. That is deliberate for now: the server holds
one concrete adapter chosen at construction, and boxing every call to support a dyn we do
not need would cost allocations on the hot path. If a deployment ever needs to swap adapters
at runtime, add a boxed wrapper rather than degrading this.
Required Methods§
Sourcefn all_schemas(
&self,
) -> impl Future<Output = Result<Vec<ClassSchema>, ParseError>> + Send
fn all_schemas( &self, ) -> impl Future<Output = Result<Vec<ClassSchema>, ParseError>> + Send
Load every class schema. Upstream has no per-class fetch: a miss on any class triggers a
full getAllClasses, and reproducing that shape keeps the caching behavior comparable.
Sourcefn upsert_schema(
&self,
schema: &ClassSchema,
) -> impl Future<Output = Result<(), ParseError>> + Send
fn upsert_schema( &self, schema: &ClassSchema, ) -> impl Future<Output = Result<(), ParseError>> + Send
Persist a class schema, creating the class if it does not exist.
Must not clobber metadata it was not given. A field-adding write reaches here with
clp: None simply because nothing loaded one, and rewriting the whole _metadata block
from that would silently delete a class’s permissions on every ordinary save. The
implementation sets the field keys it knows about and leaves _metadata alone unless the
corresponding field on ClassSchema is Some.
Sourcefn insert_schema(
&self,
schema: &ClassSchema,
) -> impl Future<Output = Result<(), ParseError>> + Send
fn insert_schema( &self, schema: &ClassSchema, ) -> impl Future<Output = Result<(), ParseError>> + Send
Insert a class schema, failing if the class already exists.
The schema API’s create path, and it is an insert rather than an upsert for the same
reason reserve_field is a conditional update. Upstream calls insertSchema, which is
insertOne, and turns the backend’s duplicate-key error into DUPLICATE_VALUE
Class already exists. (MongoSchemaCollection.js:183-195); the caller then re-labels it
as INVALID_CLASS_NAME (SchemaController.js:861-864).
Reading the schema list and then upserting looks equivalent and is not. Two concurrent
POST /schemas for one class both pass the read and both write, so both report success and
the loser’s field types and CLP silently replace the winner’s. The class-already-exists
answer has to come from the write, because only the write is atomic.
Returns DUPLICATE_VALUE with upstream’s message when the class exists.
Sourcefn reserve_field(
&self,
class_name: &str,
field_name: &str,
field_type: &FieldType,
options: Option<&ParseMap>,
) -> impl Future<Output = Result<AddFieldOutcome, ParseError>> + Send
fn reserve_field( &self, class_name: &str, field_name: &str, field_type: &FieldType, options: Option<&ParseMap>, ) -> impl Future<Output = Result<AddFieldOutcome, ParseError>> + Send
Reserve a field type atomically, before any row is written, together with its options.
This is the fix for the concurrent first-write race 0.1.0 shipped with. Upstream issues a
conditional upsert, {_id: class, field: {$exists: false}} / $set: {field: type} /
upsert: true (MongoSchemaCollection.js:249-281), so a losing writer fails the condition
rather than overwriting the winner’s type. Any backend that cannot express a conditional
insert cannot implement Parse’s schema semantics safely, which is why this is on the trait
rather than inside the Mongo adapter.
options is part of the same conditional update, not a second write. Upstream sets the
type and _metadata.fields_options.<field> in one $set under the one $exists: false
guard (MongoSchemaCollection.js:251-269). Splitting them lets a request reserve a type and
then lose its options to a concurrent writer, which is the whole failure this method exists
to prevent, one level down.
Sourcefn set_field_options(
&self,
class_name: &str,
field_name: &str,
options: &ParseMap,
) -> impl Future<Output = Result<(), ParseError>> + Send
fn set_field_options( &self, class_name: &str, field_name: &str, options: &ParseMap, ) -> impl Future<Output = Result<(), ParseError>> + Send
Set one field’s options, for a field that already exists.
updateFieldOptions (MongoSchemaCollection.js:284-300), reached when a submitted field
matches the stored type and differs only in its options
(SchemaController.js:1174-1180). Addressed per field, never as a block: the caller
does not know what options its siblings carry and must not be able to erase them.
Sourcefn set_indexes(
&self,
class_name: &str,
indexes: &ParseMap,
) -> impl Future<Output = Result<(), ParseError>> + Send
fn set_indexes( &self, class_name: &str, indexes: &ParseMap, ) -> impl Future<Output = Result<(), ParseError>> + Send
Replace _metadata.indexes with the block the request produced.
The tail of setIndexesWithSchemaFormat (MongoStorageAdapter.js:404-408). Whole-block by
design, unlike field options: the caller computed it by merging the submitted block into the
stored one, which is the same read-modify-write upstream does.
Does not create the row. Upstream uses updateSchema, not upsertSchema, so on a class
that does not exist yet this is a no-op and the indexes reach _SCHEMA through the insert
instead.
Sourcefn set_class_permissions(
&self,
class_name: &str,
clp: Option<&ClassLevelPermissions>,
) -> impl Future<Output = Result<(), ParseError>> + Send
fn set_class_permissions( &self, class_name: &str, clp: Option<&ClassLevelPermissions>, ) -> impl Future<Output = Result<(), ParseError>> + Send
Replace _metadata.class_permissions. None removes the key, which is not the same as
storing an empty block: see ClassSchema::clp.
Sourcefn delete_class(
&self,
schema: &ClassSchema,
) -> impl Future<Output = Result<(), ParseError>> + Send
fn delete_class( &self, schema: &ClassSchema, ) -> impl Future<Output = Result<(), ParseError>> + Send
Drop a class: its rows, its schema entry and every join collection belonging to it.
Upstream refuses on a non-empty class at the REST layer (code 255), not here, so this does what it is told.
Sourcefn delete_fields(
&self,
schema: &ClassSchema,
fields: &[String],
) -> impl Future<Output = Result<(), ParseError>> + Send
fn delete_fields( &self, schema: &ClassSchema, fields: &[String], ) -> impl Future<Output = Result<(), ParseError>> + Send
Remove fields from a class: the schema entry and the column on every row.
Deliberately does not touch join collections, matching
MongoStorageAdapter.js:495-501. Dropping a Relation field leaves its join collection
in place, and a class recreated with the same field name inherits the old memberships.
That is upstream behavior and a client can observe it.
Sourcefn create(
&self,
schema: &ClassSchema,
row: &Row,
) -> impl Future<Output = Result<WriteResult, ParseError>> + Send
fn create( &self, schema: &ClassSchema, row: &Row, ) -> impl Future<Output = Result<WriteResult, ParseError>> + Send
Insert one row. object_id is generated by the caller, not the adapter, because it is
part of the Parse contract rather than a storage detail.
Sourcefn upsert_one(
&self,
schema: &ClassSchema,
query: &Query,
row: &Row,
) -> impl Future<Output = Result<(), ParseError>> + Send
fn upsert_one( &self, schema: &ClassSchema, query: &Query, row: &Row, ) -> impl Future<Output = Result<(), ParseError>> + Send
Insert a row, or do nothing if one already matches.
Exists for join tables, whose membership rows carry no objectId and must be idempotent:
adding a user to a role twice is one membership (DatabaseController.js:794-806).
Sourcefn find(
&self,
schema: &ClassSchema,
query: &Query,
options: &QueryOptions,
) -> impl Future<Output = Result<Vec<Row>, ParseError>> + Send
fn find( &self, schema: &ClassSchema, query: &Query, options: &QueryOptions, ) -> impl Future<Output = Result<Vec<Row>, ParseError>> + Send
Find rows matching the query.
Sourcefn count(
&self,
schema: &ClassSchema,
query: &Query,
) -> impl Future<Output = Result<u64, ParseError>> + Send
fn count( &self, schema: &ClassSchema, query: &Query, ) -> impl Future<Output = Result<u64, ParseError>> + Send
Count rows matching the query.
Sourcefn update(
&self,
schema: &ClassSchema,
query: &Query,
update: &Update,
) -> impl Future<Output = Result<u64, ParseError>> + Send
fn update( &self, schema: &ClassSchema, query: &Query, update: &Update, ) -> impl Future<Output = Result<u64, ParseError>> + Send
Update matching rows.
Returns how many rows matched, so a caller can distinguish “updated nothing because the
object does not exist” from “updated nothing because the ACL excluded it”. Upstream
conflates those into OBJECT_NOT_FOUND, which is the behavior to reproduce at the REST
layer, but the adapter should not throw the information away before then.
Sourcefn update_one_returning(
&self,
schema: &ClassSchema,
query: &Query,
update: &Update,
) -> impl Future<Output = Result<Option<Row>, ParseError>> + Send
fn update_one_returning( &self, schema: &ClassSchema, query: &Query, update: &Update, ) -> impl Future<Output = Result<Option<Row>, ParseError>> + Send
Update one row and return its post-image.
Needed because an update carrying an op has to tell the client the resulting value:
_sanitizeDatabaseResult reads it off the document the adapter returns
(DatabaseController.js:2129-2157), and upstream gets it from findOneAndUpdate with
returnDocument: 'after' (MongoStorageAdapter.js:660-665). Ok(None) means nothing
matched.
Sourcefn delete(
&self,
schema: &ClassSchema,
query: &Query,
) -> impl Future<Output = Result<u64, ParseError>> + Send
fn delete( &self, schema: &ClassSchema, query: &Query, ) -> impl Future<Output = Result<u64, ParseError>> + Send
Delete matching rows. Returns how many, for the same reason as update.
Sourcefn ensure_index(
&self,
class_name: &str,
fields: &[&str],
name: Option<&str>,
unique: bool,
case_insensitive: bool,
) -> impl Future<Output = Result<(), ParseError>> + Send
fn ensure_index( &self, class_name: &str, fields: &[&str], name: Option<&str>, unique: bool, case_insensitive: bool, ) -> impl Future<Output = Result<(), ParseError>> + Send
Create a unique index.
Index names are part of the contract. Both adapters recover duplicated_field by regex
over the index name, and the Mongo regex matches only auto-generated <field>_1 names, so
a differently-named index changes the error a client sees. name: None means “let the
backend auto-name it”, which is what produces username_1.
case_insensitive builds it under upstream’s collation, {locale: "en_US", strength: 2}
(MongoCollection.js:134-136). Strength 2 ignores case and normalizes equivalent Unicode
forms, and keeps diacritics significant, so Café and Cafe are different keys while a
precomposed and a decomposed Café are one.
unique is separate from case_insensitive and the two are not correlated. Upstream’s
ensureIndex never sets unique at all (MongoStorageAdapter.js:782-812), so its
case_insensitive_username is a plain collated index that exists to make the collated
uniqueness query fast. Creating it unique instead is a mixed-fleet break rather than a
stricter local choice: parse-server booting against the same database asks for the
non-unique form under the same name and gets IndexKeySpecsConflict (86), so it refuses to
start. Found exactly that way, by Gate D.
Sourcefn create_indexes(
&self,
class_name: &str,
indexes: &[SchemaIndex],
) -> impl Future<Output = Result<(), ParseError>> + Send
fn create_indexes( &self, class_name: &str, indexes: &[SchemaIndex], ) -> impl Future<Output = Result<(), ParseError>> + Send
Create named indexes from the schema API’s indexes block.
Separate from StorageAdapter::ensure_index because these are not unique, are
named by the caller rather than by the backend, and carry a caller-supplied key document
including sort direction and _p_-prefixed pointer columns.
The write to _metadata.indexes is the caller’s, and it must not happen before this
resolves (MongoStorageAdapter.js:398-408). A schema row claiming an index that was
never built is worse than no index at all on a shared database: a parse-server node reading
that row treats the index as present and will not create it either.
Sourcefn drop_index(
&self,
class_name: &str,
name: &str,
) -> impl Future<Output = Result<(), ParseError>> + Send
fn drop_index( &self, class_name: &str, name: &str, ) -> impl Future<Output = Result<(), ParseError>> + Send
Drop an index by name, for the {"__op":"Delete"} form.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".