update: updating files with detailed comments
This commit is contained in:
parent
245321849a
commit
df25885fcf
34 changed files with 3399 additions and 137 deletions
|
@ -1,3 +1,24 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/// This library provides core functionality for data management and persistence.
|
||||
///
|
||||
/// It exports several modules:
|
||||
/// - `managed`: Handles managed objects and their lifecycle.
|
||||
/// - `persistent_store`: Provides interfaces for data persistence.
|
||||
/// - `query`: Offers query building and execution capabilities.
|
||||
/// - `schema`: Defines schema-related structures and operations.
|
||||
///
|
||||
/// These modules collectively form a framework for efficient data handling,
|
||||
/// storage, and retrieval within the Protevus Platform.
|
||||
library;
|
||||
|
||||
export 'src/managed/managed.dart';
|
||||
export 'src/persistent_store/persistent_store.dart';
|
||||
export 'src/query/query.dart';
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
import 'package:meta/meta_meta.dart';
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
import 'package:protevus_database/src/managed/relationship_type.dart';
|
||||
|
||||
/// An [ArgumentError] thrown when attempting to access an invalid property while building a `Query.values`.
|
||||
///
|
||||
/// This error is thrown when attempting to access a property that is not backed by a column in the database table being inserted into.
|
||||
/// This prohibits accessing `ManagedObject` and `ManagedSet` properties, except for `ManagedObject` properties with a `Relate` annotation.
|
||||
/// For `Relate` properties, you may only set their primary key property.
|
||||
final ArgumentError _invalidValueConstruction = ArgumentError(
|
||||
"Invalid property access when building 'Query.values'. "
|
||||
"May only assign values to properties backed by a column of the table being inserted into. "
|
||||
|
@ -8,6 +22,13 @@ final ArgumentError _invalidValueConstruction = ArgumentError(
|
|||
"properties with a 'Relate' annotation. For 'Relate' properties, you may only set their "
|
||||
"primary key property.");
|
||||
|
||||
/// A concrete implementation of [ManagedBacking] that stores the values of a [ManagedObject].
|
||||
///
|
||||
/// This class is responsible for managing the actual values of a [ManagedObject]. It provides methods to get and set the
|
||||
/// values of the object's properties, and ensures that the values are valid according to the property's type.
|
||||
///
|
||||
/// When setting a value for a property, this class checks if the value is assignable to the property's type. If the value
|
||||
/// is not assignable, a [ValidationException] is thrown.
|
||||
class ManagedValueBacking extends ManagedBacking {
|
||||
@override
|
||||
Map<String, dynamic> contents = {};
|
||||
|
@ -31,6 +52,15 @@ class ManagedValueBacking extends ManagedBacking {
|
|||
}
|
||||
}
|
||||
|
||||
/// A concrete implementation of [ManagedBacking] that is designed to work with foreign key properties of a [ManagedObject].
|
||||
///
|
||||
/// This class is used when you need to create a new [ManagedObject] instance and only set its primary key property, which is
|
||||
/// typically the foreign key property in a relationship. It allows you to set the primary key property without having to create
|
||||
/// a full [ManagedObject] instance.
|
||||
///
|
||||
/// The `ManagedForeignKeyBuilderBacking` class is useful when you are building a [Query] and need to set the foreign key property
|
||||
/// of a related object, without creating the full related object. It ensures that only the primary key property can be set, and
|
||||
/// throws an [ArgumentError] if you try to set any other properties.
|
||||
class ManagedForeignKeyBuilderBacking extends ManagedBacking {
|
||||
ManagedForeignKeyBuilderBacking();
|
||||
ManagedForeignKeyBuilderBacking.from(
|
||||
|
@ -65,6 +95,17 @@ class ManagedForeignKeyBuilderBacking extends ManagedBacking {
|
|||
}
|
||||
}
|
||||
|
||||
/// A concrete implementation of [ManagedBacking] that is designed to work with [ManagedObject] instances being used in a [Query.values].
|
||||
///
|
||||
/// This class is responsible for managing the values of a [ManagedObject] instance when it is being used to build a `Query.values` object.
|
||||
/// It allows you to set the values of the object's properties, including its relationship properties, in a way that is compatible with the
|
||||
/// constraints of the `Query.values` object.
|
||||
///
|
||||
/// When setting a value for a property, this class checks the type of the property and ensures that the value being set is compatible with it.
|
||||
/// For example, if the property is a [ManagedRelationshipDescription] with a `ManagedRelationshipType.belongsTo` relationship type, this class will
|
||||
/// allow you to set the property to a [ManagedObject] instance or `null`, but not to a [ManagedSet] or other [ManagedObject] type.
|
||||
///
|
||||
/// If you attempt to set an invalid value for a property, this class will throw an [ArgumentError] with a helpful error message.
|
||||
class ManagedBuilderBacking extends ManagedBacking {
|
||||
ManagedBuilderBacking();
|
||||
ManagedBuilderBacking.from(ManagedEntity entity, ManagedBacking original) {
|
||||
|
@ -92,9 +133,20 @@ class ManagedBuilderBacking extends ManagedBacking {
|
|||
});
|
||||
}
|
||||
|
||||
/// The contents of the `ManagedValueBacking` class, which is a map that stores the values of a `ManagedObject`.
|
||||
@override
|
||||
Map<String, dynamic> contents = {};
|
||||
|
||||
/// Retrieves the value for the given property in the `ManagedBacking` instance.
|
||||
///
|
||||
/// If the property is a [ManagedRelationshipDescription] and not a `belongsTo` relationship,
|
||||
/// an [ArgumentError] is thrown with the `_invalidValueConstruction` message.
|
||||
///
|
||||
/// If the property is a [ManagedRelationshipDescription] and the key is not present in the
|
||||
/// `contents` map, a new [ManagedObject] instance is created using the `ManagedForeignKeyBuilderBacking`
|
||||
/// and stored in the `contents` map under the property name.
|
||||
///
|
||||
/// The value for the property is then returned from the `contents` map.
|
||||
@override
|
||||
dynamic valueForProperty(ManagedPropertyDescription property) {
|
||||
if (property is ManagedRelationshipDescription) {
|
||||
|
@ -111,6 +163,19 @@ class ManagedBuilderBacking extends ManagedBacking {
|
|||
return contents[property.name];
|
||||
}
|
||||
|
||||
/// Sets the value for the specified property in the `ManagedBacking` instance.
|
||||
///
|
||||
/// If the property is a [ManagedRelationshipDescription] and not a `belongsTo` relationship,
|
||||
/// an [ArgumentError] is thrown with the `_invalidValueConstruction` message.
|
||||
///
|
||||
/// If the property is a [ManagedRelationshipDescription] and the value is `null`, the
|
||||
/// value in the `contents` map is set to `null`.
|
||||
///
|
||||
/// If the property is a [ManagedRelationshipDescription] and the value is not `null`,
|
||||
/// a new [ManagedObject] instance is created using the `ManagedForeignKeyBuilderBacking`
|
||||
/// and stored in the `contents` map under the property name.
|
||||
///
|
||||
/// For all other property types, the value is simply stored in the `contents` map.
|
||||
@override
|
||||
void setValueForProperty(ManagedPropertyDescription property, dynamic value) {
|
||||
if (property is ManagedRelationshipDescription) {
|
||||
|
@ -136,6 +201,18 @@ class ManagedBuilderBacking extends ManagedBacking {
|
|||
}
|
||||
}
|
||||
|
||||
/// A concrete implementation of [ManagedBacking] that tracks the access of properties in a [ManagedObject].
|
||||
///
|
||||
/// This class is designed to monitor the access of properties in a [ManagedObject] instance. It keeps track of the
|
||||
/// [KeyPath]s that are accessed, and when a property is accessed, it creates a new object or set based on the
|
||||
/// type of the property.
|
||||
///
|
||||
/// For [ManagedRelationshipDescription] properties, it creates a new instance of the destination entity with a
|
||||
/// `ManagedAccessTrackingBacking` backing, or a [ManagedSet] for `hasMany` relationships. For [ManagedAttributeDescription]
|
||||
/// properties with a document type, it creates a [DocumentAccessTracker] object.
|
||||
///
|
||||
/// The `keyPaths` list keeps track of all the [KeyPath]s that have been accessed, and the `workingKeyPath` property
|
||||
/// keeps track of the current [KeyPath] being built.
|
||||
class ManagedAccessTrackingBacking extends ManagedBacking {
|
||||
List<KeyPath> keyPaths = [];
|
||||
KeyPath? workingKeyPath;
|
||||
|
@ -179,6 +256,16 @@ class ManagedAccessTrackingBacking extends ManagedBacking {
|
|||
}
|
||||
}
|
||||
|
||||
/// A class that tracks access to a document property in a [ManagedObject].
|
||||
///
|
||||
/// This class is used in conjunction with the [ManagedAccessTrackingBacking] class to monitor
|
||||
/// the access of document properties in a [ManagedObject] instance. When a document property
|
||||
/// is accessed, a new instance of this class is created, and the [KeyPath] that represents
|
||||
/// the access to the document property is updated.
|
||||
///
|
||||
/// The `owner` property of this class holds the [KeyPath] that represents the access to the
|
||||
/// document property. When the overridden `operator []` is called, it adds the key or index
|
||||
/// used to access the document property to the `owner` [KeyPath].
|
||||
class DocumentAccessTracker extends Document {
|
||||
DocumentAccessTracker(this.owner);
|
||||
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import 'dart:async';
|
||||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:protevus_openapi/documentable.dart';
|
||||
import 'package:protevus_database/src/managed/data_model_manager.dart' as mm;
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
|
@ -36,7 +44,7 @@ import 'package:protevus_database/src/query/query.dart';
|
|||
/// }
|
||||
/// }
|
||||
class ManagedContext implements APIComponentDocumenter {
|
||||
/// Creates an instance of [ManagedContext] from a [ManagedDataModel] and [PersistentStore].
|
||||
/// Creates a new instance of [ManagedContext] with the provided [dataModel] and [persistentStore].
|
||||
///
|
||||
/// This is the default constructor.
|
||||
///
|
||||
|
@ -47,15 +55,36 @@ class ManagedContext implements APIComponentDocumenter {
|
|||
_finalizer.attach(this, persistentStore, detach: this);
|
||||
}
|
||||
|
||||
/// Creates a child context from [parentContext].
|
||||
/// Creates a child [ManagedContext] from the provided [parentContext].
|
||||
///
|
||||
/// The created child context will share the same [persistentStore] and [dataModel]
|
||||
/// as the [parentContext]. This allows you to perform database operations within
|
||||
/// a transaction by creating a child context and executing queries on it.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// await context.transaction((transaction) async {
|
||||
/// final childContext = ManagedContext.childOf(transaction);
|
||||
/// final query = Query<MyModel>(childContext)..values.name = 'John';
|
||||
/// await query.insert();
|
||||
/// });
|
||||
ManagedContext.childOf(ManagedContext parentContext)
|
||||
: persistentStore = parentContext.persistentStore,
|
||||
dataModel = parentContext.dataModel;
|
||||
|
||||
/// A [Finalizer] that is used to automatically close the [PersistentStore] when the [ManagedContext] is destroyed.
|
||||
///
|
||||
/// This [Finalizer] is attached to the [ManagedContext] instance in the constructor, and will call the `close()` method
|
||||
/// of the [PersistentStore] when the [ManagedContext] is garbage collected or explicitly closed. This ensures that the
|
||||
/// resources associated with the [PersistentStore] are properly cleaned up when the [ManagedContext] is no longer needed.
|
||||
static final Finalizer<PersistentStore> _finalizer =
|
||||
Finalizer((store) async => store.close());
|
||||
|
||||
/// The persistent store that [Query]s on this context are executed through.
|
||||
///
|
||||
/// The [PersistentStore] is responsible for maintaining the connection to the database and
|
||||
/// executing queries on behalf of the [ManagedContext]. This property holds the instance
|
||||
/// of the persistent store that this [ManagedContext] will use to interact with the database.
|
||||
PersistentStore persistentStore;
|
||||
|
||||
/// The data model containing the [ManagedEntity]s that describe the [ManagedObject]s this instance works with.
|
||||
|
@ -78,9 +107,8 @@ class ManagedContext implements APIComponentDocumenter {
|
|||
/// [transactionBlock], roll back any changes made in the transaction, but this method will not
|
||||
/// throw.
|
||||
///
|
||||
/// TODO: the following statement is not true.
|
||||
/// Rollback takes a string but the transaction
|
||||
/// returns <T>. It would seem to be a better idea to still throw the manual Rollback
|
||||
/// returns `Future<void>`. It would seem to be a better idea to still throw the manual Rollback
|
||||
/// so the user has a consistent method of handling the rollback. We could add a property
|
||||
/// to the Rollback class 'manual' which would be used to indicate a manual rollback.
|
||||
/// For the moment I've changed the return type to Future<void> as
|
||||
|
@ -104,7 +132,7 @@ class ManagedContext implements APIComponentDocumenter {
|
|||
);
|
||||
}
|
||||
|
||||
/// Closes this context and release its underlying resources.
|
||||
/// Closes this [ManagedContext] and releases its underlying resources.
|
||||
///
|
||||
/// This method closes the connection to [persistentStore] and releases [dataModel].
|
||||
/// A context may not be reused once it has been closed.
|
||||
|
@ -114,7 +142,7 @@ class ManagedContext implements APIComponentDocumenter {
|
|||
mm.remove(dataModel!);
|
||||
}
|
||||
|
||||
/// Returns an entity for a type from [dataModel].
|
||||
/// Returns the [ManagedEntity] for the given [type] from the [dataModel].
|
||||
///
|
||||
/// See [ManagedDataModel.entityForType].
|
||||
ManagedEntity entityForType(Type type) {
|
||||
|
@ -123,7 +151,20 @@ class ManagedContext implements APIComponentDocumenter {
|
|||
|
||||
/// Inserts a single [object] into this context.
|
||||
///
|
||||
/// This method is equivalent shorthand for [Query.insert].
|
||||
/// This method is a shorthand for creating a [Query] with the provided [object] and
|
||||
/// calling [Query.insert] to insert the object into the database.
|
||||
///
|
||||
/// This method is useful when you need to insert a single object into the database.
|
||||
/// If you need to insert multiple objects, consider using the [insertObjects] method
|
||||
/// instead.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// final user = User()..name = 'John Doe';
|
||||
/// await context.insertObject(user);
|
||||
///
|
||||
/// @param object The [ManagedObject] instance to be inserted.
|
||||
/// @return A [Future] that completes with the inserted [object] when the insert operation is complete.
|
||||
Future<T> insertObject<T extends ManagedObject>(T object) {
|
||||
final query = Query<T>(this)..values = object;
|
||||
return query.insert();
|
||||
|
@ -131,8 +172,21 @@ class ManagedContext implements APIComponentDocumenter {
|
|||
|
||||
/// Inserts each object in [objects] into this context.
|
||||
///
|
||||
/// If any insertion fails, no objects will be inserted into the database and an exception
|
||||
/// is thrown.
|
||||
/// This method takes a list of [ManagedObject] instances and inserts them into the
|
||||
/// database in a single operation. If any of the insertions fail, no objects will
|
||||
/// be inserted and an exception will be thrown.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// final users = [
|
||||
/// User()..name = 'John Doe',
|
||||
/// User()..name = 'Jane Doe',
|
||||
/// ];
|
||||
/// await context.insertObjects(users);
|
||||
///
|
||||
/// @param objects A list of [ManagedObject] instances to be inserted.
|
||||
/// @return A [Future] that completes with a list of the inserted objects when the
|
||||
/// insert operation is complete.
|
||||
Future<List<T>> insertObjects<T extends ManagedObject>(
|
||||
List<T> objects,
|
||||
) async {
|
||||
|
@ -141,8 +195,23 @@ class ManagedContext implements APIComponentDocumenter {
|
|||
|
||||
/// Returns an object of type [T] from this context if it exists, otherwise returns null.
|
||||
///
|
||||
/// If [T] cannot be inferred, an error is thrown. If [identifier] is not the same type as [T]'s primary key,
|
||||
/// null is returned.
|
||||
/// This method retrieves a single [ManagedObject] of type [T] from the database based on the provided [identifier].
|
||||
/// If the object of type [T] is found in the database, it is returned. If the object is not found, `null` is returned.
|
||||
///
|
||||
/// If the type [T] cannot be inferred, an `ArgumentError` is thrown. Similarly, if the provided [identifier] is not
|
||||
/// of the same type as the primary key of the [ManagedEntity] for type [T], `null` is returned.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// final user = await context.fetchObjectWithID<User>(1);
|
||||
/// if (user != null) {
|
||||
/// print('Found user: ${user.name}');
|
||||
/// } else {
|
||||
/// print('User not found');
|
||||
/// }
|
||||
///
|
||||
/// @param identifier The value of the primary key for the object of type [T] to fetch.
|
||||
/// @return A [Future] that completes with the fetched object of type [T] if it exists, or `null` if it does not.
|
||||
Future<T?> fetchObjectWithID<T extends ManagedObject>(
|
||||
dynamic identifier,
|
||||
) async {
|
||||
|
@ -162,12 +231,22 @@ class ManagedContext implements APIComponentDocumenter {
|
|||
return query.fetchOne();
|
||||
}
|
||||
|
||||
/// Documents the components of the [ManagedContext] by delegating to the
|
||||
/// [dataModel]'s [documentComponents] method.
|
||||
///
|
||||
/// This method is part of the [APIComponentDocumenter] interface, which is
|
||||
/// implemented by [ManagedContext]. It is responsible for generating
|
||||
/// documentation for the components (such as [ManagedEntity] and
|
||||
/// [ManagedAttribute]) that are part of the data model managed by this
|
||||
/// [ManagedContext].
|
||||
///
|
||||
/// The documentation is generated and added to the provided [APIDocumentContext].
|
||||
@override
|
||||
void documentComponents(APIDocumentContext context) =>
|
||||
dataModel!.documentComponents(context);
|
||||
}
|
||||
|
||||
/// Throw this object to roll back a [ManagedContext.transaction].
|
||||
/// An exception that can be thrown to rollback a transaction in [ManagedContext.transaction].
|
||||
///
|
||||
/// When thrown in a transaction, it will cancel an in-progress transaction and rollback
|
||||
/// any changes it has made.
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:protevus_openapi/documentable.dart';
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
|
@ -12,7 +21,6 @@ import 'package:protevus_runtime/runtime.dart';
|
|||
/// all subclasses of [ManagedObject], building a [ManagedEntity] for each.
|
||||
///
|
||||
/// Most applications do not need to access instances of this type.
|
||||
///
|
||||
class ManagedDataModel extends Object implements APIComponentDocumenter {
|
||||
/// Creates an instance of [ManagedDataModel] from a list of types that extend [ManagedObject]. It is preferable
|
||||
/// to use [ManagedDataModel.fromCurrentMirrorSystem] over this method.
|
||||
|
@ -69,8 +77,23 @@ class ManagedDataModel extends Object implements APIComponentDocumenter {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns an [Iterable] of all [ManagedEntity] instances registered in this [ManagedDataModel].
|
||||
///
|
||||
/// This property provides access to the collection of all [ManagedEntity] instances that
|
||||
/// were discovered and registered during the construction of this [ManagedDataModel].
|
||||
Iterable<ManagedEntity> get entities => _entities.values;
|
||||
|
||||
/// Returns a [ManagedEntity] for a [Type].
|
||||
///
|
||||
/// [type] may be either a sub
|
||||
/// [type] may be either a subclass of [ManagedObject] or a [ManagedObject]'s table definition. For example, the following
|
||||
/// definition
|
||||
final Map<Type, ManagedEntity> _entities = {};
|
||||
|
||||
/// A map that associates table definitions to their corresponding [ManagedEntity] instances.
|
||||
///
|
||||
/// This map is used to retrieve a [ManagedEntity] instance given a table definition type,
|
||||
/// which can be useful when the type of the managed object is not known.
|
||||
final Map<String, ManagedEntity> _tableDefinitionToEntityMap = {};
|
||||
|
||||
/// Returns a [ManagedEntity] for a [Type].
|
||||
|
@ -97,9 +120,24 @@ class ManagedDataModel extends Object implements APIComponentDocumenter {
|
|||
return entity;
|
||||
}
|
||||
|
||||
/// Attempts to retrieve a [ManagedEntity] for the given [Type].
|
||||
///
|
||||
/// This method first checks the [_entities] map for a direct match on the [Type]. If no match is found,
|
||||
/// it then checks the [_tableDefinitionToEntityMap] for a match on the string representation of the [Type].
|
||||
///
|
||||
/// If a [ManagedEntity] is found, it is returned. Otherwise, `null` is returned.
|
||||
ManagedEntity? tryEntityForType(Type type) =>
|
||||
_entities[type] ?? _tableDefinitionToEntityMap[type.toString()];
|
||||
|
||||
/// Documents the components of the managed data model.
|
||||
///
|
||||
/// This method iterates over all the [ManagedEntity] instances registered in this
|
||||
/// [ManagedDataModel] and calls the `documentComponents` method on each one, passing
|
||||
/// the provided [APIDocumentContext] instance.
|
||||
///
|
||||
/// This allows each [ManagedEntity] to describe its own components, such as the
|
||||
/// database table definition and the properties of the corresponding [ManagedObject]
|
||||
/// subclass, in the context of the API documentation.
|
||||
@override
|
||||
void documentComponents(APIDocumentContext context) {
|
||||
for (final e in entities) {
|
||||
|
@ -108,7 +146,11 @@ class ManagedDataModel extends Object implements APIComponentDocumenter {
|
|||
}
|
||||
}
|
||||
|
||||
/// Thrown when a [ManagedDataModel] encounters an error.
|
||||
/// An error that is thrown when a [ManagedDataModel] encounters an issue.
|
||||
///
|
||||
/// This error is used to indicate that there was a problem during the
|
||||
/// construction or usage of a [ManagedDataModel] instance. The error
|
||||
/// message provides information about the specific issue that occurred.
|
||||
class ManagedDataModelError extends Error {
|
||||
ManagedDataModelError(this.message);
|
||||
|
||||
|
|
|
@ -1,8 +1,37 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/data_model.dart';
|
||||
import 'package:protevus_database/src/managed/entity.dart';
|
||||
|
||||
/// A map that keeps track of the number of [ManagedDataModel] instances in the system.
|
||||
Map<ManagedDataModel, int> _dataModels = {};
|
||||
|
||||
/// Finds a [ManagedEntity] for the specified [Type].
|
||||
///
|
||||
/// Searches through the [_dataModels] map to find the first [ManagedEntity] that
|
||||
/// matches the given [Type]. If no matching [ManagedEntity] is found and [orElse]
|
||||
/// is provided, the [orElse] function is called to provide a fallback
|
||||
/// [ManagedEntity]. If no [ManagedEntity] is found and [orElse] is not provided,
|
||||
/// a [StateError] is thrown.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `type`: The [Type] of the [ManagedEntity] to find.
|
||||
/// - `orElse`: An optional function that returns a fallback [ManagedEntity] if
|
||||
/// no match is found.
|
||||
///
|
||||
/// Returns:
|
||||
/// The found [ManagedEntity], or the result of calling [orElse] if provided and
|
||||
/// no match is found.
|
||||
///
|
||||
/// Throws:
|
||||
/// A [StateError] if no [ManagedEntity] is found and [orElse] is not provided.
|
||||
ManagedEntity findEntity(
|
||||
Type type, {
|
||||
ManagedEntity Function()? orElse,
|
||||
|
@ -23,10 +52,21 @@ ManagedEntity findEntity(
|
|||
return orElse();
|
||||
}
|
||||
|
||||
/// Adds a [ManagedDataModel] to the [_dataModels] map, incrementing the count if it already exists
|
||||
/// or setting the count to 1 if it's a new entry.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `dataModel`: The [ManagedDataModel] to be added to the map.
|
||||
void add(ManagedDataModel dataModel) {
|
||||
_dataModels.update(dataModel, (count) => count + 1, ifAbsent: () => 1);
|
||||
}
|
||||
|
||||
/// Removes a [ManagedDataModel] from the [_dataModels] map, decrementing the count if it already exists.
|
||||
///
|
||||
/// If the count becomes less than 1, the [ManagedDataModel] is removed from the map completely.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `dataModel`: The [ManagedDataModel] to be removed from the map.
|
||||
void remove(ManagedDataModel dataModel) {
|
||||
if (_dataModels[dataModel] != null) {
|
||||
_dataModels.update(dataModel, (count) => count - 1);
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
|
||||
/// Allows storage of unstructured data in a [ManagedObject] property.
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_openapi/documentable.dart';
|
||||
import 'package:protevus_database/src/managed/backing.dart';
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
|
@ -133,6 +142,11 @@ class ManagedEntity implements APIComponentDocumenter {
|
|||
/// This is determined by the attribute with the [primaryKey] annotation.
|
||||
late String primaryKey;
|
||||
|
||||
/// Returns the primary key attribute of this entity.
|
||||
///
|
||||
/// The primary key attribute is the [ManagedAttributeDescription] instance that represents the primary key
|
||||
/// column of the database table associated with this entity. This property retrieves that attribute
|
||||
/// by looking up the [primaryKey] property of this entity.
|
||||
ManagedAttributeDescription? get primaryKeyAttribute {
|
||||
return attributes[primaryKey];
|
||||
}
|
||||
|
@ -155,16 +169,39 @@ class ManagedEntity implements APIComponentDocumenter {
|
|||
return _tableName;
|
||||
}
|
||||
|
||||
/// The name of the table in the database that this entity maps to.
|
||||
///
|
||||
/// By default, the table will be named by the table definition, e.g., a managed object declared as so will have a [tableName] of '_User'.
|
||||
///
|
||||
/// class User extends ManagedObject<_User> implements _User {}
|
||||
/// class _User { ... }
|
||||
///
|
||||
/// You may implement the static method [tableName] on the table definition of a [ManagedObject] to return a [String] table
|
||||
/// name override this default.
|
||||
final String _tableName;
|
||||
|
||||
/// The list of default property names of this object.
|
||||
///
|
||||
/// By default, a [Query] will fetch the properties in this list. You may specify
|
||||
/// a different set of properties by setting the [Query.returningProperties] value. The default
|
||||
/// set of properties is a list of all attributes that do not have the [Column.shouldOmitByDefault] flag
|
||||
/// set in their [Column] and all [ManagedRelationshipType.belongsTo] relationships.
|
||||
///
|
||||
/// This list cannot be modified.
|
||||
List<String>? _defaultProperties;
|
||||
|
||||
/// Derived from this' [tableName].
|
||||
///
|
||||
/// This overrides the default [hashCode] implementation for the [ManagedEntity] class.
|
||||
/// The hash code is calculated based solely on the [tableName] property of the
|
||||
/// [ManagedEntity] instance. This means that two [ManagedEntity] instances will be
|
||||
/// considered equal (i.e., have the same hash code) if they have the same [tableName].
|
||||
@override
|
||||
int get hashCode {
|
||||
return tableName.hashCode;
|
||||
}
|
||||
|
||||
/// Creates a new instance of this entity's instance type.
|
||||
/// Creates a new instance of the [ManagedObject] subclass associated with this [ManagedEntity].
|
||||
///
|
||||
/// By default, the returned object will use a normal value backing map.
|
||||
/// If [backing] is non-null, it will be the backing map of the returned object.
|
||||
|
@ -176,6 +213,14 @@ class ManagedEntity implements APIComponentDocumenter {
|
|||
return (runtime.instanceOfImplementation()..entity = this) as T;
|
||||
}
|
||||
|
||||
/// Creates a new [ManagedSet] of type [T] from the provided [objects].
|
||||
///
|
||||
/// The [objects] parameter should be an [Iterable] of dynamic values that can be
|
||||
/// converted to instances of [T]. This method will use the [ManagedEntityRuntime]
|
||||
/// implementation to create the appropriate [ManagedSet] instance.
|
||||
///
|
||||
/// If the [objects] cannot be converted to instances of [T], this method will
|
||||
/// return `null`.
|
||||
ManagedSet<T>? setOf<T extends ManagedObject>(Iterable<dynamic> objects) {
|
||||
return runtime.setOfImplementation(objects) as ManagedSet<T>?;
|
||||
}
|
||||
|
@ -297,6 +342,20 @@ class ManagedEntity implements APIComponentDocumenter {
|
|||
return tracker.keyPaths;
|
||||
}
|
||||
|
||||
/// Generates an API schema object for this managed entity.
|
||||
///
|
||||
/// This method creates an [APISchemaObject] that represents the database table
|
||||
/// associated with this managed entity. The schema object includes properties
|
||||
/// for each attribute and relationship defined in the entity, excluding any
|
||||
/// transient properties or properties that are not included in the default
|
||||
/// result set.
|
||||
///
|
||||
/// The schema object's title is set to the name of the entity, and the description
|
||||
/// is set to a message indicating that no two objects may have the same value for
|
||||
/// all of the unique properties defined for this entity (if any).
|
||||
///
|
||||
/// The [APIDocumentContext] parameter is used to register the schema object
|
||||
/// with the API document context.
|
||||
APISchemaObject document(APIDocumentContext context) {
|
||||
final schemaProperties = <String, APISchemaObject>{};
|
||||
final obj = APISchemaObject.object(schemaProperties)..title = name;
|
||||
|
@ -326,11 +385,22 @@ class ManagedEntity implements APIComponentDocumenter {
|
|||
return obj;
|
||||
}
|
||||
|
||||
/// Two entities are considered equal if they have the same [tableName].
|
||||
/// Compares two [ManagedEntity] instances for equality based on their [tableName].
|
||||
///
|
||||
/// Two [ManagedEntity] instances are considered equal if they have the same [tableName].
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is ManagedEntity && tableName == other.tableName;
|
||||
|
||||
/// Provides a string representation of the [ManagedEntity] instance.
|
||||
///
|
||||
/// The string representation includes the following information:
|
||||
///
|
||||
/// - The name of the database table associated with the entity.
|
||||
/// - A list of all attribute properties defined in the entity, with their string representations.
|
||||
/// - A list of all relationship properties defined in the entity, with their string representations.
|
||||
///
|
||||
/// This method is primarily intended for debugging and logging purposes.
|
||||
@override
|
||||
String toString() {
|
||||
final buf = StringBuffer();
|
||||
|
@ -349,6 +419,20 @@ class ManagedEntity implements APIComponentDocumenter {
|
|||
return buf.toString();
|
||||
}
|
||||
|
||||
/// Generates an API schema object for this managed entity and registers it with the provided API document context.
|
||||
///
|
||||
/// This method creates an [APISchemaObject] that represents the database table
|
||||
/// associated with this managed entity. The schema object includes properties
|
||||
/// for each attribute and relationship defined in the entity, excluding any
|
||||
/// transient properties or properties that are not included in the default
|
||||
/// result set.
|
||||
///
|
||||
/// The schema object's title is set to the name of the entity, and the description
|
||||
/// is set to a message indicating that no two objects may have the same value for
|
||||
/// all of the unique properties defined for this entity (if any).
|
||||
///
|
||||
/// The [APIDocumentContext] parameter is used to register the schema object
|
||||
/// with the API document context.
|
||||
@override
|
||||
void documentComponents(APIDocumentContext context) {
|
||||
final obj = document(context);
|
||||
|
@ -356,25 +440,139 @@ class ManagedEntity implements APIComponentDocumenter {
|
|||
}
|
||||
}
|
||||
|
||||
/// Defines the runtime implementation for a [ManagedEntity].
|
||||
///
|
||||
/// The `ManagedEntityRuntime` interface provides a set of methods that are used to implement the
|
||||
/// runtime behavior of a [ManagedEntity]. This interface is used by the `ManagedEntity` class to
|
||||
/// interact with the underlying runtime implementation, which may vary depending on the compilation
|
||||
/// target (e.g., using mirrors or code generation).
|
||||
///
|
||||
/// Implementers of this interface must provide the following functionality:
|
||||
///
|
||||
/// - `finalize(ManagedDataModel dataModel)`: Perform any necessary finalization steps for the
|
||||
/// managed entity, such as setting up caches or performing other initialization tasks.
|
||||
/// - `instanceOfImplementation({ManagedBacking? backing})`: Create a new instance of the
|
||||
/// [ManagedObject] associated with the managed entity, optionally using the provided backing
|
||||
/// object.
|
||||
/// - `setOfImplementation(Iterable<dynamic> objects)`: Create a new instance of [ManagedSet] for the
|
||||
/// managed entity, using the provided objects.
|
||||
/// - `setTransientValueForKey(ManagedObject object, String key, dynamic value)`: Set a transient
|
||||
/// value for the specified key on the given [ManagedObject] instance.
|
||||
/// - `getTransientValueForKey(ManagedObject object, String? key)`: Retrieve the transient value
|
||||
/// for the specified key on the given [ManagedObject] instance.
|
||||
/// - `isValueInstanceOf(dynamic value)`: Check if the provided value is an instance of the
|
||||
/// [ManagedObject] associated with the managed entity.
|
||||
/// - `isValueListOf(dynamic value)`: Check if the provided value is a list of instances of the
|
||||
/// [ManagedObject] associated with the managed entity.
|
||||
/// - `getPropertyName(Invocation invocation, ManagedEntity entity)`: Retrieve the property name
|
||||
/// associated with the provided method invocation, given the managed entity.
|
||||
/// - `dynamicConvertFromPrimitiveValue(ManagedPropertyDescription property, dynamic value)`:
|
||||
/// Convert the provided primitive value to the appropriate type for the specified managed
|
||||
/// property description.
|
||||
abstract class ManagedEntityRuntime {
|
||||
/// Performs any necessary finalization steps for the managed entity, such as setting up caches or performing other initialization tasks.
|
||||
///
|
||||
/// This method is called by the [ManagedDataModel] to finalize the managed entity after it has been created. Implementers of this interface
|
||||
/// should use this method to perform any necessary setup or initialization tasks for the managed entity, such as building caches or
|
||||
/// preparing other data structures.
|
||||
///
|
||||
/// The [dataModel] parameter provides access to the overall [ManagedDataModel] that contains this managed entity, which may be useful for
|
||||
/// performing finalization tasks that require information about the broader data model.
|
||||
void finalize(ManagedDataModel dataModel) {}
|
||||
|
||||
/// The entity associated with this managed object.
|
||||
///
|
||||
/// This property provides access to the [ManagedEntity] instance that represents the database table
|
||||
/// associated with this [ManagedObject]. The [ManagedEntity] contains metadata about the structure of
|
||||
/// the database table, such as the names and types of its columns, and the relationships between
|
||||
/// this table and other tables.
|
||||
ManagedEntity get entity;
|
||||
|
||||
/// Creates a new instance of this entity's instance type.
|
||||
///
|
||||
/// By default, the returned object will use a normal value backing map.
|
||||
/// If [backing] is non-null, it will be the backing map of the returned object.
|
||||
ManagedObject instanceOfImplementation({ManagedBacking? backing});
|
||||
|
||||
/// Creates a new [ManagedSet] of the type associated with this managed entity from the provided [objects].
|
||||
///
|
||||
/// The [objects] parameter should be an [Iterable] of dynamic values that can be
|
||||
/// converted to instances of the [ManagedObject] type associated with this managed entity.
|
||||
/// This method will use the [ManagedEntityRuntime] implementation to create the appropriate
|
||||
/// [ManagedSet] instance.
|
||||
///
|
||||
/// If the [objects] cannot be converted to instances of the [ManagedObject] type, this
|
||||
/// method will return `null`.
|
||||
ManagedSet setOfImplementation(Iterable<dynamic> objects);
|
||||
|
||||
/// Sets a transient value for the specified key on the given [ManagedObject] instance.
|
||||
///
|
||||
/// The [object] parameter is the [ManagedObject] instance on which the transient value should be set.
|
||||
/// The [key] parameter is the string identifier for the transient value that should be set.
|
||||
/// The [value] parameter is the dynamic value that should be assigned to the transient property identified by the [key].
|
||||
void setTransientValueForKey(ManagedObject object, String key, dynamic value);
|
||||
|
||||
/// Retrieves the transient value for the specified key on the given [ManagedObject] instance.
|
||||
///
|
||||
/// The [object] parameter is the [ManagedObject] instance from which the transient value should be retrieved.
|
||||
/// The [key] parameter is the string identifier for the transient value that should be retrieved.
|
||||
/// This method returns the dynamic value associated with the transient property identified by the [key].
|
||||
/// If the [key] does not exist or is `null`, this method will return `null`.
|
||||
dynamic getTransientValueForKey(ManagedObject object, String? key);
|
||||
|
||||
/// Checks if the provided [value] is an instance of the [ManagedObject] associated with this [ManagedEntity].
|
||||
///
|
||||
/// This method is used to determine if a given value is an instance of the [ManagedObject] type that corresponds
|
||||
/// to the current [ManagedEntity]. This is useful for validating the type of values that are being used with
|
||||
/// this managed entity.
|
||||
///
|
||||
/// The [value] parameter is the dynamic value to be checked.
|
||||
///
|
||||
/// Returns `true` if the [value] is an instance of the [ManagedObject] associated with this [ManagedEntity],
|
||||
/// and `false` otherwise.
|
||||
bool isValueInstanceOf(dynamic value);
|
||||
|
||||
/// Checks if the provided [value] is a list of instances of the [ManagedObject] associated with this [ManagedEntity].
|
||||
///
|
||||
/// This method is used to determine if a given value is a list of instances of the [ManagedObject] type that corresponds
|
||||
/// to the current [ManagedEntity]. This is useful for validating the type of values that are being used with
|
||||
/// this managed entity.
|
||||
///
|
||||
/// The [value] parameter is the dynamic value to be checked.
|
||||
///
|
||||
/// Returns `true` if the [value] is a list of instances of the [ManagedObject] associated with this [ManagedEntity],
|
||||
/// and `false` otherwise.
|
||||
bool isValueListOf(dynamic value);
|
||||
|
||||
/// Retrieves the property name associated with the provided method invocation, given the managed entity.
|
||||
///
|
||||
/// This method is used to determine the name of the property that a method invocation is accessing on a
|
||||
/// [ManagedObject] instance. This information is often needed to properly handle the invocation and
|
||||
/// interact with the managed entity.
|
||||
///
|
||||
/// The [invocation] parameter is the [Invocation] object that represents the method invocation.
|
||||
/// The [entity] parameter is the [ManagedEntity] instance that the method invocation is being performed on.
|
||||
///
|
||||
/// Returns the property name associated with the provided method invocation, or `null` if the property
|
||||
/// name cannot be determined.
|
||||
String? getPropertyName(Invocation invocation, ManagedEntity entity);
|
||||
|
||||
/// Converts the provided primitive [value] to the appropriate type for the specified [property].
|
||||
///
|
||||
/// This method is used to convert a dynamic value, such as one retrieved from a database or API,
|
||||
/// into the correct type for a [ManagedPropertyDescription]. The [property] parameter specifies
|
||||
/// the type of the property that the value should be converted to.
|
||||
///
|
||||
/// The [value] parameter is the dynamic value to be converted. This method will attempt to
|
||||
/// convert the [value] to the appropriate type for the [property], based on the property's
|
||||
/// [ManagedPropertyType]. If the conversion is not possible, the method may return a value
|
||||
/// that is not strictly type-compatible with the property, but is the closest possible
|
||||
/// representation.
|
||||
///
|
||||
/// The returned value will be of a type that is compatible with the [property]'s
|
||||
/// [ManagedPropertyType]. If the conversion is not possible, the method may return a value
|
||||
/// that is not strictly type-compatible with the property, but is the closest possible
|
||||
/// representation.
|
||||
dynamic dynamicConvertFromPrimitiveValue(
|
||||
ManagedPropertyDescription property,
|
||||
dynamic value,
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
import 'package:protevus_http/src/serializable.dart';
|
||||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_http/http.dart';
|
||||
|
||||
/// An exception thrown when an ORM property validator is violated.
|
||||
///
|
||||
/// Behaves the same as [SerializableException].
|
||||
/// This exception behaves the same as [SerializableException]. It is used to
|
||||
/// indicate that a validation error has occurred, such as when a property
|
||||
/// value does not meet the expected criteria.
|
||||
class ValidationException extends SerializableException {
|
||||
ValidationException(super.errors);
|
||||
}
|
||||
|
|
|
@ -1,25 +1,149 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
|
||||
/// A class that represents a path to a property in a managed object.
|
||||
///
|
||||
/// The `KeyPath` class is used to represent a path to a property within a managed object.
|
||||
/// It provides methods to create new `KeyPath` instances by removing or adding keys to an
|
||||
/// existing `KeyPath`.
|
||||
///
|
||||
/// The `path` field is a list of `ManagedPropertyDescription` objects, which represent
|
||||
/// the individual properties that make up the path. The `dynamicElements` field is used
|
||||
/// to store any dynamic elements that are part of the path.
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```dart
|
||||
/// final keyPath = KeyPath(managedObject.property);
|
||||
/// final newKeyPath = KeyPath.byAddingKey(keyPath, managedObject.anotherProperty);
|
||||
/// ```
|
||||
class KeyPath {
|
||||
/// Constructs a new `KeyPath` instance with the given root property.
|
||||
///
|
||||
/// The `path` field of the `KeyPath` instance will be initialized with a single
|
||||
/// `ManagedPropertyDescription` object, which represents the root property.
|
||||
///
|
||||
/// This constructor is typically used as the starting point for building a `KeyPath`
|
||||
/// instance, which can then be further modified using the other constructors and
|
||||
/// methods provided by the `KeyPath` class.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final keyPath = KeyPath(managedObject.property);
|
||||
/// ```
|
||||
KeyPath(ManagedPropertyDescription? root) : path = [root];
|
||||
|
||||
/// Creates a new `KeyPath` instance by removing the first `offset` keys from the original `KeyPath`.
|
||||
///
|
||||
/// This constructor is useful when you want to create a new `KeyPath` that represents a sub-path of an existing `KeyPath`.
|
||||
///
|
||||
/// The `original` parameter is the `KeyPath` instance from which the new `KeyPath` will be derived.
|
||||
/// The `offset` parameter specifies the number of keys to remove from the beginning of the `original` `KeyPath`.
|
||||
///
|
||||
/// The resulting `KeyPath` instance will have a `path` list that contains the remaining keys, starting from the `offset`-th key.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final originalKeyPath = KeyPath(managedObject.property1).byAddingKey(managedObject.property2);
|
||||
/// final subKeyPath = KeyPath.byRemovingFirstNKeys(originalKeyPath, 1);
|
||||
/// // The `subKeyPath` will have a `path` list containing only `managedObject.property2`
|
||||
/// ```
|
||||
KeyPath.byRemovingFirstNKeys(KeyPath original, int offset)
|
||||
: path = original.path.sublist(offset);
|
||||
|
||||
/// Constructs a new `KeyPath` instance by adding a new key to the end of an existing `KeyPath`.
|
||||
///
|
||||
/// This constructor is useful when you want to create a new `KeyPath` that represents a longer path
|
||||
/// by adding a new property to the end of an existing `KeyPath`.
|
||||
///
|
||||
/// The `original` parameter is the `KeyPath` instance to which the new key will be added.
|
||||
/// The `key` parameter is the `ManagedPropertyDescription` of the new property to be added to the `KeyPath`.
|
||||
///
|
||||
/// The resulting `KeyPath` instance will have a `path` list that contains all the keys from the `original`
|
||||
/// `KeyPath`, plus the new `key` added to the end.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final originalKeyPath = KeyPath(managedObject.property1);
|
||||
/// final newKeyPath = KeyPath.byAddingKey(originalKeyPath, managedObject.property2);
|
||||
/// // The `newKeyPath` will have a `path` list containing both `managedObject.property1` and `managedObject.property2`
|
||||
/// ```
|
||||
KeyPath.byAddingKey(KeyPath original, ManagedPropertyDescription key)
|
||||
: path = List.from(original.path)..add(key);
|
||||
|
||||
/// A list of `ManagedPropertyDescription` objects that represent the individual properties
|
||||
/// that make up the path of the `KeyPath` instance. The order of the properties in the
|
||||
/// list corresponds to the order of the path.
|
||||
///
|
||||
/// This field is used to store the individual properties that make up the path of the `KeyPath`.
|
||||
/// Each `ManagedPropertyDescription` object in the list represents a single property in the path.
|
||||
/// The order of the properties in the list corresponds to the order of the path, with the first
|
||||
/// property in the path being the first element in the list, and so on.
|
||||
final List<ManagedPropertyDescription?> path;
|
||||
|
||||
/// A list of dynamic elements that are part of the key path.
|
||||
///
|
||||
/// The `dynamicElements` field is used to store any dynamic elements that are part of the `KeyPath`. This allows the `KeyPath` to represent paths that include dynamic or variable elements, in addition to the static property descriptions stored in the `path` field.
|
||||
List<dynamic>? dynamicElements;
|
||||
|
||||
/// Returns the `ManagedPropertyDescription` at the specified `index` in the `path` list.
|
||||
///
|
||||
/// This operator allows you to access the individual `ManagedPropertyDescription` objects that make up the `KeyPath` instance, using an index.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final keyPath = KeyPath(managedObject.property1).byAddingKey(managedObject.property2);
|
||||
/// final secondProperty = keyPath[1]; // Returns the `ManagedPropertyDescription` for `managedObject.property2`
|
||||
/// ```
|
||||
ManagedPropertyDescription? operator [](int index) => path[index];
|
||||
|
||||
/// Returns the number of properties in the key path.
|
||||
///
|
||||
/// This getter returns the length of the `path` list, which represents the number of
|
||||
/// properties that make up the key path. This can be useful when you need to know
|
||||
/// how many properties are in the key path, for example, when iterating over them
|
||||
/// or performing other operations that require the length of the key path.
|
||||
int get length => path.length;
|
||||
|
||||
/// Adds a new `ManagedPropertyDescription` to the end of the `path` list.
|
||||
///
|
||||
/// This method is used to add a new property description to the `KeyPath` instance.
|
||||
/// The new property description will be appended to the end of the `path` list, effectively
|
||||
/// extending the key path.
|
||||
///
|
||||
/// This can be useful when you need to create a new `KeyPath` by adding additional properties
|
||||
/// to an existing `KeyPath` instance.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final keyPath = KeyPath(managedObject.property1);
|
||||
/// keyPath.add(managedObject.property2);
|
||||
/// // The `keyPath` now represents the path "property1.property2"
|
||||
/// ```
|
||||
void add(ManagedPropertyDescription element) {
|
||||
path.add(element);
|
||||
}
|
||||
|
||||
/// Adds a dynamic element to the `dynamicElements` list.
|
||||
///
|
||||
/// This method is used to add a new dynamic element to the `dynamicElements` list of the `KeyPath` instance.
|
||||
/// The `dynamicElements` list is used to store any dynamic or variable elements that are part of the key path, in
|
||||
/// addition to the static property descriptions stored in the `path` list.
|
||||
///
|
||||
/// If the `dynamicElements` list is `null`, it will be initialized before adding the new element.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final keyPath = KeyPath(managedObject.property1);
|
||||
/// keyPath.addDynamicElement(someVariable);
|
||||
/// ```
|
||||
void addDynamicElement(dynamic element) {
|
||||
dynamicElements ??= [];
|
||||
dynamicElements!.add(element);
|
||||
|
|
|
@ -1,3 +1,35 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/// This library file serves as a central export point for various components
|
||||
/// of the Protevus Platform's data model and validation system.
|
||||
///
|
||||
/// It exports the following modules:
|
||||
/// - attributes: Likely contains attribute-related functionality
|
||||
/// - context: Probably defines context-related classes or functions
|
||||
/// - data_model: Likely contains core data model structures
|
||||
/// - document: Possibly related to document handling or representation
|
||||
/// - entity: Likely defines entity-related classes or functions
|
||||
/// - exception: Probably contains custom exception classes
|
||||
/// - object: Likely contains object-related utilities or base classes
|
||||
/// - property_description: Possibly related to describing object properties
|
||||
/// - set: Likely contains set-related functionality
|
||||
/// - type: Probably includes type-related utilities or definitions
|
||||
/// - validation/managed: Likely contains managed validation functionality
|
||||
/// - validation/metadata: Probably includes metadata-based validation
|
||||
/// - key_path: Likely related to handling key paths in data structures
|
||||
///
|
||||
/// This library file allows users to import all these components with a single
|
||||
/// import statement, simplifying the use of the Protevus Platform's core
|
||||
/// functionalities in other parts of the application.
|
||||
library;
|
||||
|
||||
export 'attributes.dart';
|
||||
export 'context.dart';
|
||||
export 'data_model.dart';
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_openapi/documentable.dart';
|
||||
import 'package:protevus_database/src/managed/backing.dart';
|
||||
import 'package:protevus_database/src/managed/data_model_manager.dart' as mm;
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
import 'package:protevus_http/src/serializable.dart';
|
||||
import 'package:protevus_http/http.dart';
|
||||
import 'package:protevus_openapi/v3.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// Instances of this class provide storage for [ManagedObject]s.
|
||||
/// An abstract class that provides storage for [ManagedObject] instances.
|
||||
///
|
||||
/// This class is primarily used internally.
|
||||
///
|
||||
|
@ -23,13 +32,25 @@ import 'package:meta/meta.dart';
|
|||
/// Conduit implements concrete subclasses of this class to provide behavior for property storage
|
||||
/// and query-building.
|
||||
abstract class ManagedBacking {
|
||||
/// Retrieve a property by its entity and name.
|
||||
/// Retrieves the value of the specified [ManagedPropertyDescription] property.
|
||||
///
|
||||
/// This method is used to get the value of a property from the [ManagedBacking] instance.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [property]: The [ManagedPropertyDescription] for the property to retrieve.
|
||||
///
|
||||
/// Returns:
|
||||
/// The value of the specified property.
|
||||
dynamic valueForProperty(ManagedPropertyDescription property);
|
||||
|
||||
/// Sets a property by its entity and name.
|
||||
/// Sets the value of the specified [ManagedPropertyDescription] property to the provided [value].
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [property]: The [ManagedPropertyDescription] of the property to be set.
|
||||
/// - [value]: The value to be set for the specified property.
|
||||
void setValueForProperty(ManagedPropertyDescription property, dynamic value);
|
||||
|
||||
/// Removes a property from this instance.
|
||||
/// Removes a property from the backing map of this [ManagedBacking] instance.
|
||||
///
|
||||
/// Use this method to use any reference of a property from this instance.
|
||||
void removeProperty(String propertyName) {
|
||||
|
@ -37,10 +58,14 @@ abstract class ManagedBacking {
|
|||
}
|
||||
|
||||
/// A map of all set values of this instance.
|
||||
///
|
||||
/// This property returns a map that contains all the properties that have been set
|
||||
/// on this instance of `ManagedBacking`. The keys in the map are the property names,
|
||||
/// and the values are the corresponding property values.
|
||||
Map<String, dynamic> get contents;
|
||||
}
|
||||
|
||||
/// An object that represents a database row.
|
||||
/// An abstract class that provides storage for [ManagedObject] instances.
|
||||
///
|
||||
/// This class must be subclassed. A subclass is declared for each table in a database. These subclasses
|
||||
/// create the data model of an application.
|
||||
|
@ -66,37 +91,82 @@ abstract class ManagedBacking {
|
|||
/// See more documentation on defining a data model at http://conduit.io/docs/db/modeling_data/
|
||||
abstract class ManagedObject<T> extends Serializable {
|
||||
/// IMPROVEMENT: Cache of entity.properties to reduce property loading time
|
||||
///
|
||||
/// This code caches the entity's properties in a `Map<String, ManagedPropertyDescription?>` to
|
||||
/// improve the performance of accessing these properties. By caching the properties, the code
|
||||
/// avoids having to load them from the `entity` object every time they are needed, which can
|
||||
/// improve the overall performance of the application.
|
||||
late Map<String, ManagedPropertyDescription?> properties = entity.properties;
|
||||
|
||||
/// Cache of entity.properties using ResponseKey name as key, in case no ResponseKey is set then default property name is used as key
|
||||
/// A cache of the `entity.properties` map, using the response key name as the key.
|
||||
///
|
||||
/// If a property does not have a response key set, the default property name is used as the key instead.
|
||||
/// This cache is used to improve the performance of accessing the property information, as it avoids having to
|
||||
/// look up the properties in the `entity.properties` map every time they are needed.
|
||||
late Map<String, ManagedPropertyDescription?> responseKeyProperties = {
|
||||
for (final key in properties.keys) mapKeyName(key): properties[key]
|
||||
};
|
||||
|
||||
/// A flag that determines whether to include a property with a null value in the output map.
|
||||
///
|
||||
/// When the `ManagedObject` has no properties or the first property's response model has `includeIfNullField` set to `true`,
|
||||
/// this flag is set to `true`, indicating that null values should be included in the output map.
|
||||
/// Otherwise, it is set to `false`, and null values will be omitted from the output map.
|
||||
late final bool modelFieldIncludeIfNull = properties.isEmpty ||
|
||||
(properties.values.first?.responseModel?.includeIfNullField ?? true);
|
||||
|
||||
/// Determines the key name to use for a property when serializing the model to a map.
|
||||
///
|
||||
/// This method first checks if the property has a response key set, and if so, uses that as the key name.
|
||||
/// If the property does not have a response key, it uses the property name.
|
||||
/// If the property name is null, it falls back to using the original property name.
|
||||
///
|
||||
/// This allows the model to control the key names used in the serialized output, which can be useful for
|
||||
/// maintaining consistent naming conventions or working with external APIs that have specific key naming requirements.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `propertyName`: The name of the property to get the key name for.
|
||||
///
|
||||
/// Returns:
|
||||
/// The key name to use for the property when serializing the model to a map.
|
||||
String mapKeyName(String propertyName) {
|
||||
final property = properties[propertyName];
|
||||
return property?.responseKey?.name ?? property?.name ?? propertyName;
|
||||
}
|
||||
|
||||
/// A flag that determines whether this class should be automatically documented.
|
||||
///
|
||||
/// If `true`, the class will be automatically documented, typically as part of an API documentation generation process.
|
||||
/// If `false`, the class will not be automatically documented, and any documentation for it must be added manually.
|
||||
static bool get shouldAutomaticallyDocument => false;
|
||||
|
||||
/// The [ManagedEntity] this instance is described by.
|
||||
///
|
||||
/// This property holds the [ManagedEntity] that describes the table definition for the managed object
|
||||
/// of type `T`. The [ManagedEntity] is used to provide metadata about the object, such as its
|
||||
/// properties, relationships, and validation rules.
|
||||
ManagedEntity entity = mm.findEntity(T);
|
||||
|
||||
/// The persistent values of this object.
|
||||
///
|
||||
/// Values stored by this object are stored in [backing]. A backing is a [Map], where each key
|
||||
/// is a property name of this object. A backing adds some access logic to storing and retrieving
|
||||
/// its key-value pairs.
|
||||
/// This property represents the persistent values of the current `ManagedObject` instance. The values are stored in a
|
||||
/// [ManagedBacking] object, which is a `Map` where the keys are property names and the values are the corresponding
|
||||
/// property values.
|
||||
///
|
||||
/// You rarely need to use [backing] directly. There are many implementations of [ManagedBacking]
|
||||
/// for fulfilling the behavior of the ORM, so you cannot rely on its behavior.
|
||||
ManagedBacking backing = ManagedValueBacking();
|
||||
|
||||
/// Retrieves a value by property name from [backing].
|
||||
///
|
||||
/// This operator overload allows you to access the value of a property on the `ManagedObject` instance
|
||||
/// using the bracket notation (`instance[propertyName]`).
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `propertyName`: The name of the property to retrieve the value for.
|
||||
///
|
||||
/// Returns:
|
||||
/// The value of the specified property, or throws an `ArgumentError` if the property does not exist on the entity.
|
||||
dynamic operator [](String propertyName) {
|
||||
final prop = properties[propertyName];
|
||||
if (prop == null) {
|
||||
|
@ -108,6 +178,16 @@ abstract class ManagedObject<T> extends Serializable {
|
|||
}
|
||||
|
||||
/// Sets a value by property name in [backing].
|
||||
///
|
||||
/// This operator overload allows you to set the value of a property on the `ManagedObject` instance
|
||||
/// using the bracket notation (`instance[propertyName] = value`).
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `propertyName`: The name of the property to set the value for.
|
||||
/// - `value`: The value to set for the specified property.
|
||||
///
|
||||
/// Throws:
|
||||
/// - `ArgumentError` if the specified `propertyName` does not exist on the entity.
|
||||
void operator []=(String? propertyName, dynamic value) {
|
||||
final prop = properties[propertyName];
|
||||
if (prop == null) {
|
||||
|
@ -120,12 +200,20 @@ abstract class ManagedObject<T> extends Serializable {
|
|||
|
||||
/// Removes a property from [backing].
|
||||
///
|
||||
/// This will remove a value from the backing map.
|
||||
/// This method removes the specified property from the backing map of the `ManagedBacking` instance.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `propertyName`: The name of the property to remove from the backing map.
|
||||
void removePropertyFromBackingMap(String propertyName) {
|
||||
backing.removeProperty(propertyName);
|
||||
}
|
||||
|
||||
/// Removes multiple properties from [backing].
|
||||
///
|
||||
/// This method removes the specified properties from the backing map of the `ManagedBacking` instance.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `propertyNames`: A list of property names to remove from the backing map.
|
||||
void removePropertiesFromBackingMap(List<String> propertyNames) {
|
||||
for (final propertyName in propertyNames) {
|
||||
backing.removeProperty(propertyName);
|
||||
|
@ -133,6 +221,15 @@ abstract class ManagedObject<T> extends Serializable {
|
|||
}
|
||||
|
||||
/// Checks whether or not a property has been set in this instances' [backing].
|
||||
///
|
||||
/// This method checks if the specified property name exists as a key in the [contents] map of the [backing] object.
|
||||
/// It returns `true` if the property has been set, and `false` otherwise.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `propertyName`: The name of the property to check for.
|
||||
///
|
||||
/// Returns:
|
||||
/// `true` if the property has been set in the [backing] object, `false` otherwise.
|
||||
bool hasValueForProperty(String propertyName) {
|
||||
return backing.contents.containsKey(propertyName);
|
||||
}
|
||||
|
@ -164,8 +261,7 @@ abstract class ManagedObject<T> extends Serializable {
|
|||
/// This method is invoked prior to validation and therefore any values modified in this method
|
||||
/// are subject to the validation behavior of this instance.
|
||||
///
|
||||
/// An example implementation would set the 'createdDate' of an object when it is first created
|
||||
///
|
||||
/// An example implementation would set the 'createdDate' of an object when it is first created:
|
||||
/// @override
|
||||
/// void willInsert() {
|
||||
/// createdDate = new DateTime.now().toUtc();
|
||||
|
@ -200,6 +296,18 @@ abstract class ManagedObject<T> extends Serializable {
|
|||
return ManagedValidator.run(this, event: forEvent);
|
||||
}
|
||||
|
||||
/// Provides dynamic handling of property access and updates.
|
||||
///
|
||||
/// This `noSuchMethod` implementation allows for dynamic access and updates to properties of the `ManagedObject`.
|
||||
///
|
||||
/// When an unknown method is called on the `ManagedObject`, this implementation will check if the method name
|
||||
/// corresponds to a property on the entity. If it does, it will return the value of the property if the method
|
||||
/// is a getter, or set the value of the property if the method is a setter.
|
||||
///
|
||||
/// If the method name does not correspond to a property, the default `NoSuchMethodError` is thrown.
|
||||
///
|
||||
/// This implementation provides a more convenient way to access and update properties compared to using the
|
||||
/// square bracket notation (`[]` and `[]=`).
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) {
|
||||
final propertyName = entity.runtime.getPropertyName(invocation, entity);
|
||||
|
@ -216,6 +324,31 @@ abstract class ManagedObject<T> extends Serializable {
|
|||
throw NoSuchMethodError.withInvocation(this, invocation);
|
||||
}
|
||||
|
||||
/// Reads the values from the provided [object] map and sets them on the [ManagedObject] instance.
|
||||
///
|
||||
/// This method iterates over the key-value pairs in the [object] map and sets the corresponding
|
||||
/// properties on the [ManagedObject] instance. It checks the following:
|
||||
///
|
||||
/// - If the key in the [object] map does not correspond to a property in the [responseKeyProperties]
|
||||
/// map, it throws a [ValidationException] with the error message "invalid input key 'key'".
|
||||
/// - If the property is marked as private (its name starts with an underscore), it throws a
|
||||
/// [ValidationException] with the error message "invalid input key 'key'".
|
||||
/// - If the property is a [ManagedAttributeDescription]:
|
||||
/// - If the property is not transient, it sets the value on the [backing] object using the
|
||||
/// [convertFromPrimitiveValue] method of the property.
|
||||
/// - If the property is transient, it checks if the property is available as input. If not, it
|
||||
/// throws a [ValidationException] with the error message "invalid input key 'key'". Otherwise,
|
||||
/// it sets the transient value on the [ManagedObject] instance using the
|
||||
/// [setTransientValueForKey] method of the [entity.runtime].
|
||||
/// - For all other properties, it sets the value on the [backing] object using the
|
||||
/// [convertFromPrimitiveValue] method of the property.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [object]: A map of the values to be set on the [ManagedObject] instance.
|
||||
///
|
||||
/// Throws:
|
||||
/// - [ValidationException] if any of the input keys are invalid or the values cannot be converted
|
||||
/// to the appropriate type.
|
||||
@override
|
||||
void readFromMap(Map<String, dynamic> object) {
|
||||
object.forEach((key, v) {
|
||||
|
@ -292,13 +425,45 @@ abstract class ManagedObject<T> extends Serializable {
|
|||
return outputMap;
|
||||
}
|
||||
|
||||
/// Generates an [APISchemaObject] that describes the schema of the managed object.
|
||||
///
|
||||
/// This method is used to generate an [APISchemaObject] that describes the schema of the managed object. The resulting
|
||||
/// schema object can be used in OpenAPI/Swagger documentation or other API documentation tools.
|
||||
///
|
||||
/// The [APIDocumentContext] parameter is used to provide contextual information about the API documentation being generated.
|
||||
/// This context is passed to the [ManagedEntity.document] method, which is responsible for generating the schema object.
|
||||
///
|
||||
/// Returns:
|
||||
/// The [APISchemaObject] that describes the schema of the managed object.
|
||||
@override
|
||||
APISchemaObject documentSchema(APIDocumentContext context) =>
|
||||
entity.document(context);
|
||||
|
||||
/// Checks if a property is private.
|
||||
///
|
||||
/// This method checks whether the given property name starts with an underscore,
|
||||
/// which is a common convention in Dart to indicate a private property.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `propertyName`: The name of the property to check.
|
||||
///
|
||||
/// Returns:
|
||||
/// `true` if the property name starts with an underscore, indicating that the
|
||||
/// property is private, and `false` otherwise.
|
||||
static bool _isPropertyPrivate(String propertyName) =>
|
||||
propertyName.startsWith("_");
|
||||
|
||||
/// Determines whether to include a property with a null value in the output map.
|
||||
///
|
||||
/// This method checks the `includeIfNull` property of the `responseKey` associated with the
|
||||
/// given `ManagedPropertyDescription`. If the `responseKey` has an `includeIfNull` value set,
|
||||
/// that value is used. Otherwise, the `modelFieldIncludeIfNull` flag is used.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `property`: The `ManagedPropertyDescription` to check for the `includeIfNull` setting.
|
||||
///
|
||||
/// Returns:
|
||||
/// `true` if a property with a null value should be included in the output map, `false` otherwise.
|
||||
bool _includeIfNull(ManagedPropertyDescription property) =>
|
||||
property.responseKey?.includeIfNull ?? modelFieldIncludeIfNull;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_openapi/documentable.dart';
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
import 'package:protevus_database/src/managed/relationship_type.dart';
|
||||
|
@ -12,6 +21,25 @@ import 'package:protevus_runtime/runtime.dart';
|
|||
/// about the property such as its name and type. Those properties are represented by concrete subclasses of this class, [ManagedRelationshipDescription]
|
||||
/// and [ManagedAttributeDescription].
|
||||
abstract class ManagedPropertyDescription {
|
||||
/// Initializes a new instance of [ManagedPropertyDescription].
|
||||
///
|
||||
/// The [ManagedPropertyDescription] class represents a property of a [ManagedObject] object. This constructor sets the basic properties of the
|
||||
/// [ManagedPropertyDescription] instance, such as the entity, name, type, declared type, uniqueness, indexing, nullability, inclusion in default result sets,
|
||||
/// autoincrement, validators, response model, and response key.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [entity]: The [ManagedEntity] that contains this property.
|
||||
/// - [name]: The identifying name of this property.
|
||||
/// - [type]: The value type of this property, indicating the Dart type and database column type.
|
||||
/// - [declaredType]: The type of the variable that this property represents.
|
||||
/// - [unique]: Whether or not this property must be unique across all instances represented by [entity]. Defaults to `false`.
|
||||
/// - [indexed]: Whether or not this property should be indexed by a [PersistentStore]. Defaults to `false`.
|
||||
/// - [nullable]: Whether or not this property can be null. Defaults to `false`.
|
||||
/// - [includedInDefaultResultSet]: Whether or not this property is returned in the default set of [Query.returningProperties]. Defaults to `true`.
|
||||
/// - [autoincrement]: Whether or not this property should use an auto-incrementing scheme. Defaults to `false`.
|
||||
/// - [validators]: A list of [ManagedValidator]s for this instance.
|
||||
/// - [responseModel]: The [ResponseModel] associated with this property.
|
||||
/// - [responseKey]: The [ResponseKey] associated with this property.
|
||||
ManagedPropertyDescription(
|
||||
this.entity,
|
||||
this.name,
|
||||
|
@ -36,44 +64,64 @@ abstract class ManagedPropertyDescription {
|
|||
}
|
||||
|
||||
/// A reference to the [ManagedEntity] that contains this property.
|
||||
///
|
||||
/// The [ManagedEntity] that this [ManagedPropertyDescription] belongs to. This property provides a way to access the entity that
|
||||
/// manages the data represented by this property.
|
||||
final ManagedEntity entity;
|
||||
|
||||
/// The value type of this property.
|
||||
///
|
||||
/// Will indicate the Dart type and database column type of this property.
|
||||
/// This property indicates the Dart type and database column type of this property. It is used to determine how the property
|
||||
/// should be stored and retrieved from the database, as well as how it should be represented in the application's data model.
|
||||
final ManagedType? type;
|
||||
|
||||
/// The identifying name of this property.
|
||||
///
|
||||
/// This field represents the name of the property being described by this [ManagedPropertyDescription] instance.
|
||||
/// The name is used to uniquely identify the property within the [ManagedEntity] that it belongs to.
|
||||
final String name;
|
||||
|
||||
/// Whether or not this property must be unique to across all instances represented by [entity].
|
||||
///
|
||||
/// Defaults to false.
|
||||
/// This property determines if the value of this property must be unique across all instances of the [ManagedObject] that this [ManagedPropertyDescription] belongs to. If set to `true`, the [PersistentStore] will ensure that no two instances have the same value for this property.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
final bool isUnique;
|
||||
|
||||
/// Whether or not this property should be indexed by a [PersistentStore].
|
||||
///
|
||||
/// Defaults to false.
|
||||
/// When set to `true`, the [PersistentStore] will create an index for this property, which can improve the performance of
|
||||
/// queries that filter or sort on this property. This is useful for properties that are frequently used in queries, but it
|
||||
/// may come at the cost of increased storage requirements and write latency.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
final bool isIndexed;
|
||||
|
||||
/// Whether or not this property can be null.
|
||||
///
|
||||
/// Defaults to false.
|
||||
/// This property determines if the value of this property can be `null` or not. If set to `true`, the [ManagedObject] that this
|
||||
/// [ManagedPropertyDescription] belongs to can have a `null` value for this property. If set to `false`, the [ManagedObject]
|
||||
/// cannot have a `null` value for this property.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
final bool isNullable;
|
||||
|
||||
/// Whether or not this property is returned in the default set of [Query.returningProperties].
|
||||
///
|
||||
/// This defaults to true. If true, when executing a [Query] that does not explicitly specify [Query.returningProperties],
|
||||
/// this property will be returned. If false, you must explicitly specify this property in [Query.returningProperties] to retrieve it from persistent storage.
|
||||
/// This defaults to `true`. If `true`, when executing a [Query] that does not explicitly specify [Query.returningProperties],
|
||||
/// this property will be returned. If `false`, you must explicitly specify this property in [Query.returningProperties] to retrieve it from persistent storage.
|
||||
final bool isIncludedInDefaultResultSet;
|
||||
|
||||
/// Whether or not this property should use an auto-incrementing scheme.
|
||||
///
|
||||
/// By default, false. When true, it signals to the [PersistentStore] that this property should automatically be assigned a value
|
||||
/// by the database.
|
||||
/// When this is set to `true`, it signals to the [PersistentStore] that this property should automatically be assigned a value
|
||||
/// by the database. This is commonly used for primary key properties that should have a unique, incrementing value for each new
|
||||
/// instance of the [ManagedObject].
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
final bool autoincrement;
|
||||
|
||||
/// Whether or not this attribute is private or not.
|
||||
/// Determines whether the current property is marked as private.
|
||||
///
|
||||
/// Private variables are prefixed with `_` (underscores). This properties are not read
|
||||
/// or written to maps and cannot be accessed from outside the class.
|
||||
|
@ -83,15 +131,44 @@ abstract class ManagedPropertyDescription {
|
|||
return name.startsWith("_");
|
||||
}
|
||||
|
||||
/// [ManagedValidator]s for this instance.
|
||||
/// The list of [ManagedValidator]s associated with this instance.
|
||||
///
|
||||
/// [ManagedValidator]s are used to validate the values of this property
|
||||
/// before they are stored in the database. The `validators` property
|
||||
/// returns a read-only list of these validators.
|
||||
List<ManagedValidator> get validators => _validators;
|
||||
|
||||
/// The list of [ManagedValidator]s associated with this instance.
|
||||
///
|
||||
/// [ManagedValidator]s are used to validate the values of this property
|
||||
/// before they are stored in the database. The `validators` property
|
||||
/// returns a read-only list of these validators.
|
||||
final List<ManagedValidator> _validators;
|
||||
|
||||
/// The [ResponseModel] associated with this property.
|
||||
///
|
||||
/// The [ResponseModel] defines the structure of the response
|
||||
/// that will be returned for this property. This allows for
|
||||
/// customization of the documentation and schema for this
|
||||
/// property, beyond the default behavior.
|
||||
final ResponseModel? responseModel;
|
||||
|
||||
/// The [ResponseKey] associated with this property.
|
||||
///
|
||||
/// The [ResponseKey] defines the key that will be used for this
|
||||
/// property in the response object. This allows for customization
|
||||
/// of the property names in the response, beyond the default
|
||||
/// behavior.
|
||||
final ResponseKey? responseKey;
|
||||
|
||||
/// Whether or not a the argument can be assigned to this property.
|
||||
/// Determines whether the provided Dart value can be assigned to this property.
|
||||
///
|
||||
/// This method checks if the given `dartValue` is compatible with the type of this property.
|
||||
/// It delegates the type checking to the `isAssignableWith` method of the [ManagedType] associated with this property.
|
||||
///
|
||||
/// Returns:
|
||||
/// - `true` if the `dartValue` can be assigned to this property.
|
||||
/// - `false` otherwise.
|
||||
bool isAssignableWith(dynamic dartValue) => type!.isAssignableWith(dartValue);
|
||||
|
||||
/// Converts a value from a more complex value into a primitive value according to this instance's definition.
|
||||
|
@ -99,23 +176,49 @@ abstract class ManagedPropertyDescription {
|
|||
/// This method takes a Dart representation of a value and converts it to something that can
|
||||
/// be used elsewhere (e.g. an HTTP body or database query). How this value is computed
|
||||
/// depends on this instance's definition.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [value]: The Dart representation of the value to be converted.
|
||||
///
|
||||
/// Returns:
|
||||
/// The converted primitive value.
|
||||
dynamic convertToPrimitiveValue(dynamic value);
|
||||
|
||||
/// Converts a value to a more complex value from a primitive value according to this instance's definition.
|
||||
///
|
||||
/// This method takes a non-Dart representation of a value (e.g. an HTTP body or database query)
|
||||
/// and turns it into a Dart representation . How this value is computed
|
||||
/// and turns it into a Dart representation. How this value is computed
|
||||
/// depends on this instance's definition.
|
||||
dynamic convertFromPrimitiveValue(dynamic value);
|
||||
|
||||
/// The type of the variable that this property represents.
|
||||
///
|
||||
/// This property represents the Dart type of the variable that the [ManagedPropertyDescription] instance
|
||||
/// is describing. It is used to ensure that the value assigned to this property is compatible with the
|
||||
/// expected type.
|
||||
final Type? declaredType;
|
||||
|
||||
/// Returns an [APISchemaObject] that represents this property.
|
||||
///
|
||||
/// Used during documentation.
|
||||
/// This method generates an [APISchemaObject] that describes the schema of this property, which can be used for API documentation.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [context]: The [APIDocumentContext] that provides information about the current documentation context.
|
||||
///
|
||||
/// Returns:
|
||||
/// An [APISchemaObject] that represents the schema of this property.
|
||||
APISchemaObject documentSchemaObject(APIDocumentContext context);
|
||||
|
||||
/// Creates a typed API schema object based on the provided [ManagedType].
|
||||
///
|
||||
/// This method generates an [APISchemaObject] that represents the schema of a property based on its
|
||||
/// [ManagedType]. The generated schema object can be used for API documentation and other purposes.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [type]: The [ManagedType] that the schema object should be generated for.
|
||||
///
|
||||
/// Returns:
|
||||
/// An [APISchemaObject] that represents the schema of the provided [ManagedType].
|
||||
static APISchemaObject _typedSchemaObject(ManagedType type) {
|
||||
switch (type.kind) {
|
||||
case ManagedPropertyType.integer:
|
||||
|
@ -157,6 +260,26 @@ abstract class ManagedPropertyDescription {
|
|||
/// Each scalar property [ManagedObject] object persists is described by an instance of [ManagedAttributeDescription]. This class
|
||||
/// adds two properties to [ManagedPropertyDescription] that are only valid for non-relationship types, [isPrimaryKey] and [defaultValue].
|
||||
class ManagedAttributeDescription extends ManagedPropertyDescription {
|
||||
/// This constructor is used to create a [ManagedAttributeDescription] instance, which represents a scalar property of a [ManagedObject].
|
||||
/// It initializes the properties of the [ManagedPropertyDescription] base class, and also sets the `isPrimaryKey` and `defaultValue` properties
|
||||
/// specific to [ManagedAttributeDescription].
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `entity`: The [ManagedEntity] that contains this property.
|
||||
/// - `name`: The identifying name of this property.
|
||||
/// - `type`: The value type of this property, indicating the Dart type and database column type.
|
||||
/// - `declaredType`: The type of the variable that this property represents.
|
||||
/// - `transientStatus`: The validity of this attribute as input, output or both.
|
||||
/// - `primaryKey`: Whether or not this attribute is the primary key for its [ManagedEntity]. Defaults to `false`.
|
||||
/// - `defaultValue`: The default value for this attribute. Defaults to `null`.
|
||||
/// - `unique`: Whether or not this property must be unique across all instances represented by `entity`. Defaults to `false`.
|
||||
/// - `indexed`: Whether or not this property should be indexed by a [PersistentStore]. Defaults to `false`.
|
||||
/// - `nullable`: Whether or not this property can be null. Defaults to `false`.
|
||||
/// - `includedInDefaultResultSet`: Whether or not this property is returned in the default set of [Query.returningProperties]. Defaults to `true`.
|
||||
/// - `autoincrement`: Whether or not this property should use an auto-incrementing scheme. Defaults to `false`.
|
||||
/// - `validators`: A list of [ManagedValidator]s for this instance. Defaults to an empty list.
|
||||
/// - `responseModel`: The [ResponseModel] associated with this property.
|
||||
/// - `responseKey`: The [ResponseKey] associated with this property.
|
||||
ManagedAttributeDescription(
|
||||
super.entity,
|
||||
super.name,
|
||||
|
@ -175,6 +298,17 @@ class ManagedAttributeDescription extends ManagedPropertyDescription {
|
|||
super.responseKey,
|
||||
}) : isPrimaryKey = primaryKey;
|
||||
|
||||
/// Initializes a new instance of [ManagedAttributeDescription] for a transient property.
|
||||
///
|
||||
/// A transient property is a property that is not backed by a database column, but is still part of the [ManagedObject] model.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `entity`: The [ManagedEntity] that contains this property.
|
||||
/// - `name`: The identifying name of this property.
|
||||
/// - `type`: The value type of this property, indicating the Dart type and database column type.
|
||||
/// - `declaredType`: The type of the variable that this property represents.
|
||||
/// - `transientStatus`: The validity of this attribute as input, output or both.
|
||||
/// - `responseKey`: The [ResponseKey] associated with this property.
|
||||
ManagedAttributeDescription.transient(
|
||||
super.entity,
|
||||
super.name,
|
||||
|
@ -193,6 +327,28 @@ class ManagedAttributeDescription extends ManagedPropertyDescription {
|
|||
validators: [],
|
||||
);
|
||||
|
||||
/// Creates a new instance of [ManagedAttributeDescription] with the provided parameters.
|
||||
///
|
||||
/// This method is a factory method that simplifies the creation of [ManagedAttributeDescription] instances.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `entity`: The [ManagedEntity] that contains this property.
|
||||
/// - `name`: The identifying name of this property.
|
||||
/// - `type`: The value type of this property, indicating the Dart type and database column type.
|
||||
/// - `transientStatus`: The validity of this attribute as input, output or both.
|
||||
/// - `primaryKey`: Whether or not this attribute is the primary key for its [ManagedEntity]. Defaults to `false`.
|
||||
/// - `defaultValue`: The default value for this attribute. Defaults to `null`.
|
||||
/// - `unique`: Whether or not this property must be unique across all instances represented by `entity`. Defaults to `false`.
|
||||
/// - `indexed`: Whether or not this property should be indexed by a [PersistentStore]. Defaults to `false`.
|
||||
/// - `nullable`: Whether or not this property can be null. Defaults to `false`.
|
||||
/// - `includedInDefaultResultSet`: Whether or not this property is returned in the default set of [Query.returningProperties]. Defaults to `true`.
|
||||
/// - `autoincrement`: Whether or not this property should use an auto-incrementing scheme. Defaults to `false`.
|
||||
/// - `validators`: A list of [ManagedValidator]s for this instance. Defaults to an empty list.
|
||||
/// - `responseKey`: The [ResponseKey] associated with this property.
|
||||
/// - `responseModel`: The [ResponseModel] associated with this property.
|
||||
///
|
||||
/// Returns:
|
||||
/// A new instance of [ManagedAttributeDescription] with the provided parameters.
|
||||
static ManagedAttributeDescription make<T>(
|
||||
ManagedEntity entity,
|
||||
String name,
|
||||
|
@ -228,25 +384,27 @@ class ManagedAttributeDescription extends ManagedPropertyDescription {
|
|||
);
|
||||
}
|
||||
|
||||
/// Whether or not this attribute is the primary key for its [ManagedEntity].
|
||||
/// Indicates whether this attribute is the primary key for its [ManagedEntity].
|
||||
///
|
||||
/// Defaults to false.
|
||||
final bool isPrimaryKey;
|
||||
|
||||
/// The default value for this attribute.
|
||||
///
|
||||
/// By default, null. This value is a String, so the underlying persistent store is responsible for parsing it. This allows for default values
|
||||
/// that aren't constant values, such as database function calls.
|
||||
/// By default, this property is `null`. This value is a `String`, so the underlying persistent store is responsible for parsing it. This allows for
|
||||
/// default values that aren't constant values, such as database function calls.
|
||||
final String? defaultValue;
|
||||
|
||||
/// Whether or not this attribute is backed directly by the database.
|
||||
/// Determines whether this attribute is backed directly by the database.
|
||||
///
|
||||
/// If [transientStatus] is non-null, this value will be true. Otherwise, the attribute is backed by a database field/column.
|
||||
bool get isTransient => transientStatus != null;
|
||||
|
||||
/// Contains lookup table for string value of an enumeration to the enumerated value.
|
||||
///
|
||||
/// Value is null when this attribute does not represent an enumerated type.
|
||||
/// This property returns a map that maps the string representation of an enumeration value
|
||||
/// to the actual enumeration value. This is used when dealing with enumerated values in
|
||||
/// the context of a [ManagedAttributeDescription].
|
||||
///
|
||||
/// If `enum Options { option1, option2 }` then this map contains:
|
||||
///
|
||||
|
@ -260,11 +418,25 @@ class ManagedAttributeDescription extends ManagedPropertyDescription {
|
|||
/// The validity of a transient attribute as input, output or both.
|
||||
///
|
||||
/// If this property is non-null, the attribute is transient (not backed by a database field/column).
|
||||
/// The [Serialize] value indicates whether the attribute is available for input, output, or both.
|
||||
final Serialize? transientStatus;
|
||||
|
||||
/// Whether or not this attribute is represented by a Dart enum.
|
||||
/// Determines whether this attribute is represented by a Dart enum.
|
||||
///
|
||||
/// If the [enumerationValueMap] property is not empty, this attribute is considered an
|
||||
/// enumerated value, meaning it is represented by a Dart enum.
|
||||
bool get isEnumeratedValue => enumerationValueMap.isNotEmpty;
|
||||
|
||||
/// Generates an [APISchemaObject] that represents the schema of this property for API documentation.
|
||||
///
|
||||
/// This method creates an [APISchemaObject] that describes the schema of this property, including
|
||||
/// information about its type, nullability, enumerations, and other metadata.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `context`: The [APIDocumentContext] that provides information about the current documentation context.
|
||||
///
|
||||
/// Returns:
|
||||
/// An [APISchemaObject] that represents the schema of this property.
|
||||
@override
|
||||
APISchemaObject documentSchemaObject(APIDocumentContext context) {
|
||||
final prop = ManagedPropertyDescription._typedSchemaObject(type!)
|
||||
|
@ -310,6 +482,23 @@ class ManagedAttributeDescription extends ManagedPropertyDescription {
|
|||
return prop;
|
||||
}
|
||||
|
||||
/// Generates a string representation of the properties of this `ManagedPropertyDescription` instance.
|
||||
///
|
||||
/// The resulting string includes information about the following properties:
|
||||
/// - `isPrimaryKey`: Whether this property is the primary key for the associated `ManagedEntity`.
|
||||
/// - `isTransient`: Whether this property is a transient property (i.e., not backed by a database column).
|
||||
/// - `autoincrement`: Whether this property uses auto-incrementing for its values.
|
||||
/// - `isUnique`: Whether this property must have unique values across all instances of the associated `ManagedEntity`.
|
||||
/// - `defaultValue`: The default value for this property, if any.
|
||||
/// - `isIndexed`: Whether this property is indexed in the database.
|
||||
/// - `isNullable`: Whether this property can have a `null` value.
|
||||
///
|
||||
/// The string representation is formatted as follows:
|
||||
/// ```
|
||||
/// - <name> | <type> | Flags: <flag1> <flag2> ... <flagN>
|
||||
/// ```
|
||||
/// where `<name>` is the name of the property, `<type>` is the type of the property, and `<flag1>`, `<flag2>`, ..., `<flagN>` are the flags
|
||||
/// corresponding to the property's characteristics (e.g., `primary_key`, `transient`, `autoincrementing`, `unique`, `defaults to <value>`, `indexed`, `nullable`, `required`).
|
||||
@override
|
||||
String toString() {
|
||||
final flagBuffer = StringBuffer();
|
||||
|
@ -340,6 +529,22 @@ class ManagedAttributeDescription extends ManagedPropertyDescription {
|
|||
return "- $name | $type | Flags: $flagBuffer";
|
||||
}
|
||||
|
||||
/// Converts a value to a more primitive value according to this instance's definition.
|
||||
///
|
||||
/// This method takes a Dart representation of a value and converts it to something that can
|
||||
/// be used elsewhere (e.g. an HTTP body or database query). The conversion depends on the
|
||||
/// type of this property.
|
||||
///
|
||||
/// For `DateTime` values, the method converts the `DateTime` to an ISO 8601 string.
|
||||
/// For enumerated values, the method converts the enum value to a string representing the enum name.
|
||||
/// For `Document` values, the method extracts the data from the `Document` object.
|
||||
/// For all other values, the method simply returns the original value.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [value]: The Dart representation of the value to be converted.
|
||||
///
|
||||
/// Returns:
|
||||
/// The converted primitive value.
|
||||
@override
|
||||
dynamic convertToPrimitiveValue(dynamic value) {
|
||||
if (value == null) {
|
||||
|
@ -359,6 +564,24 @@ class ManagedAttributeDescription extends ManagedPropertyDescription {
|
|||
return value;
|
||||
}
|
||||
|
||||
/// Converts a value from a primitive value into a more complex value according to this instance's definition.
|
||||
///
|
||||
/// This method takes a non-Dart representation of a value (e.g. an HTTP body or database query)
|
||||
/// and turns it into a Dart representation. The conversion process depends on the type of this property.
|
||||
///
|
||||
/// For `DateTime` values, the method parses the input string into a `DateTime` object.
|
||||
/// For `double` values, the method converts the input number to a `double`.
|
||||
/// For enumerated values, the method looks up the corresponding enum value using the `enumerationValueMap`.
|
||||
/// For `Document` values, the method wraps the input value in a `Document` object.
|
||||
/// For `List` and `Map` values, the method delegates the conversion to the `entity.runtime.dynamicConvertFromPrimitiveValue` method.
|
||||
///
|
||||
/// If the input value is not compatible with the expected type, a `ValidationException` is thrown.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `value`: The non-Dart representation of the value to be converted.
|
||||
///
|
||||
/// Returns:
|
||||
/// The converted Dart representation of the value.
|
||||
@override
|
||||
dynamic convertFromPrimitiveValue(dynamic value) {
|
||||
if (value == null) {
|
||||
|
@ -396,7 +619,33 @@ class ManagedAttributeDescription extends ManagedPropertyDescription {
|
|||
}
|
||||
|
||||
/// Contains information for a relationship property of a [ManagedObject].
|
||||
///
|
||||
/// The `ManagedRelationshipDescription` class represents a relationship property of a [ManagedObject]. It contains information about the
|
||||
/// destination entity, the delete rule, the relationship type, and the inverse key. This class is used to manage the data model and
|
||||
/// provide information about relationship properties.
|
||||
class ManagedRelationshipDescription extends ManagedPropertyDescription {
|
||||
/// Initializes a new instance of [ManagedRelationshipDescription].
|
||||
///
|
||||
/// This constructor creates a new instance of [ManagedRelationshipDescription], which represents a relationship property of a [ManagedObject].
|
||||
/// The constructor sets the properties of the [ManagedRelationshipDescription] instance, including the destination entity, delete rule, relationship type,
|
||||
/// inverse key, and other metadata.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `entity`: The [ManagedEntity] that contains this property.
|
||||
/// - `name`: The identifying name of this property.
|
||||
/// - `type`: The value type of this property, indicating the Dart type and database column type.
|
||||
/// - `declaredType`: The type of the variable that this property represents.
|
||||
/// - `destinationEntity`: The [ManagedEntity] that represents the destination of this relationship.
|
||||
/// - `deleteRule`: The delete rule for this relationship.
|
||||
/// - `relationshipType`: The type of relationship (e.g., belongs to, has one, has many).
|
||||
/// - `inverseKey`: The name of the [ManagedRelationshipDescription] on the `destinationEntity` that represents the inverse of this relationship.
|
||||
/// - `unique`: Whether or not this property must be unique across all instances represented by `entity`. Defaults to `false`.
|
||||
/// - `indexed`: Whether or not this property should be indexed by a [PersistentStore]. Defaults to `false`.
|
||||
/// - `nullable`: Whether or not this property can be null. Defaults to `false`.
|
||||
/// - `includedInDefaultResultSet`: Whether or not this property is returned in the default set of [Query.returningProperties]. Defaults to `true`.
|
||||
/// - `validators`: A list of [ManagedValidator]s for this instance. Defaults to an empty list.
|
||||
/// - `responseModel`: The [ResponseModel] associated with this property.
|
||||
/// - `responseKey`: The [ResponseKey] associated with this property.
|
||||
ManagedRelationshipDescription(
|
||||
super.entity,
|
||||
super.name,
|
||||
|
@ -415,6 +664,28 @@ class ManagedRelationshipDescription extends ManagedPropertyDescription {
|
|||
super.responseKey,
|
||||
});
|
||||
|
||||
/// Creates a new instance of [ManagedRelationshipDescription] with the provided parameters.
|
||||
///
|
||||
/// This method is a factory method that simplifies the creation of [ManagedRelationshipDescription] instances.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `entity`: The [ManagedEntity] that contains this property.
|
||||
/// - `name`: The identifying name of this property.
|
||||
/// - `type`: The value type of this property, indicating the Dart type and database column type.
|
||||
/// - `destinationEntity`: The [ManagedEntity] that represents the destination of this relationship.
|
||||
/// - `deleteRule`: The delete rule for this relationship.
|
||||
/// - `relationshipType`: The type of relationship (e.g., belongs to, has one, has many).
|
||||
/// - `inverseKey`: The name of the [ManagedRelationshipDescription] on the `destinationEntity` that represents the inverse of this relationship.
|
||||
/// - `unique`: Whether or not this property must be unique across all instances represented by `entity`. Defaults to `false`.
|
||||
/// - `indexed`: Whether or not this property should be indexed by a [PersistentStore]. Defaults to `false`.
|
||||
/// - `nullable`: Whether or not this property can be null. Defaults to `false`.
|
||||
/// - `includedInDefaultResultSet`: Whether or not this property is returned in the default set of [Query.returningProperties]. Defaults to `true`.
|
||||
/// - `validators`: A list of [ManagedValidator]s for this instance. Defaults to an empty list.
|
||||
/// - `responseKey`: The [ResponseKey] associated with this property.
|
||||
/// - `responseModel`: The [ResponseModel] associated with this property.
|
||||
///
|
||||
/// Returns:
|
||||
/// A new instance of [ManagedRelationshipDescription] with the provided parameters.
|
||||
static ManagedRelationshipDescription make<T>(
|
||||
ManagedEntity entity,
|
||||
String name,
|
||||
|
@ -450,26 +721,78 @@ class ManagedRelationshipDescription extends ManagedPropertyDescription {
|
|||
);
|
||||
}
|
||||
|
||||
/// The entity that this relationship's instances are represented by.
|
||||
/// The [ManagedEntity] that represents the destination of this relationship.
|
||||
///
|
||||
/// This property holds a reference to the [ManagedEntity] that describes the model
|
||||
/// that the objects on the other end of this relationship belong to. This is used
|
||||
/// to ensure that the values assigned to this relationship property are compatible
|
||||
/// with the expected model.
|
||||
final ManagedEntity destinationEntity;
|
||||
|
||||
/// The delete rule for this relationship.
|
||||
///
|
||||
/// The delete rule determines what happens to the related objects when the object
|
||||
/// containing this relationship is deleted. The possible values are:
|
||||
///
|
||||
/// - `DeleteRule.cascade`: When the object is deleted, all related objects are also deleted.
|
||||
/// - `DeleteRule.restrict`: When the object is deleted, the operation will fail if there are any related objects.
|
||||
/// - `DeleteRule.nullify`: When the object is deleted, the foreign key values in the related objects will be set to `null`.
|
||||
/// - `DeleteRule.setDefault`: When the object is deleted, the foreign key values in the related objects will be set to their default values.
|
||||
final DeleteRule? deleteRule;
|
||||
|
||||
/// The type of relationship.
|
||||
/// The type of relationship represented by this [ManagedRelationshipDescription].
|
||||
///
|
||||
/// The relationship type can be one of the following:
|
||||
/// - `ManagedRelationshipType.belongsTo`: This property represents a "belongs to" relationship, where the object containing this property
|
||||
/// belongs to another object.
|
||||
/// - `ManagedRelationshipType.hasOne`: This property represents a "has one" relationship, where the object containing this property
|
||||
/// has a single related object.
|
||||
/// - `ManagedRelationshipType.hasMany`: This property represents a "has many" relationship, where the object containing this property
|
||||
/// has a set of related objects.
|
||||
final ManagedRelationshipType relationshipType;
|
||||
|
||||
/// The name of the [ManagedRelationshipDescription] on [destinationEntity] that represents the inverse of this relationship.
|
||||
/// The [ManagedRelationshipDescription] on [destinationEntity] that represents the inverse of this relationship.
|
||||
///
|
||||
/// This property holds the name of the [ManagedRelationshipDescription] on the [destinationEntity] that represents the inverse
|
||||
/// of this relationship. This information is used to ensure that the relationships between objects are properly defined and
|
||||
/// navigable in both directions.
|
||||
final String inverseKey;
|
||||
|
||||
/// The [ManagedRelationshipDescription] on [destinationEntity] that represents the inverse of this relationship.
|
||||
/// Gets the [ManagedRelationshipDescription] on [destinationEntity] that represents the inverse of this relationship.
|
||||
///
|
||||
/// This property returns the [ManagedRelationshipDescription] instance on the [destinationEntity] that represents the
|
||||
/// inverse of the current relationship. The inverse relationship is specified by the [inverseKey] property.
|
||||
///
|
||||
/// This method is used to navigate the relationship in the opposite direction, allowing you to access the related
|
||||
/// objects from the other side of the relationship.
|
||||
///
|
||||
/// Returns:
|
||||
/// The [ManagedRelationshipDescription] that represents the inverse of this relationship, or `null` if no inverse
|
||||
/// relationship is defined.
|
||||
ManagedRelationshipDescription? get inverse =>
|
||||
destinationEntity.relationships[inverseKey];
|
||||
|
||||
/// Whether or not this relationship is on the belonging side.
|
||||
/// Indicates whether this relationship is on the belonging side of the relationship.
|
||||
///
|
||||
/// This property returns `true` if the `relationshipType` of this `ManagedRelationshipDescription` is
|
||||
/// `ManagedRelationshipType.belongsTo`, which means that the object containing this property "belongs to"
|
||||
/// another object. If the `relationshipType` is not `belongsTo`, this property returns `false`.
|
||||
bool get isBelongsTo => relationshipType == ManagedRelationshipType.belongsTo;
|
||||
|
||||
/// Whether or not a the argument can be assigned to this property.
|
||||
/// Determines whether the provided Dart value can be assigned to this property.
|
||||
///
|
||||
/// This method checks if the given `dartValue` is compatible with the type of this property.
|
||||
/// For relationships with a 'has many' type, the method checks if the `dartValue` is a list of
|
||||
/// [ManagedObject] instances that belong to the destination entity. For other relationship
|
||||
/// types, the method checks if the `dartValue` is a [ManagedObject] instance that belongs
|
||||
/// to the destination entity.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `dartValue`: The Dart value to be checked for assignment compatibility.
|
||||
///
|
||||
/// Returns:
|
||||
/// - `true` if the `dartValue` can be assigned to this property.
|
||||
/// - `false` otherwise.
|
||||
@override
|
||||
bool isAssignableWith(dynamic dartValue) {
|
||||
if (relationshipType == ManagedRelationshipType.hasMany) {
|
||||
|
@ -478,6 +801,31 @@ class ManagedRelationshipDescription extends ManagedPropertyDescription {
|
|||
return destinationEntity.runtime.isValueInstanceOf(dartValue);
|
||||
}
|
||||
|
||||
/// Converts a value to a more primitive value according to this instance's definition.
|
||||
///
|
||||
/// This method takes a Dart representation of a value and converts it to something that can
|
||||
/// be used elsewhere (e.g. an HTTP body or database query). The conversion process depends
|
||||
/// on the type of the relationship.
|
||||
///
|
||||
/// For relationship properties with a "has many" type, the method converts the `ManagedSet`
|
||||
/// instance to a list of maps, where each map represents the associated `ManagedObject`
|
||||
/// instances.
|
||||
///
|
||||
/// For relationship properties with a "belongs to" type, the method checks if only the
|
||||
/// primary key of the associated `ManagedObject` is being fetched. If so, it returns a
|
||||
/// map containing only the primary key value. Otherwise, it returns the full map
|
||||
/// representation of the associated `ManagedObject`.
|
||||
///
|
||||
/// If the provided `value` is `null`, the method returns `null`.
|
||||
///
|
||||
/// If the provided `value` is not a `ManagedSet` or `ManagedObject`, the method throws a
|
||||
/// `StateError` with a message indicating the invalid relationship assignment.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `value`: The Dart representation of the value to be converted.
|
||||
///
|
||||
/// Returns:
|
||||
/// The converted primitive value.
|
||||
@override
|
||||
dynamic convertToPrimitiveValue(dynamic value) {
|
||||
if (value is ManagedSet) {
|
||||
|
@ -504,6 +852,26 @@ class ManagedRelationshipDescription extends ManagedPropertyDescription {
|
|||
);
|
||||
}
|
||||
|
||||
/// Converts a value from a primitive value into a more complex value according to this instance's definition.
|
||||
///
|
||||
/// This method takes a non-Dart representation of a value (e.g. an HTTP body or database query)
|
||||
/// and turns it into a Dart representation. The conversion process depends on the type of the relationship.
|
||||
///
|
||||
/// For relationship properties with a "belongs to" or "has one" type, the method creates a new instance of the
|
||||
/// [ManagedObject] associated with the destination entity, and populates it with the data from the provided map.
|
||||
///
|
||||
/// For relationship properties with a "has many" type, the method creates a [ManagedSet] instance, and populates
|
||||
/// it with new [ManagedObject] instances created from the provided list of maps.
|
||||
///
|
||||
/// If the input value is `null`, the method returns `null`.
|
||||
///
|
||||
/// If the input value is not a map or list, as expected for the relationship type, a [ValidationException] is thrown.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `value`: The non-Dart representation of the value to be converted.
|
||||
///
|
||||
/// Returns:
|
||||
/// The converted Dart representation of the value.
|
||||
@override
|
||||
dynamic convertFromPrimitiveValue(dynamic value) {
|
||||
if (value == null) {
|
||||
|
@ -538,6 +906,17 @@ class ManagedRelationshipDescription extends ManagedPropertyDescription {
|
|||
return destinationEntity.setOf(value.map(instantiator));
|
||||
}
|
||||
|
||||
/// Generates an [APISchemaObject] that represents the schema of this relationship property for API documentation.
|
||||
///
|
||||
/// This method creates an [APISchemaObject] that describes the schema of this relationship property, including
|
||||
/// information about the type of relationship (hasMany, hasOne, or belongsTo), the related object schema, and
|
||||
/// whether the property is read-only and nullable.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `context`: The [APIDocumentContext] that provides information about the current documentation context.
|
||||
///
|
||||
/// Returns:
|
||||
/// An [APISchemaObject] that represents the schema of this relationship property.
|
||||
@override
|
||||
APISchemaObject documentSchemaObject(APIDocumentContext context) {
|
||||
final relatedType =
|
||||
|
@ -560,6 +939,18 @@ class ManagedRelationshipDescription extends ManagedPropertyDescription {
|
|||
..title = name;
|
||||
}
|
||||
|
||||
/// Generates a string representation of the properties of this `ManagedRelationshipDescription` instance.
|
||||
///
|
||||
/// The resulting string includes information about the following properties:
|
||||
/// - `name`: The identifying name of this property.
|
||||
/// - `destinationEntity`: The name of the `ManagedEntity` that represents the destination of this relationship.
|
||||
/// - `relationshipType`: The type of relationship (e.g., `belongs to`, `has one`, `has many`).
|
||||
/// - `inverseKey`: The name of the `ManagedRelationshipDescription` on the `destinationEntity` that represents the inverse of this relationship.
|
||||
///
|
||||
/// The string representation is formatted as follows:
|
||||
/// ```
|
||||
/// - <name> -> '<destinationEntity.name>' | Type: <relTypeString> | Inverse: <inverseKey>
|
||||
/// ```
|
||||
@override
|
||||
String toString() {
|
||||
var relTypeString = "has-one";
|
||||
|
|
|
@ -1,2 +1,18 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/// The possible database relationships.
|
||||
///
|
||||
/// This enum represents the different types of relationships that can exist between
|
||||
/// database entities. The available relationship types are:
|
||||
///
|
||||
/// - `hasOne`: A one-to-one relationship, where one entity has exactly one related entity.
|
||||
/// - `hasMany`: A one-to-many relationship, where one entity can have multiple related entities.
|
||||
/// - `belongsTo`: A many-to-one relationship, where multiple entities can belong to a single parent entity.
|
||||
enum ManagedRelationshipType { hasOne, hasMany, belongsTo }
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import 'dart:collection';
|
||||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'dart:collection';
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
|
||||
/// Instances of this type contain zero or more instances of [ManagedObject] and represent has-many relationships.
|
||||
|
@ -22,48 +30,78 @@ import 'package:protevus_database/src/managed/managed.dart';
|
|||
class ManagedSet<InstanceType extends ManagedObject> extends Object
|
||||
with ListMixin<InstanceType> {
|
||||
/// Creates an empty [ManagedSet].
|
||||
///
|
||||
/// This constructor initializes a new [ManagedSet] instance with an empty internal list.
|
||||
ManagedSet() {
|
||||
_innerValues = [];
|
||||
}
|
||||
|
||||
/// Creates a [ManagedSet] from an [Iterable] of [InstanceType]s.
|
||||
///
|
||||
/// This constructor initializes a new [ManagedSet] instance with the elements of the provided [Iterable].
|
||||
ManagedSet.from(Iterable<InstanceType> items) {
|
||||
_innerValues = items.toList();
|
||||
}
|
||||
|
||||
/// Creates a [ManagedSet] from an [Iterable] of [dynamic]s.
|
||||
///
|
||||
/// This constructor initializes a new [ManagedSet] instance with the elements of the provided [Iterable] of [dynamic]s.
|
||||
/// The elements are converted to the appropriate [InstanceType] using [List.from].
|
||||
ManagedSet.fromDynamic(Iterable<dynamic> items) {
|
||||
_innerValues = List<InstanceType>.from(items);
|
||||
}
|
||||
|
||||
/// The internal list that stores the elements of this [ManagedSet].
|
||||
late final List<InstanceType> _innerValues;
|
||||
|
||||
/// The number of elements in this set.
|
||||
/// The number of elements in this [ManagedSet].
|
||||
///
|
||||
/// This property returns the number of elements in the internal list that stores the elements of this [ManagedSet].
|
||||
@override
|
||||
int get length => _innerValues.length;
|
||||
|
||||
/// Sets the length of the internal list that stores the elements of this [ManagedSet].
|
||||
///
|
||||
/// This setter allows you to change the length of the internal list that stores the elements of this [ManagedSet].
|
||||
/// If the new length is greater than the current length, the list is extended and the new elements are initialized to `null`.
|
||||
/// If the new length is less than the current length, the list is truncated to the new length.
|
||||
@override
|
||||
set length(int newLength) {
|
||||
_innerValues.length = newLength;
|
||||
}
|
||||
|
||||
/// Adds an [InstanceType] to this set.
|
||||
/// Adds an [InstanceType] object to this [ManagedSet].
|
||||
///
|
||||
/// This method adds the provided [InstanceType] object to the internal list of this [ManagedSet].
|
||||
/// The length of the [ManagedSet] is increased by 1, and the new element is appended to the end of the list.
|
||||
@override
|
||||
void add(InstanceType item) {
|
||||
_innerValues.add(item);
|
||||
}
|
||||
|
||||
/// Adds an [Iterable] of [InstanceType] to this set.
|
||||
/// Adds all the elements of the provided [Iterable] of [InstanceType] to this [ManagedSet].
|
||||
///
|
||||
/// This method adds all the elements of the provided [Iterable] to the internal list of this [ManagedSet].
|
||||
/// The length of the [ManagedSet] is increased by the number of elements in the [Iterable], and the new elements
|
||||
/// are appended to the end of the list.
|
||||
@override
|
||||
void addAll(Iterable<InstanceType> items) {
|
||||
_innerValues.addAll(items);
|
||||
}
|
||||
|
||||
/// Retrieves an [InstanceType] from this set by an index.
|
||||
///
|
||||
/// This overloaded index operator allows you to access the elements of the internal list
|
||||
/// that stores the elements of this [ManagedSet] using an integer index. The element
|
||||
/// at the specified index is returned.
|
||||
@override
|
||||
InstanceType operator [](int index) => _innerValues[index];
|
||||
|
||||
/// Set an [InstanceType] in this set by an index.
|
||||
/// Sets the [InstanceType] object at the specified [index] in this [ManagedSet].
|
||||
///
|
||||
/// This overloaded index assignment operator allows you to assign a new [InstanceType] object to the
|
||||
/// element at the specified [index] in the internal list that stores the elements of this [ManagedSet].
|
||||
/// If the [index] is out of bounds, an [RangeError] will be thrown.
|
||||
@override
|
||||
void operator []=(int index, InstanceType value) {
|
||||
_innerValues[index] = value;
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
|
||||
/// Possible data types for [ManagedEntity] attributes.
|
||||
///
|
||||
/// This enum represents the different data types that can be used for attributes in a [ManagedEntity].
|
||||
/// Each enum value corresponds to a specific Dart data type that will be used to represent the attribute.
|
||||
enum ManagedPropertyType {
|
||||
/// Represented by instances of [int].
|
||||
integer,
|
||||
|
@ -30,13 +42,27 @@ enum ManagedPropertyType {
|
|||
document
|
||||
}
|
||||
|
||||
/// Complex type storage for [ManagedEntity] attributes.
|
||||
/// Represents complex data types for attributes in a [ManagedEntity].
|
||||
///
|
||||
/// This class provides a way to represent complex data types, such as maps, lists, and enumerations, that can be used as
|
||||
/// attributes in a [ManagedEntity]. It encapsulates information about the type, including the primitive kind, the type
|
||||
/// of elements in the case of collections, and whether the type is an enumeration.
|
||||
///
|
||||
/// The [ManagedType] class is used internally by the Protevus database management system to handle the storage and
|
||||
/// retrieval of complex data types in the database.
|
||||
class ManagedType {
|
||||
/// Creates a new instance.
|
||||
/// Creates a new instance of [ManagedType] with the provided parameters.
|
||||
///
|
||||
/// [type] must be representable by [ManagedPropertyType].
|
||||
ManagedType(this.type, this.kind, this.elements, this.enumerationMap);
|
||||
|
||||
/// Creates a new instance of [ManagedType] with the provided parameters.
|
||||
///
|
||||
/// [kind] is the primitive type of the managed property.
|
||||
/// [elements] is the type of the elements in the case of a collection (map or list) property.
|
||||
/// [enumerationMap] is a map of the enum options and their corresponding Dart enum types, in the case of an enumerated property.
|
||||
///
|
||||
/// This method is a convenience constructor for creating [ManagedType] instances with the appropriate parameters.
|
||||
static ManagedType make<T>(
|
||||
ManagedPropertyType kind,
|
||||
ManagedType? elements,
|
||||
|
@ -47,25 +73,48 @@ class ManagedType {
|
|||
|
||||
/// The primitive kind of this type.
|
||||
///
|
||||
/// All types have a kind. If kind is a map or list, it will also have [elements].
|
||||
/// All types have a kind. If [kind] is a map or list, it will also have [elements] to specify the type of the map keys or list elements.
|
||||
final ManagedPropertyType kind;
|
||||
|
||||
/// The primitive kind of each element of this type.
|
||||
/// The type of the elements in this managed property.
|
||||
///
|
||||
/// If [kind] is a collection (map or list), this value stores the type of each element in the collection.
|
||||
/// Keys of map types are always [String].
|
||||
final ManagedType? elements;
|
||||
|
||||
/// Dart representation of this type.
|
||||
/// The Dart type represented by this [ManagedType] instance.
|
||||
final Type type;
|
||||
|
||||
/// Whether this is an enum type.
|
||||
/// Whether this [ManagedType] instance represents an enumerated type.
|
||||
///
|
||||
/// This property returns `true` if the `enumerationMap` property is not empty, indicating that this type represents an enumeration. Otherwise, it returns `false`.
|
||||
bool get isEnumerated => enumerationMap.isNotEmpty;
|
||||
|
||||
/// For enumerated types, this is a map of the name of the option to its Dart enum type.
|
||||
///
|
||||
/// This property provides a way to associate the string representation of an enumeration value with its corresponding
|
||||
/// Dart enum type. It is used in the context of a [ManagedType] instance to represent an enumerated property in a
|
||||
/// [ManagedEntity].
|
||||
///
|
||||
/// The keys of this map are the string representations of the enum options, and the values are the corresponding
|
||||
/// Dart enum types.
|
||||
final Map<String, dynamic> enumerationMap;
|
||||
|
||||
/// Whether [dartValue] can be assigned to properties with this type.
|
||||
/// Checks whether the provided [dartValue] can be assigned to properties with this [ManagedType].
|
||||
///
|
||||
/// This method examines the [kind] of the [ManagedType] and determines whether the provided [dartValue] is compatible
|
||||
/// with the expected data type.
|
||||
///
|
||||
/// If the [dartValue] is `null`, this method will return `true`, as null can be assigned to any property.
|
||||
///
|
||||
/// For each specific [ManagedPropertyType], the method checks the type of the [dartValue] and returns `true` if it
|
||||
/// matches the expected type, and `false` otherwise.
|
||||
///
|
||||
/// For [ManagedPropertyType.string], if the [enumerationMap] is not empty, the method checks whether the [dartValue]
|
||||
/// is one of the enum values in the map.
|
||||
///
|
||||
/// @param dartValue The value to be checked for assignment compatibility.
|
||||
/// @return `true` if the [dartValue] can be assigned to properties with this [ManagedType], `false` otherwise.
|
||||
bool isAssignableWith(dynamic dartValue) {
|
||||
if (dartValue == null) {
|
||||
return true;
|
||||
|
@ -98,31 +147,90 @@ class ManagedType {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a string representation of the [ManagedPropertyType] instance.
|
||||
///
|
||||
/// The string representation is simply the name of the [ManagedPropertyType] enum value.
|
||||
/// This method is useful for logging or debugging purposes, as it provides a human-readable
|
||||
/// representation of the property type.
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```dart
|
||||
/// ManagedPropertyType type = ManagedPropertyType.integer;
|
||||
/// print(type.toString()); // Output: "integer"
|
||||
/// ```
|
||||
@override
|
||||
String toString() {
|
||||
return "$kind";
|
||||
}
|
||||
|
||||
/// Returns a list of Dart types that are supported by the Protevus database management system.
|
||||
///
|
||||
/// The supported Dart types are:
|
||||
/// - `String`: Represents a string of text.
|
||||
/// - `DateTime`: Represents a specific date and time.
|
||||
/// - `bool`: Represents a boolean value (true or false).
|
||||
/// - `int`: Represents an integer number.
|
||||
/// - `double`: Represents a floating-point number.
|
||||
/// - `Document`: Represents a complex data structure that can be stored in the database.
|
||||
///
|
||||
/// This list of supported types is used internally by the Protevus database management system to ensure that
|
||||
/// the data being stored in the database is compatible with the expected data types.
|
||||
static List<Type> get supportedDartTypes {
|
||||
return [String, DateTime, bool, int, double, Document];
|
||||
}
|
||||
|
||||
/// Returns the [ManagedPropertyType] for integer properties.
|
||||
///
|
||||
/// This property provides a convenient way to access the [ManagedPropertyType.integer] value,
|
||||
/// which represents integer properties in a [ManagedEntity].
|
||||
static ManagedPropertyType get integer => ManagedPropertyType.integer;
|
||||
|
||||
/// Returns the [ManagedPropertyType] for big integer properties.
|
||||
///
|
||||
/// This property provides a convenient way to access the [ManagedPropertyType.bigInteger] value,
|
||||
/// which represents big integer properties in a [ManagedEntity].
|
||||
static ManagedPropertyType get bigInteger => ManagedPropertyType.bigInteger;
|
||||
|
||||
/// Returns the [ManagedPropertyType] for string properties.
|
||||
///
|
||||
/// This property provides a convenient way to access the [ManagedPropertyType.string] value,
|
||||
/// which represents string properties in a [ManagedEntity].
|
||||
static ManagedPropertyType get string => ManagedPropertyType.string;
|
||||
|
||||
/// Returns the [ManagedPropertyType] for datetime properties.
|
||||
///
|
||||
/// This property provides a convenient way to access the [ManagedPropertyType.datetime] value,
|
||||
/// which represents datetime properties in a [ManagedEntity].
|
||||
static ManagedPropertyType get datetime => ManagedPropertyType.datetime;
|
||||
|
||||
/// Returns the [ManagedPropertyType] for boolean properties.
|
||||
///
|
||||
/// This property provides a convenient way to access the [ManagedPropertyType.boolean] value,
|
||||
/// which represents boolean properties in a [ManagedEntity].
|
||||
static ManagedPropertyType get boolean => ManagedPropertyType.boolean;
|
||||
|
||||
/// Returns the [ManagedPropertyType] for double precision properties.
|
||||
///
|
||||
/// This property provides a convenient way to access the [ManagedPropertyType.doublePrecision] value,
|
||||
/// which represents double precision properties in a [ManagedEntity].
|
||||
static ManagedPropertyType get doublePrecision =>
|
||||
ManagedPropertyType.doublePrecision;
|
||||
|
||||
/// Returns the [ManagedPropertyType] for map properties.
|
||||
///
|
||||
/// This property provides a convenient way to access the [ManagedPropertyType.map] value,
|
||||
/// which represents map properties in a [ManagedEntity].
|
||||
static ManagedPropertyType get map => ManagedPropertyType.map;
|
||||
|
||||
/// Returns the [ManagedPropertyType] for list properties.
|
||||
///
|
||||
/// This property provides a convenient way to access the [ManagedPropertyType.list] value,
|
||||
/// which represents list properties in a [ManagedEntity].
|
||||
static ManagedPropertyType get list => ManagedPropertyType.list;
|
||||
|
||||
/// Returns the [ManagedPropertyType] for document properties.
|
||||
///
|
||||
/// This property provides a convenient way to access the [ManagedPropertyType.document] value,
|
||||
/// which represents document properties in a [ManagedEntity].
|
||||
static ManagedPropertyType get document => ManagedPropertyType.document;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,31 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
|
||||
/// Represents the different types of validations that can be performed on an input.
|
||||
///
|
||||
/// - `regex`: Validate the input using a regular expression pattern.
|
||||
/// - `comparison`: Validate the input using a comparison operator and a value.
|
||||
/// - `length`: Validate the length of the input.
|
||||
/// - `present`: Ensure the input is not null or empty.
|
||||
/// - `absent`: Ensure the input is null or empty.
|
||||
/// - `oneOf`: Ensure the input is one of the specified values.
|
||||
enum ValidateType { regex, comparison, length, present, absent, oneOf }
|
||||
|
||||
/// Represents the different comparison operators that can be used in a validation expression.
|
||||
///
|
||||
/// - `equalTo`: Ensures the input is equal to the specified value.
|
||||
/// - `lessThan`: Ensures the input is less than the specified value.
|
||||
/// - `lessThanEqualTo`: Ensures the input is less than or equal to the specified value.
|
||||
/// - `greaterThan`: Ensures the input is greater than the specified value.
|
||||
/// - `greaterThanEqualTo`: Ensures the input is greater than or equal to the specified value.
|
||||
enum ValidationOperator {
|
||||
equalTo,
|
||||
lessThan,
|
||||
|
@ -10,15 +34,55 @@ enum ValidationOperator {
|
|||
greaterThanEqualTo
|
||||
}
|
||||
|
||||
/// Represents a validation expression that can be used to validate an input value.
|
||||
///
|
||||
/// The `ValidationExpression` class has two properties:
|
||||
///
|
||||
/// - `operator`: The comparison operator to be used in the validation.
|
||||
/// - `value`: The value to be compared against the input.
|
||||
///
|
||||
/// The `compare` method is used to perform the validation and add any errors to the provided `ValidationContext`.
|
||||
class ValidationExpression {
|
||||
/// Initializes a new instance of the [ValidationExpression] class.
|
||||
///
|
||||
/// The [operator] parameter specifies the comparison operator to be used in the validation.
|
||||
/// The [value] parameter specifies the value to be compared against the input.
|
||||
ValidationExpression(this.operator, this.value);
|
||||
|
||||
/// The comparison operator to be used in the validation.
|
||||
final ValidationOperator operator;
|
||||
|
||||
/// The value to be compared against the input during the validation process.
|
||||
dynamic value;
|
||||
|
||||
/// Compares the provided input value against the value specified in the [ValidationExpression].
|
||||
///
|
||||
/// The comparison is performed based on the [ValidationOperator] specified in the [ValidationExpression].
|
||||
/// If the comparison fails, an error message is added to the provided [ValidationContext].
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [context]: The [ValidationContext] to which any errors will be added.
|
||||
/// - [input]: The value to be compared against the [ValidationExpression] value.
|
||||
///
|
||||
/// Throws:
|
||||
/// - [ClassCastException]: If the [value] property of the [ValidationExpression] is not a [Comparable].
|
||||
void compare(ValidationContext context, dynamic input) {
|
||||
/// Converts the [value] property of the [ValidationExpression] to a [Comparable] type, or sets it to `null` if the conversion fails.
|
||||
///
|
||||
/// This step is necessary because the [compare] method requires the [value] to be a [Comparable] in order to perform the comparison.
|
||||
final comparisonValue = value as Comparable?;
|
||||
|
||||
/// Compares the provided input value against the value specified in the [ValidationExpression].
|
||||
///
|
||||
/// The comparison is performed based on the [ValidationOperator] specified in the [ValidationExpression].
|
||||
/// If the comparison fails, an error message is added to the provided [ValidationContext].
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [context]: The [ValidationContext] to which any errors will be added.
|
||||
/// - [input]: The value to be compared against the [ValidationExpression] value.
|
||||
///
|
||||
/// Throws:
|
||||
/// - [ClassCastException]: If the [value] property of the [ValidationExpression] is not a [Comparable].
|
||||
switch (operator) {
|
||||
case ValidationOperator.equalTo:
|
||||
{
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
import 'package:protevus_database/src/managed/validation/impl.dart';
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
|
@ -6,6 +15,10 @@ import 'package:protevus_database/src/query/query.dart';
|
|||
///
|
||||
/// Instances of this type are created during [ManagedDataModel] compilation.
|
||||
class ManagedValidator {
|
||||
/// Constructs a [ManagedValidator] instance with the specified [definition] and [state].
|
||||
///
|
||||
/// The [definition] parameter contains the metadata associated with this instance, while
|
||||
/// the [state] parameter holds a dynamic value that can be used during the validation process.
|
||||
ManagedValidator(this.definition, this.state);
|
||||
|
||||
/// Executes all [Validate]s for [object].
|
||||
|
@ -81,17 +94,52 @@ class ManagedValidator {
|
|||
}
|
||||
|
||||
/// The property being validated.
|
||||
///
|
||||
/// This property represents the [ManagedPropertyDescription] that is being
|
||||
/// validated by the current instance of [ManagedValidator]. It is used to
|
||||
/// retrieve information about the property, such as its name, type, and
|
||||
/// relationship details.
|
||||
ManagedPropertyDescription? property;
|
||||
|
||||
/// The metadata associated with this instance.
|
||||
///
|
||||
/// The `definition` property contains the metadata associated with this instance of `ManagedValidator`.
|
||||
/// This metadata is used to define the validation rules that will be applied to the properties
|
||||
/// of a `ManagedObject` during an insert or update operation.
|
||||
final Validate definition;
|
||||
|
||||
/// The dynamic state associated with this validator.
|
||||
///
|
||||
/// This property holds a dynamic value that can be used during the validation process.
|
||||
/// The state is provided when the [ManagedValidator] is constructed and can be used
|
||||
/// by the validation logic to customize the validation behavior.
|
||||
final dynamic state;
|
||||
|
||||
/// Validates the property according to the validation rules defined in the [definition] property.
|
||||
///
|
||||
/// This method is called by the [run] method of the [ManagedValidator] class to perform the actual
|
||||
/// validation of a property value. The [context] parameter is used to store the validation results,
|
||||
/// and the [value] parameter is the value of the property being validated.
|
||||
///
|
||||
/// The validation logic is defined in the [definition] property, which is an instance of the [Validate]
|
||||
/// class. This class contains the metadata that describes the validation rules to be applied to the
|
||||
/// property.
|
||||
void validate(ValidationContext context, dynamic value) {
|
||||
definition.validate(context, value);
|
||||
}
|
||||
|
||||
/// Returns a string representation of the given validation event.
|
||||
///
|
||||
/// This method is a helper function that takes a [Validating] event and
|
||||
/// returns a string describing the event.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `op`: The [Validating] event to be described.
|
||||
///
|
||||
/// Returns:
|
||||
/// A string representing the given validation event. The possible return
|
||||
/// values are "insert", "update", or "unknown" if the event is not
|
||||
/// recognized.
|
||||
static String _getEventName(Validating op) {
|
||||
switch (op) {
|
||||
case Validating.insert:
|
||||
|
|
|
@ -1,9 +1,21 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_openapi/documentable.dart';
|
||||
import 'package:protevus_database/db.dart';
|
||||
import 'package:protevus_database/src/managed/validation/impl.dart';
|
||||
import 'package:protevus_openapi/v3.dart';
|
||||
|
||||
/// Types of operations [ManagedValidator]s will be triggered for.
|
||||
///
|
||||
/// - [update]: The validation is triggered during an update operation.
|
||||
/// - [insert]: The validation is triggered during an insert operation.
|
||||
enum Validating { update, insert }
|
||||
|
||||
/// Information about a validation being performed.
|
||||
|
@ -52,6 +64,7 @@ class ValidationContext {
|
|||
class ValidateCompilationError extends Error {
|
||||
ValidateCompilationError(this.reason);
|
||||
|
||||
/// The reason for the [ValidateCompilationError].
|
||||
final String reason;
|
||||
}
|
||||
|
||||
|
@ -79,7 +92,9 @@ class ValidateCompilationError extends Error {
|
|||
class Validate {
|
||||
/// Invoke this constructor when creating custom subclasses.
|
||||
///
|
||||
/// This constructor is used so that subclasses can pass [onUpdate] and [onInsert].
|
||||
/// This constructor is used so that subclasses can pass [onUpdate] and [onInsert] values to control when
|
||||
/// the validation is performed. For example:
|
||||
///
|
||||
/// Example:
|
||||
/// class CustomValidate extends Validate<String> {
|
||||
/// CustomValidate({bool onUpdate: true, bool onInsert: true})
|
||||
|
@ -104,6 +119,20 @@ class Validate {
|
|||
_equalTo = null,
|
||||
type = null;
|
||||
|
||||
/// A private constructor used to create instances of the [Validate] class.
|
||||
///
|
||||
/// This constructor is used by the various named constructors of the [Validate] class to set the instance
|
||||
/// variables with the provided values.
|
||||
///
|
||||
/// - [onUpdate]: Whether the validation should be performed during update operations.
|
||||
/// - [onInsert]: Whether the validation should be performed during insert operations.
|
||||
/// - [validator]: The type of validation to perform.
|
||||
/// - [value]: A value used by the validation.
|
||||
/// - [greaterThan]: A value to compare the input value against using the "greater than" operator.
|
||||
/// - [greaterThanEqualTo]: A value to compare the input value against using the "greater than or equal to" operator.
|
||||
/// - [equalTo]: A value to compare the input value against using the "equal to" operator.
|
||||
/// - [lessThan]: A value to compare the input value against using the "less than" operator.
|
||||
/// - [lessThanEqualTo]: A value to compare the input value against using the "less than or equal to" operator.
|
||||
const Validate._({
|
||||
bool onUpdate = true,
|
||||
bool onInsert = true,
|
||||
|
@ -292,21 +321,67 @@ class Validate {
|
|||
|
||||
/// A validator that ensures a value cannot be modified after insertion.
|
||||
///
|
||||
/// This is equivalent to `Validate.absent(onUpdate: true, onInsert: false).
|
||||
/// This is equivalent to `Validate.absent(onUpdate: true, onInsert: false)`.
|
||||
///
|
||||
/// This validator is used to ensure that a property, once set during the initial
|
||||
/// insertion of a record, cannot be updated. For example, you might use this
|
||||
/// validator on a `dateCreated` property to ensure that the creation date
|
||||
/// cannot be changed after the record is inserted.
|
||||
const Validate.constant() : this.absent(onUpdate: true, onInsert: false);
|
||||
|
||||
/// Whether or not this validation is checked on update queries.
|
||||
///
|
||||
/// This property determines whether the validation will be performed during update operations on the database.
|
||||
/// If `true`, the validation will be executed during update queries. If `false`, the validation will be skipped
|
||||
/// during update queries.
|
||||
final bool runOnUpdate;
|
||||
|
||||
/// Whether or not this validation is checked on insert queries.
|
||||
///
|
||||
/// This property determines whether the validation will be performed during insert operations on the database.
|
||||
/// If `true`, the validation will be executed during insert queries. If `false`, the validation will be skipped
|
||||
/// during insert queries.
|
||||
final bool runOnInsert;
|
||||
|
||||
/// The value associated with the validator.
|
||||
///
|
||||
/// The meaning of this value depends on the type of validator. For example, for a
|
||||
/// [Validate.matches] validator, this value would be the regular expression pattern to
|
||||
/// match against. For a [Validate.oneOf] validator, this value would be the list of
|
||||
/// allowed values.
|
||||
final dynamic _value;
|
||||
|
||||
/// The greater than value for the comparison validation.
|
||||
final Comparable? _greaterThan;
|
||||
|
||||
/// The greater than or equal to value for the comparison validation.
|
||||
final Comparable? _greaterThanEqualTo;
|
||||
|
||||
/// The value to compare the input value against using the "equal to" operator.
|
||||
///
|
||||
/// This value is used in the [_comparisonCompiler] method to create a [ValidationExpression]
|
||||
/// with the [ValidationOperator.equalTo] operator.
|
||||
final Comparable? _equalTo;
|
||||
|
||||
/// The "less than" value for the comparison validation.
|
||||
final Comparable? _lessThan;
|
||||
|
||||
/// The "less than or equal to" value for the comparison validation.
|
||||
///
|
||||
/// This value is used in the [_comparisonCompiler] method to create a [ValidationExpression]
|
||||
/// with the [ValidationOperator.lessThanEqualTo] operator.
|
||||
final Comparable? _lessThanEqualTo;
|
||||
|
||||
/// The type of validation to be performed.
|
||||
///
|
||||
/// This can be one of the following values:
|
||||
///
|
||||
/// - `ValidateType.absent`: The property must not be present in the update or insert query.
|
||||
/// - `ValidateType.present`: The property must be present in the update or insert query.
|
||||
/// - `ValidateType.oneOf`: The property value must be one of the values in the provided list.
|
||||
/// - `ValidateType.comparison`: The property value must meet the comparison conditions specified.
|
||||
/// - `ValidateType.regex`: The property value must match the provided regular expression.
|
||||
/// - `ValidateType.length`: The length of the property value must meet the specified length conditions.
|
||||
final ValidateType? type;
|
||||
|
||||
/// Subclasses override this method to perform any one-time initialization tasks and check for correctness.
|
||||
|
@ -422,10 +497,95 @@ class Validate {
|
|||
///
|
||||
/// Used during documentation process. When creating custom validator subclasses, override this method
|
||||
/// to modify [object] for any constraints the validator imposes.
|
||||
/// This method is used during the documentation process. When creating custom validator subclasses,
|
||||
/// override this method to modify the [object] parameter for any constraints the validator imposes.
|
||||
///
|
||||
/// The constraints added to the [APISchemaObject] depend on the type of validator:
|
||||
///
|
||||
/// - For `ValidateType.regex` validators, the `pattern` property of the [APISchemaObject] is set to the
|
||||
/// regular expression pattern specified in the validator.
|
||||
/// - For `ValidateType.comparison` validators, the `minimum`, `maximum`, `exclusiveMinimum`, and
|
||||
/// `exclusiveMaximum` properties of the [APISchemaObject] are set based on the comparison values
|
||||
/// specified in the validator.
|
||||
/// - For `ValidateType.length` validators, the `minLength`, `maxLength`, and `maximum` properties
|
||||
/// of the [APISchemaObject] are set based on the length-related values specified in the validator.
|
||||
/// - For `ValidateType.present` and `ValidateType.absent` validators, no constraints are added to the
|
||||
/// [APISchemaObject].
|
||||
/// - For `ValidateType.oneOf` validators, the `enumerated` property of the [APISchemaObject] is set
|
||||
/// to the list of allowed values specified in the validator.
|
||||
///
|
||||
/// @param context The [APIDocumentContext] being used to document the API.
|
||||
/// @param object The [APISchemaObject] to which constraints should be added.
|
||||
void constrainSchemaObject(
|
||||
/// Adds constraints to an [APISchemaObject] imposed by this validator.
|
||||
///
|
||||
/// This method is used during the documentation process. When creating custom validator subclasses,
|
||||
/// override this method to modify the [object] parameter for any constraints the validator imposes.
|
||||
///
|
||||
/// The constraints added to the [APISchemaObject] depend on the type of validator:
|
||||
///
|
||||
/// - For `ValidateType.regex` validators, the `pattern` property of the [APISchemaObject] is set to the
|
||||
/// regular expression pattern specified in the validator.
|
||||
/// - For `ValidateType.comparison` validators, the `minimum`, `maximum`, `exclusiveMinimum`, and
|
||||
/// `exclusiveMaximum` properties of the [APISchemaObject] are set based on the comparison values
|
||||
/// specified in the validator.
|
||||
/// - For `ValidateType.length` validators, the `minLength`, `maxLength`, and `maximum` properties
|
||||
/// of the [APISchemaObject] are set based on the length-related values specified in the validator.
|
||||
/// - For `ValidateType.present` and `ValidateType.absent` validators, no constraints are added to the
|
||||
/// [APISchemaObject].
|
||||
/// - For `ValidateType.oneOf` validators, the `enumerated` property of the [APISchemaObject] is set
|
||||
/// to the list of allowed values specified in the validator.
|
||||
///
|
||||
/// @param context The [APIDocumentContext] being used to document the API.
|
||||
/// @param object The [APISchemaObject] to which constraints should be added.
|
||||
APIDocumentContext context,
|
||||
|
||||
/// Adds constraints to an [APISchemaObject] imposed by this validator.
|
||||
///
|
||||
/// This method is used during the documentation process. When creating custom validator subclasses,
|
||||
/// override this method to modify the [object] parameter for any constraints the validator imposes.
|
||||
///
|
||||
/// The constraints added to the [APISchemaObject] depend on the type of validator:
|
||||
///
|
||||
/// - For `ValidateType.regex` validators, the `pattern` property of the [APISchemaObject] is set to the
|
||||
/// regular expression pattern specified in the validator.
|
||||
/// - For `ValidateType.comparison` validators, the `minimum`, `maximum`, `exclusiveMinimum`, and
|
||||
/// `exclusiveMaximum` properties of the [APISchemaObject] are set based on the comparison values
|
||||
/// specified in the validator.
|
||||
/// - For `ValidateType.length` validators, the `minLength`, `maxLength`, and `maximum` properties
|
||||
/// of the [APISchemaObject] are set based on the length-related values specified in the validator.
|
||||
/// - For `ValidateType.present` and `ValidateType.absent` validators, no constraints are added to the
|
||||
/// [APISchemaObject].
|
||||
/// - For `ValidateType.oneOf` validators, the `enumerated` property of the [APISchemaObject] is set
|
||||
/// to the list of allowed values specified in the validator.
|
||||
///
|
||||
/// @param context The [APIDocumentContext] being used to document the API.
|
||||
/// @param object The [APISchemaObject] to which constraints should be added.
|
||||
APISchemaObject object,
|
||||
) {
|
||||
/// Adds constraints to an [APISchemaObject] imposed by this validator.
|
||||
///
|
||||
/// This method is used during the documentation process. When creating custom validator subclasses,
|
||||
/// override this method to modify the [object] parameter for any constraints the validator imposes.
|
||||
///
|
||||
/// The constraints added to the [APISchemaObject] depend on the type of validator:
|
||||
///
|
||||
/// - For `ValidateType.regex` validators, the `pattern` property of the [APISchemaObject] is set to the
|
||||
/// regular expression pattern specified in the validator.
|
||||
/// - For `ValidateType.comparison` validators, the `minimum`, `maximum`, `exclusiveMinimum`, and
|
||||
/// `exclusiveMaximum` properties of the [APISchemaObject] are set based on the comparison values
|
||||
/// specified in the validator.
|
||||
/// - For `ValidateType.length` validators, the `minLength`, `maxLength`, and `maximum` properties
|
||||
/// of the [APISchemaObject] are set based on the length-related values specified in the validator.
|
||||
/// - For `ValidateType.present` and `ValidateType.absent` validators, no constraints are added to the
|
||||
/// [APISchemaObject].
|
||||
/// - For `ValidateType.oneOf` validators, the `enumerated` property of the [APISchemaObject] is set
|
||||
/// to the list of allowed values specified in the validator.
|
||||
///
|
||||
/// @param context The [APIDocumentContext] being used to document the API.
|
||||
/// @param object The [APISchemaObject] to which constraints should be added.
|
||||
///
|
||||
/// The implementation of this method is as follows:
|
||||
switch (type!) {
|
||||
case ValidateType.regex:
|
||||
{
|
||||
|
@ -485,6 +645,25 @@ class Validate {
|
|||
}
|
||||
}
|
||||
|
||||
/// Compiles the [Validate.oneOf] validator.
|
||||
///
|
||||
/// The [Validate.oneOf] validator ensures that the value of a property is one of a set of allowed values.
|
||||
///
|
||||
/// This method checks the following:
|
||||
///
|
||||
/// - The `_value` property must be a `List`.
|
||||
/// - The type of the property being validated must be either `String`, `int`, or `bigint`.
|
||||
/// - The list of allowed values must all be assignable to the type of the property being validated.
|
||||
/// - The list of allowed values must not be empty.
|
||||
///
|
||||
/// If any of these conditions are not met, a [ValidateCompilationError] is thrown with a descriptive error message.
|
||||
///
|
||||
/// The compiled result is the list of allowed values, which will be stored in the [ValidationContext.state] property
|
||||
/// during validation.
|
||||
///
|
||||
/// @param typeBeingValidated The [ManagedType] of the property being validated.
|
||||
/// @param relationshipInverseType If the property is a relationship, the type of the inverse property.
|
||||
/// @return The list of allowed values for the [Validate.oneOf] validator.
|
||||
dynamic _oneOfCompiler(
|
||||
ManagedType typeBeingValidated, {
|
||||
Type? relationshipInverseType,
|
||||
|
@ -523,6 +702,15 @@ class Validate {
|
|||
return options;
|
||||
}
|
||||
|
||||
/// A list of [ValidationExpression] objects representing the various comparison
|
||||
/// conditions specified for this validator.
|
||||
///
|
||||
/// The [ValidationExpression] objects are created based on the values of the
|
||||
/// `_equalTo`, `_lessThan`, `_lessThanEqualTo`, `_greaterThan`, and
|
||||
/// `_greaterThanEqualTo` instance variables.
|
||||
///
|
||||
/// This method is used by the `_comparisonCompiler` method to compile the
|
||||
/// comparison validator.
|
||||
List<ValidationExpression> get _expressions {
|
||||
final comparisons = <ValidationExpression>[];
|
||||
if (_equalTo != null) {
|
||||
|
@ -558,6 +746,23 @@ class Validate {
|
|||
return comparisons;
|
||||
}
|
||||
|
||||
/// Compiles the comparison validator.
|
||||
///
|
||||
/// This method is responsible for creating a list of [ValidationExpression] objects
|
||||
/// that represent the various comparison conditions specified for this validator.
|
||||
///
|
||||
/// The method performs the following tasks:
|
||||
///
|
||||
/// 1. Retrieves the list of comparison expressions from the `_expressions` getter.
|
||||
/// 2. For each expression, it calls the `_parseComparisonValue` method to parse and validate
|
||||
/// the comparison value based on the type of the property being validated.
|
||||
///
|
||||
/// The compiled result is the list of [ValidationExpression] objects, which will be stored
|
||||
/// in the [ValidationContext.state] property during validation.
|
||||
///
|
||||
/// @param typeBeingValidated The [ManagedType] of the property being validated.
|
||||
/// @param relationshipInverseType If the property is a relationship, the type of the inverse property.
|
||||
/// @return The list of [ValidationExpression] objects representing the comparison conditions.
|
||||
dynamic _comparisonCompiler(
|
||||
ManagedType? typeBeingValidated, {
|
||||
Type? relationshipInverseType,
|
||||
|
@ -573,6 +778,28 @@ class Validate {
|
|||
return exprs;
|
||||
}
|
||||
|
||||
/// Parses the comparison value for the [Validate.compare] validator.
|
||||
///
|
||||
/// This method is responsible for validating the type of the comparison value
|
||||
/// and converting it to the appropriate type if necessary.
|
||||
///
|
||||
/// If the property being validated is of type [DateTime], the method attempts to
|
||||
/// parse the [referenceValue] as a [DateTime] using [DateTime.parse]. If the
|
||||
/// parsing fails, a [ValidateCompilationError] is thrown.
|
||||
///
|
||||
/// If the property being validated is not of type [DateTime], the method checks
|
||||
/// if the [referenceValue] is assignable to the type of the property being
|
||||
/// validated. If the types are not compatible, a [ValidateCompilationError] is
|
||||
/// thrown.
|
||||
///
|
||||
/// If the [relationshipInverseType] is not null, the method checks if the
|
||||
/// [referenceValue] is assignable to the primary key type of the relationship
|
||||
/// being validated.
|
||||
///
|
||||
/// @param referenceValue The value to be used for the comparison.
|
||||
/// @param typeBeingValidated The [ManagedType] of the property being validated.
|
||||
/// @param relationshipInverseType If the property is a relationship, the type of the inverse property.
|
||||
/// @return The parsed comparison value as a [Comparable] object.
|
||||
Comparable? _parseComparisonValue(
|
||||
dynamic referenceValue,
|
||||
ManagedType? typeBeingValidated, {
|
||||
|
@ -611,6 +838,27 @@ class Validate {
|
|||
return referenceValue as Comparable?;
|
||||
}
|
||||
|
||||
/// Compiles the regular expression validator.
|
||||
///
|
||||
/// This method is responsible for compiling the regular expression pattern specified
|
||||
/// in the `Validate.matches` validator.
|
||||
///
|
||||
/// The method performs the following tasks:
|
||||
///
|
||||
/// 1. Checks that the property being validated is of type `String`. If not, a `ValidateCompilationError`
|
||||
/// is thrown with an appropriate error message.
|
||||
/// 2. Checks that the `_value` property, which should contain the regular expression pattern,
|
||||
/// is of type `String`. If not, a `ValidateCompilationError` is thrown with an appropriate
|
||||
/// error message.
|
||||
/// 3. Creates a `RegExp` object using the regular expression pattern specified in the `_value`
|
||||
/// property, and returns it as the compiled result.
|
||||
///
|
||||
/// The compiled `RegExp` object will be stored in the `ValidationContext.state` property
|
||||
/// during validation.
|
||||
///
|
||||
/// @param typeBeingValidated The [ManagedType] of the property being validated.
|
||||
/// @param relationshipInverseType If the property is a relationship, the type of the inverse property.
|
||||
/// @return The compiled `RegExp` object representing the regular expression pattern.
|
||||
dynamic _regexCompiler(
|
||||
ManagedType? typeBeingValidated, {
|
||||
Type? relationshipInverseType,
|
||||
|
@ -630,6 +878,25 @@ class Validate {
|
|||
return RegExp(_value);
|
||||
}
|
||||
|
||||
/// Compiles the length validator.
|
||||
///
|
||||
/// This method is responsible for creating a list of [ValidationExpression] objects
|
||||
/// that represent the various length-based conditions specified for this validator.
|
||||
///
|
||||
/// The method performs the following tasks:
|
||||
///
|
||||
/// 1. Checks that the property being validated is of type `String`. If not, a
|
||||
/// `ValidateCompilationError` is thrown with an appropriate error message.
|
||||
/// 2. Retrieves the list of length-based expressions from the `_expressions` getter.
|
||||
/// 3. Checks that all the values in the expressions are of type `int`. If not, a
|
||||
/// `ValidateCompilationError` is thrown with an appropriate error message.
|
||||
///
|
||||
/// The compiled result is the list of [ValidationExpression] objects, which will be
|
||||
/// stored in the [ValidationContext.state] property during validation.
|
||||
///
|
||||
/// @param typeBeingValidated The [ManagedType] of the property being validated.
|
||||
/// @param relationshipInverseType If the property is a relationship, the type of the inverse property.
|
||||
/// @return The list of [ValidationExpression] objects representing the length-based conditions.
|
||||
dynamic _lengthCompiler(
|
||||
ManagedType typeBeingValidated, {
|
||||
Type? relationshipInverseType,
|
||||
|
|
|
@ -1,32 +1,92 @@
|
|||
import 'dart:async';
|
||||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:protevus_database/src/managed/context.dart';
|
||||
import 'package:protevus_database/src/managed/entity.dart';
|
||||
import 'package:protevus_database/src/managed/object.dart';
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
import 'package:protevus_database/src/schema/schema.dart';
|
||||
|
||||
/// Specifies the return type for a persistent store query.
|
||||
///
|
||||
/// - [rowCount]: Indicates that the query should return the number of rows affected.
|
||||
/// - [rows]: Indicates that the query should return the result set as a list of rows.
|
||||
enum PersistentStoreQueryReturnType { rowCount, rows }
|
||||
|
||||
/// An interface for implementing persistent storage.
|
||||
/// Specifies the return type for a persistent store query.
|
||||
///
|
||||
/// You rarely need to use this class directly. See [Query] for how to interact with instances of this class.
|
||||
/// Implementors of this class serve as the bridge between [Query]s and a specific database.
|
||||
abstract class PersistentStore {
|
||||
/// Creates a new database-specific [Query].
|
||||
///
|
||||
/// Subclasses override this method to provide a concrete implementation of [Query]
|
||||
/// specific to this type. Objects returned from this method must implement [Query]. They
|
||||
/// should mixin [QueryMixin] to most of the behavior provided by a query.
|
||||
/// This method creates a new instance of a [Query] subclass that is specific to the
|
||||
/// database implementation represented by this [PersistentStore]. The returned
|
||||
/// [Query] instance will be capable of interacting with the database in the appropriate
|
||||
/// way.
|
||||
///
|
||||
/// The [context] parameter specifies the [ManagedContext] that the [Query] will be
|
||||
/// associated with. The [entity] parameter specifies the [ManagedEntity] that the
|
||||
/// [Query] will operate on. Optionally, [values] can be provided which will be
|
||||
/// used to initialize the [Query].
|
||||
///
|
||||
/// Subclasses must override this method to provide a concrete implementation of [Query]
|
||||
/// specific to this type of [PersistentStore]. The objects returned from this method
|
||||
/// must implement [Query] and should mixin [QueryMixin] to inherit the majority of
|
||||
/// the behavior provided by a query.
|
||||
Query<T> newQuery<T extends ManagedObject>(
|
||||
ManagedContext context,
|
||||
ManagedEntity entity, {
|
||||
T? values,
|
||||
});
|
||||
|
||||
/// Executes an arbitrary command.
|
||||
/// Executes an arbitrary SQL command on the database.
|
||||
///
|
||||
/// This method allows you to execute any SQL command on the database managed by
|
||||
/// this [PersistentStore] instance. The [sql] parameter should contain the SQL
|
||||
/// statement to be executed, and the optional [substitutionValues] parameter
|
||||
/// can be used to provide values to be substituted into the SQL statement, similar
|
||||
/// to how a prepared statement works.
|
||||
///
|
||||
/// The return value of this method is a [Future] that completes when the SQL
|
||||
/// command has finished executing. The return value of the [Future] depends on
|
||||
/// the type of SQL statement being executed, but it is typically `null` for
|
||||
/// non-SELECT statements, or a value representing the result of the SQL statement.
|
||||
///
|
||||
/// This method is intended for advanced use cases where the higher-level query
|
||||
/// APIs provided by the [Query] class are not sufficient. In general, it is
|
||||
/// recommended to use the [Query] class instead of calling [execute] directly,
|
||||
/// as the [Query] class provides a more type-safe and database-agnostic interface
|
||||
/// for interacting with the database.
|
||||
Future execute(String sql, {Map<String, dynamic>? substitutionValues});
|
||||
|
||||
/// Executes a database query with the provided parameters.
|
||||
///
|
||||
/// This method allows you to execute a database query using a format string and a map of values.
|
||||
///
|
||||
/// The `formatString` parameter is a SQL string that can contain placeholders for values, which will be
|
||||
/// replaced with the values from the `values` parameter.
|
||||
///
|
||||
/// The `values` parameter is a map of key-value pairs, where the keys correspond to the placeholders
|
||||
/// in the `formatString`, and the values are the actual values to be substituted.
|
||||
///
|
||||
/// The `timeoutInSeconds` parameter specifies the maximum time, in seconds, that the query is allowed to
|
||||
/// run before being cancelled.
|
||||
///
|
||||
/// The optional `returnType` parameter specifies the type of return value expected from the query. If
|
||||
/// `PersistentStoreQueryReturnType.rowCount` is specified, the method will return the number of rows
|
||||
/// affected by the query. If `PersistentStoreQueryReturnType.rows` is specified, the method will return
|
||||
/// the result set as a list of rows.
|
||||
///
|
||||
/// The return value of this method is a `Future` that completes when the query has finished executing.
|
||||
/// The type of the value returned by the `Future` depends on the `returnType` parameter.
|
||||
Future<dynamic> executeQuery(
|
||||
String formatString,
|
||||
Map<String, dynamic> values,
|
||||
|
@ -34,64 +94,337 @@ abstract class PersistentStore {
|
|||
PersistentStoreQueryReturnType? returnType,
|
||||
});
|
||||
|
||||
/// Executes a database transaction.
|
||||
///
|
||||
/// This method allows you to execute a sequence of database operations as a single
|
||||
/// atomic transaction. If any of the operations in the transaction fail, the entire
|
||||
/// transaction is rolled back, ensuring data consistency.
|
||||
///
|
||||
/// The `transactionContext` parameter is the `ManagedContext` in which the transaction
|
||||
/// will be executed. This context must be separate from any existing `ManagedContext`
|
||||
/// instances, as transactions require their own isolated context.
|
||||
///
|
||||
/// The `transactionBlock` parameter is a callback function that contains the database
|
||||
/// operations to be executed as part of the transaction. This function takes the
|
||||
/// `transactionContext` as its argument and returns a `Future<T>` that represents the
|
||||
/// result of the transaction.
|
||||
///
|
||||
/// The return value of this method is a `Future<T>` that completes when the transaction
|
||||
/// has finished executing. The value returned by the `Future` is the same as the value
|
||||
/// returned by the `transactionBlock` callback.
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```dart
|
||||
/// final result = await persistentStore.transaction(
|
||||
/// transactionContext,
|
||||
/// (context) async {
|
||||
/// final user = await User(name: 'John Doe').insert(context);
|
||||
/// final account = await Account(userId: user.id, balance: 100.0).insert(context);
|
||||
/// return account;
|
||||
/// },
|
||||
/// );
|
||||
/// ```
|
||||
Future<T> transaction<T>(
|
||||
ManagedContext transactionContext,
|
||||
Future<T> Function(ManagedContext transaction) transactionBlock,
|
||||
);
|
||||
|
||||
/// Closes the underlying database connection.
|
||||
///
|
||||
/// This method is used to close the database connection managed by this
|
||||
/// `PersistentStore` instance. Calling this method will ensure that all
|
||||
/// resources associated with the database connection are properly released,
|
||||
/// and that the connection is no longer available for use.
|
||||
///
|
||||
/// The return value of this method is a `Future` that completes when the
|
||||
/// database connection has been successfully closed. If there is an error
|
||||
/// closing the connection, the `Future` will complete with an error.
|
||||
Future close();
|
||||
|
||||
// -- Schema Ops --
|
||||
|
||||
/// Creates a list of SQL statements to create a new database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to create a new database table
|
||||
/// based on the provided [SchemaTable] object. The table can be created as a
|
||||
/// temporary table if the `isTemporary` parameter is set to `true`.
|
||||
///
|
||||
/// The returned list of strings represents the SQL statements that should be executed
|
||||
/// to create the new table. The caller of this method is responsible for executing
|
||||
/// these statements to create the table in the database.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object that defines the structure of the new table.
|
||||
/// - `isTemporary`: A boolean indicating whether the table should be created as a
|
||||
/// temporary table. Temporary tables are only visible within the current session
|
||||
/// and are automatically dropped when the session ends. Defaults to `false`.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be
|
||||
/// executed to create the new table.
|
||||
List<String> createTable(SchemaTable table, {bool isTemporary = false});
|
||||
|
||||
/// Generates a list of SQL statements to rename a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to rename an existing database table.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table to be renamed.
|
||||
/// - `name`: The new name for the table.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be
|
||||
/// executed to rename the table.
|
||||
List<String> renameTable(SchemaTable table, String name);
|
||||
|
||||
/// Generates a list of SQL statements to delete a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to delete an existing database table.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table to be deleted.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be
|
||||
/// executed to delete the table.
|
||||
List<String> deleteTable(SchemaTable table);
|
||||
|
||||
/// Generates a list of SQL statements to create a unique column set for a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to create a unique column set
|
||||
/// for an existing database table. A unique column set is a set of one or more columns
|
||||
/// that must have unique values for each row in the table.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table for which the unique column
|
||||
/// set should be created.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be
|
||||
/// executed to create the unique column set.
|
||||
List<String> addTableUniqueColumnSet(SchemaTable table);
|
||||
|
||||
/// Generates a list of SQL statements to delete a unique column set for a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to delete an existing unique column set
|
||||
/// for a database table. A unique column set is a set of one or more columns
|
||||
/// that must have unique values for each row in the table.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table for which the unique column
|
||||
/// set should be deleted.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be
|
||||
/// executed to delete the unique column set.
|
||||
List<String> deleteTableUniqueColumnSet(SchemaTable table);
|
||||
|
||||
/// Generates a list of SQL statements to add a new column to a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to add a new column to an existing
|
||||
/// database table. The new column is defined by the provided [SchemaColumn] object.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table to which the new column should be added.
|
||||
/// - `column`: The [SchemaColumn] object that defines the new column to be added.
|
||||
/// - `unencodedInitialValue`: An optional string that specifies an initial value for the new column.
|
||||
/// This value will be used as the default value for the column unless the column has a specific
|
||||
/// default value defined in the [SchemaColumn] object.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be executed
|
||||
/// to add the new column to the table.
|
||||
List<String> addColumn(
|
||||
SchemaTable table,
|
||||
SchemaColumn column, {
|
||||
String? unencodedInitialValue,
|
||||
});
|
||||
|
||||
/// Generates a list of SQL statements to delete a column from a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to delete an existing column from
|
||||
/// a database table. The column to be deleted is specified by the provided [SchemaTable]
|
||||
/// and [SchemaColumn] objects.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table from which the column
|
||||
/// should be deleted.
|
||||
/// - `column`: The [SchemaColumn] object representing the column to be deleted.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be
|
||||
/// executed to delete the specified column from the table.
|
||||
List<String> deleteColumn(SchemaTable table, SchemaColumn column);
|
||||
|
||||
/// Generates a list of SQL statements to rename a column in a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to rename an existing column
|
||||
/// in a database table.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table containing the column
|
||||
/// to be renamed.
|
||||
/// - `column`: The [SchemaColumn] object representing the column to be renamed.
|
||||
/// - `name`: The new name for the column.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be
|
||||
/// executed to rename the column.
|
||||
List<String> renameColumn(
|
||||
SchemaTable table,
|
||||
SchemaColumn column,
|
||||
String name,
|
||||
);
|
||||
|
||||
/// Generates a list of SQL statements to alter the nullability of a column in a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to change the nullability of an existing
|
||||
/// column in a database table. The new nullability setting is specified by the `nullable` parameter
|
||||
/// of the provided [SchemaColumn] object.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table containing the column to be altered.
|
||||
/// - `column`: The [SchemaColumn] object representing the column to be altered.
|
||||
/// - `unencodedInitialValue`: An optional string that specifies an initial value for the column
|
||||
/// if it is being changed from nullable to non-nullable. This value will be used to populate
|
||||
/// any existing rows that have a null value in the column.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be executed
|
||||
/// to alter the nullability of the column.
|
||||
List<String> alterColumnNullability(
|
||||
SchemaTable table,
|
||||
SchemaColumn column,
|
||||
String? unencodedInitialValue,
|
||||
);
|
||||
|
||||
/// Generates a list of SQL statements to alter the uniqueness of a column in a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to change the uniqueness of an existing
|
||||
/// column in a database table. The new uniqueness setting is specified by the `unique` property
|
||||
/// of the provided [SchemaColumn] object.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table containing the column to be altered.
|
||||
/// - `column`: The [SchemaColumn] object representing the column to be altered.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be executed
|
||||
/// to alter the uniqueness of the column.
|
||||
List<String> alterColumnUniqueness(SchemaTable table, SchemaColumn column);
|
||||
|
||||
/// Generates a list of SQL statements to alter the default value of a column in a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to change the default value of an existing
|
||||
/// column in a database table. The new default value is specified by the `defaultValue` property
|
||||
/// of the provided [SchemaColumn] object.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table containing the column to be altered.
|
||||
/// - `column`: The [SchemaColumn] object representing the column to be altered.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be executed
|
||||
/// to alter the default value of the column.
|
||||
List<String> alterColumnDefaultValue(SchemaTable table, SchemaColumn column);
|
||||
|
||||
/// Generates a list of SQL statements to alter the delete rule of a column in a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to change the delete rule of an existing
|
||||
/// column in a database table. The delete rule determines what happens to the data in the
|
||||
/// column when a row is deleted from the table.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table containing the column to be altered.
|
||||
/// - `column`: The [SchemaColumn] object representing the column to be altered.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be executed
|
||||
/// to alter the delete rule of the column.
|
||||
List<String> alterColumnDeleteRule(SchemaTable table, SchemaColumn column);
|
||||
|
||||
/// Generates a list of SQL statements to add a new index to a column in a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to add a new index to an existing
|
||||
/// column in a database table. The index is defined by the provided [SchemaTable] and
|
||||
/// [SchemaColumn] objects.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table to which the new index
|
||||
/// should be added.
|
||||
/// - `column`: The [SchemaColumn] object representing the column on which the new
|
||||
/// index should be created.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be
|
||||
/// executed to add the new index to the table.
|
||||
List<String> addIndexToColumn(SchemaTable table, SchemaColumn column);
|
||||
|
||||
/// Generates a list of SQL statements to rename an index on a column in a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to rename an existing index on a
|
||||
/// column in a database table.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table containing the index
|
||||
/// to be renamed.
|
||||
/// - `column`: The [SchemaColumn] object representing the column on which the index
|
||||
/// is defined.
|
||||
/// - `newIndexName`: The new name for the index.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be
|
||||
/// executed to rename the index.
|
||||
List<String> renameIndex(
|
||||
SchemaTable table,
|
||||
SchemaColumn column,
|
||||
String newIndexName,
|
||||
);
|
||||
|
||||
/// Generates a list of SQL statements to delete an index from a column in a database table.
|
||||
///
|
||||
/// This method generates the necessary SQL statements to delete an existing index
|
||||
/// from a column in a database table.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `table`: The [SchemaTable] object representing the table from which the index
|
||||
/// should be deleted.
|
||||
/// - `column`: The [SchemaColumn] object representing the column on which the index
|
||||
/// is defined.
|
||||
///
|
||||
/// Returns:
|
||||
/// A list of strings, where each string represents a SQL statement that should be
|
||||
/// executed to delete the index from the table.
|
||||
List<String> deleteIndexFromColumn(SchemaTable table, SchemaColumn column);
|
||||
|
||||
/// Returns the current version of the database schema.
|
||||
///
|
||||
/// This property returns the current version of the database schema managed by the
|
||||
/// `PersistentStore` instance. The schema version is typically used to track the
|
||||
/// state of the database and ensure that migrations are applied correctly when the
|
||||
/// application is upgraded.
|
||||
///
|
||||
/// The returned value is a `Future<int>` that resolves to the current schema version.
|
||||
/// This method should be implemented by the concrete `PersistentStore` subclass to
|
||||
/// provide the appropriate implementation for the underlying database system.
|
||||
Future<int> get schemaVersion;
|
||||
|
||||
/// Upgrades the database schema to a new version.
|
||||
///
|
||||
/// This method applies a series of database migrations to upgrade the schema from the
|
||||
/// specified `fromSchema` version to a new version.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `fromSchema`: The current schema version of the database.
|
||||
/// - `withMigrations`: A list of [Migration] instances that should be applied to upgrade
|
||||
/// the schema to the new version.
|
||||
/// - `temporary`: If `true`, the schema upgrade will be performed on a temporary table
|
||||
/// instead of the main database table. This can be useful for testing or other
|
||||
/// advanced use cases.
|
||||
///
|
||||
/// Returns:
|
||||
/// A `Future<Schema>` that completes with the new schema version after the migrations
|
||||
/// have been successfully applied.
|
||||
Future<Schema> upgrade(
|
||||
Schema fromSchema,
|
||||
List<Migration> withMigrations, {
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/persistent_store/persistent_store.dart';
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
import 'package:protevus_http/http.dart';
|
||||
|
@ -6,6 +15,12 @@ import 'package:protevus_http/http.dart';
|
|||
///
|
||||
/// A suggested HTTP status code based on the type of exception will always be available.
|
||||
class QueryException<T> implements HandlerException {
|
||||
/// Creates a new [QueryException] instance.
|
||||
///
|
||||
/// The [event] parameter represents the type of query exception that occurred.
|
||||
/// The [message] parameter is an optional error message describing the exception.
|
||||
/// The [underlyingException] parameter is the underlying exception that caused the query failure.
|
||||
/// The [offendingItems] parameter is a list of strings representing the items that caused the query to fail.
|
||||
QueryException(
|
||||
this.event, {
|
||||
this.message,
|
||||
|
@ -13,35 +28,75 @@ class QueryException<T> implements HandlerException {
|
|||
this.offendingItems,
|
||||
});
|
||||
|
||||
/// Creates a new [QueryException] instance of type [QueryExceptionEvent.input].
|
||||
///
|
||||
/// The [message] parameter is an optional error message describing the exception.
|
||||
/// The [offendingItems] parameter is a list of strings representing the items that caused the query to fail.
|
||||
/// The [underlyingException] parameter is the underlying exception that caused the query failure.
|
||||
QueryException.input(
|
||||
this.message,
|
||||
this.offendingItems, {
|
||||
this.underlyingException,
|
||||
}) : event = QueryExceptionEvent.input;
|
||||
|
||||
/// Creates a new [QueryException] instance of type [QueryExceptionEvent.transport].
|
||||
///
|
||||
/// The [message] parameter is an optional error message describing the exception.
|
||||
/// The [underlyingException] parameter is the underlying exception that caused the query failure.
|
||||
QueryException.transport(this.message, {this.underlyingException})
|
||||
: event = QueryExceptionEvent.transport,
|
||||
offendingItems = null;
|
||||
|
||||
/// Creates a new [QueryException] instance of type [QueryExceptionEvent.conflict].
|
||||
///
|
||||
/// The [message] parameter is an optional error message describing the exception.
|
||||
/// The [offendingItems] parameter is a list of strings representing the items that caused the query to fail.
|
||||
/// The [underlyingException] parameter is the underlying exception that caused the query failure.
|
||||
QueryException.conflict(
|
||||
this.message,
|
||||
this.offendingItems, {
|
||||
this.underlyingException,
|
||||
}) : event = QueryExceptionEvent.conflict;
|
||||
|
||||
/// The optional error message describing the exception.
|
||||
final String? message;
|
||||
|
||||
/// The exception generated by the [PersistentStore] or other mechanism that caused [Query] to fail.
|
||||
///
|
||||
/// This property holds the underlying exception that led to the query failure. It can be used to provide more detailed information about the cause of the failure.
|
||||
final T? underlyingException;
|
||||
|
||||
/// The type of event that caused this exception.
|
||||
///
|
||||
/// This property indicates the specific type of query exception that occurred. The possible values are:
|
||||
///
|
||||
/// - `QueryExceptionEvent.input`: Indicates that the input data used in the query was invalid or caused an issue.
|
||||
/// - `QueryExceptionEvent.transport`: Indicates that the underlying transport mechanism (e.g., database connection) failed.
|
||||
/// - `QueryExceptionEvent.conflict`: Indicates that a unique constraint was violated in the underlying data store.
|
||||
final QueryExceptionEvent event;
|
||||
|
||||
/// The list of strings representing the items that caused the query to fail.
|
||||
///
|
||||
/// This property is only available when the [QueryExceptionEvent] is of type [QueryExceptionEvent.input] or [QueryExceptionEvent.conflict]. It is `null` for other exception types.
|
||||
final List<String>? offendingItems;
|
||||
|
||||
/// Returns a [Response] object based on the type of [QueryException] that was thrown.
|
||||
///
|
||||
/// The response will have the appropriate HTTP status code based on the [QueryExceptionEvent] type, and the response body will contain an error message and, if applicable, a list of offending items that caused the query to fail.
|
||||
@override
|
||||
Response get response {
|
||||
return Response(_getStatus(event), null, _getBody(message, offendingItems));
|
||||
}
|
||||
|
||||
/// Generates the response body for a [QueryException] based on the exception type and details.
|
||||
///
|
||||
/// The response body will contain an "error" field with the error message, and potentially a "detail" field if there are offending items that caused the query to fail.
|
||||
///
|
||||
/// If [message] is `null`, the "error" field will default to "query failed".
|
||||
///
|
||||
/// If [offendingItems] is not `null` and is not empty, a "detail" field will be added to the response body, listing the offending items separated by commas.
|
||||
///
|
||||
/// Returns a map representing the response body.
|
||||
static Map<String, String> _getBody(
|
||||
String? message,
|
||||
List<String>? offendingItems,
|
||||
|
@ -57,6 +112,13 @@ class QueryException<T> implements HandlerException {
|
|||
return body;
|
||||
}
|
||||
|
||||
/// Retrieves the appropriate HTTP status code based on the [QueryExceptionEvent] type.
|
||||
///
|
||||
/// This method maps the different [QueryExceptionEvent] types to their corresponding HTTP status codes:
|
||||
///
|
||||
/// - [QueryExceptionEvent.input]: Returns 400 (Bad Request)
|
||||
/// - [QueryExceptionEvent.transport]: Returns 503 (Service Unavailable)
|
||||
/// - [QueryExceptionEvent.conflict]: Returns 409 (Conflict)
|
||||
static int _getStatus(QueryExceptionEvent event) {
|
||||
switch (event) {
|
||||
case QueryExceptionEvent.input:
|
||||
|
@ -68,11 +130,20 @@ class QueryException<T> implements HandlerException {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a string representation of the [QueryException].
|
||||
///
|
||||
/// The returned string includes the error message and the underlying exception that caused the query failure.
|
||||
@override
|
||||
String toString() => "Query failed: $message. Reason: $underlyingException";
|
||||
}
|
||||
|
||||
/// Categorizations of query failures for [QueryException].
|
||||
///
|
||||
/// This enum defines the different types of query exceptions that can occur when interacting with a [PersistentStore] or performing a [Query]. The enum values are used to indicate the specific cause of a query failure, which helps [Controller]s determine the appropriate HTTP status code to return.
|
||||
///
|
||||
/// - `conflict`: Indicates that a unique constraint was violated in the underlying data store. [Controller]s interpret this exception to return a status code 409 (Conflict) by default.
|
||||
/// - `transport`: Indicates that the underlying transport mechanism (e.g., database connection) failed. [Controller]s interpret this exception to return a status code 503 (Service Unavailable) by default.
|
||||
/// - `input`: Indicates that the input data used in the query was invalid or caused an issue. [Controller]s interpret this exception to return a status code 400 (Bad Request) by default.
|
||||
enum QueryExceptionEvent {
|
||||
/// This event is used when the underlying [PersistentStore] reports that a unique constraint was violated.
|
||||
///
|
||||
|
|
|
@ -1,7 +1,22 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
|
||||
/// Contains binary logic operations to be applied to a [QueryExpression].
|
||||
///
|
||||
/// This class represents a junction of two [QueryExpression] instances, allowing for the creation of more complex
|
||||
/// expressions through the use of logical operators like `and`, `or`, and `not`.
|
||||
///
|
||||
/// You do not create instances of this type directly, but instead it is returned when you invoke methods like
|
||||
/// [QueryExpression.and], [QueryExpression.or], and [QueryExpression.not] on a [QueryExpression].
|
||||
class QueryExpressionJunction<T, InstanceType> {
|
||||
QueryExpressionJunction._(this.lhs);
|
||||
|
||||
|
@ -18,19 +33,47 @@ class QueryExpressionJunction<T, InstanceType> {
|
|||
/// ..where((e) => e.name).equalTo("Bob");
|
||||
///
|
||||
class QueryExpression<T, InstanceType> {
|
||||
/// Creates a new instance of [QueryExpression] with the specified [keyPath].
|
||||
///
|
||||
/// The [keyPath] represents the property path for the expression being created.
|
||||
QueryExpression(this.keyPath);
|
||||
|
||||
/// Creates a new [QueryExpression] by adding a key to the [keyPath] of the provided [original] expression.
|
||||
///
|
||||
/// This method is used to create a new [QueryExpression] by appending a new [ManagedPropertyDescription] to the
|
||||
/// [keyPath] of an existing [QueryExpression]. The resulting [QueryExpression] will have the same [_expression] as
|
||||
/// the [original] expression, but with an updated [keyPath] that includes the additional key.
|
||||
///
|
||||
/// This method is typically used when navigating through nested properties in a data model, allowing you to
|
||||
/// build up complex query expressions by adding new keys to the path.
|
||||
///
|
||||
/// @param original The original [QueryExpression] to use as the base.
|
||||
/// @param byAdding The [ManagedPropertyDescription] to add to the [keyPath] of the original expression.
|
||||
///
|
||||
/// @return A new [QueryExpression] with the updated [keyPath].
|
||||
QueryExpression.byAddingKey(
|
||||
QueryExpression<T, InstanceType> original,
|
||||
ManagedPropertyDescription byAdding,
|
||||
) : keyPath = KeyPath.byAddingKey(original.keyPath, byAdding),
|
||||
_expression = original.expression;
|
||||
|
||||
/// The key path associated with this query expression.
|
||||
///
|
||||
/// The key path represents the property path for the expression being created.
|
||||
final KeyPath keyPath;
|
||||
|
||||
// todo: This needs to be extended to an expr tree
|
||||
/// Gets or sets the predicate expression associated with this query expression.
|
||||
///
|
||||
/// The predicate expression represents the logical conditions that will be applied to the query.
|
||||
/// When setting the expression, you can also invert the expression by using the [not] method.
|
||||
PredicateExpression? get expression => _expression;
|
||||
|
||||
/// Sets the predicate expression associated with this query expression.
|
||||
///
|
||||
/// When setting the expression, you can also invert the expression by using the [not] method.
|
||||
/// If the [_invertNext] flag is set to `true`, the expression will be inverted before being
|
||||
/// assigned to the [_expression] field. After the expression is set, the [_invertNext] flag
|
||||
/// is reset to `false`.
|
||||
set expression(PredicateExpression? expr) {
|
||||
if (_invertNext) {
|
||||
_expression = expr!.inverse;
|
||||
|
@ -40,16 +83,34 @@ class QueryExpression<T, InstanceType> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A flag that indicates whether the next expression should be inverted.
|
||||
///
|
||||
/// When this flag is set to `true`, the next expression that is set using the `expression` property
|
||||
/// will be inverted before being assigned. After the expression is set, the flag is reset to `false`.
|
||||
bool _invertNext = false;
|
||||
|
||||
/// The predicate expression associated with this query expression.
|
||||
///
|
||||
/// The predicate expression represents the logical conditions that will be applied to the query.
|
||||
/// When setting the expression, you can also invert the expression by using the [not] method.
|
||||
PredicateExpression? _expression;
|
||||
|
||||
/// Creates a new [QueryExpressionJunction] instance with the current [QueryExpression] as the left-hand side.
|
||||
///
|
||||
/// This method is used internally to create a new [QueryExpressionJunction] instance that represents the logical junction
|
||||
/// between the current [QueryExpression] and another [QueryExpression].
|
||||
///
|
||||
/// The resulting [QueryExpressionJunction] instance can be used to further build up complex query expressions using
|
||||
/// methods like [and], [or], and [not].
|
||||
///
|
||||
/// @return A new [QueryExpressionJunction] instance with the current [QueryExpression] as the left-hand side.
|
||||
QueryExpressionJunction<T, InstanceType> _createJunction() =>
|
||||
QueryExpressionJunction<T, InstanceType>._(this);
|
||||
|
||||
/// Inverts the next expression.
|
||||
///
|
||||
/// You use this method to apply an inversion to the expression that follows. For example,
|
||||
/// the following example would only return objects where the 'id' is *not* equal to '5'.
|
||||
/// the following example would only return objects where the 'id' is *not* equal to '5':
|
||||
///
|
||||
/// final query = new Query<Employee>()
|
||||
/// ..where((e) => e.name).not.equalTo("Bob");
|
||||
|
@ -122,7 +183,7 @@ class QueryExpression<T, InstanceType> {
|
|||
return _createJunction();
|
||||
}
|
||||
|
||||
/// Adds a like expression to a query.
|
||||
/// Adds a 'like' expression to a query.
|
||||
///
|
||||
/// A query will only return objects where the selected property is like [value].
|
||||
///
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/backing.dart';
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
import 'package:protevus_database/src/managed/relationship_type.dart';
|
||||
|
@ -5,45 +14,148 @@ import 'package:protevus_database/src/query/page.dart';
|
|||
import 'package:protevus_database/src/query/query.dart';
|
||||
import 'package:protevus_database/src/query/sort_descriptor.dart';
|
||||
|
||||
/// A mixin that provides the implementation for the [Query] interface.
|
||||
///
|
||||
/// This mixin is used to add the functionality of the [Query] interface to a class
|
||||
/// that represents a database query. It provides methods for setting and retrieving
|
||||
/// properties of the query, such as the offset, fetch limit, timeout, and value map.
|
||||
/// It also provides methods for creating and managing subqueries, sorting and paging
|
||||
/// the results, and validating the input values.
|
||||
mixin QueryMixin<InstanceType extends ManagedObject>
|
||||
implements Query<InstanceType> {
|
||||
/// The offset of the query, which determines the starting point for the results.
|
||||
///
|
||||
/// The offset is used to skip a certain number of results from the beginning of the
|
||||
/// query. For example, an offset of 10 would skip the first 10 results and return
|
||||
/// the 11th result and all subsequent results.
|
||||
@override
|
||||
int offset = 0;
|
||||
|
||||
/// The maximum number of results to fetch from the database.
|
||||
///
|
||||
/// When the fetch limit is set to a non-zero value, the query will only return
|
||||
/// up to that many results. A fetch limit of 0 (the default) means that there
|
||||
/// is no limit on the number of results that can be returned.
|
||||
@override
|
||||
int fetchLimit = 0;
|
||||
|
||||
/// The maximum number of seconds the query is allowed to run before it is terminated.
|
||||
///
|
||||
/// The timeout is used to ensure that queries don't run indefinitely, which could
|
||||
/// cause issues in a production environment. If a query takes longer than the
|
||||
/// specified timeout, it will be automatically terminated and an error will be
|
||||
/// returned.
|
||||
@override
|
||||
int timeoutInSeconds = 30;
|
||||
|
||||
/// Determines whether the query can modify all instances of the entity, regardless of
|
||||
/// any filtering or sorting criteria that may have been applied.
|
||||
///
|
||||
/// When this property is set to `true`, the query will be able to modify all instances
|
||||
/// of the entity, even if the query has filters or sorting applied that would normally
|
||||
/// limit the set of instances that would be modified.
|
||||
///
|
||||
/// This property is typically used in administrative or management scenarios, where
|
||||
/// the user may need to perform a global modification of all instances of an entity,
|
||||
/// regardless of any specific criteria.
|
||||
@override
|
||||
bool canModifyAllInstances = false;
|
||||
|
||||
/// The value map associated with this query.
|
||||
///
|
||||
/// The value map is a dictionary that maps property names to their corresponding values.
|
||||
/// This map is used to specify the values to be inserted or updated when the query is executed.
|
||||
@override
|
||||
Map<String, dynamic>? valueMap;
|
||||
|
||||
/// The predicate of the query, which determines the conditions that must be met for a record to be included in the results.
|
||||
///
|
||||
/// The predicate is a boolean expression that is evaluated for each record in the database. Only records for which the predicate
|
||||
/// evaluates to `true` will be included in the query results.
|
||||
@override
|
||||
QueryPredicate? predicate;
|
||||
|
||||
/// The sort predicate of the query, which determines the order in which the results of the query are returned.
|
||||
///
|
||||
/// The sort predicate is a list of `QuerySortDescriptor` objects, each of which specifies a property to sort by and the
|
||||
/// direction of the sort (ascending or descending). The results of the query will be sorted according to the order
|
||||
/// of the sort descriptors in the predicate.
|
||||
@override
|
||||
QuerySortPredicate? sortPredicate;
|
||||
|
||||
/// The page descriptor for this query, which determines the ordering and
|
||||
/// bounding values for the results.
|
||||
///
|
||||
/// The page descriptor is used to paginate the results of the query, allowing
|
||||
/// the client to retrieve the results in smaller chunks rather than all at
|
||||
/// once. It specifies the property to sort the results by, the sort order,
|
||||
/// and an optional bounding value to limit the results to a specific range.
|
||||
QueryPage? pageDescriptor;
|
||||
|
||||
/// The list of sort descriptors for this query.
|
||||
///
|
||||
/// The sort descriptors specify the properties to sort the query results by
|
||||
/// and the sort order (ascending or descending) for each property.
|
||||
final List<QuerySortDescriptor> sortDescriptors = <QuerySortDescriptor>[];
|
||||
|
||||
/// A dictionary that maps ManagedRelationshipDescription objects to Query objects.
|
||||
///
|
||||
/// This dictionary is used to store the subqueries that are created when the [join] method is called on the QueryMixin.
|
||||
/// Each key in the dictionary represents a relationship in the database, and the corresponding value is the subquery
|
||||
/// that was created to fetch the data for that relationship.
|
||||
final Map<ManagedRelationshipDescription, Query> subQueries = {};
|
||||
|
||||
/// The parent query of this query, if any.
|
||||
///
|
||||
/// This property is used to keep track of the parent query when this query is a
|
||||
/// subquery created by the [join] method. It is used to ensure that the subquery
|
||||
/// does not create a cyclic join.
|
||||
QueryMixin? _parentQuery;
|
||||
|
||||
/// A list of `QueryExpression` objects that represent the expressions used in the query.
|
||||
///
|
||||
/// The `QueryExpression` objects define the conditions that must be met for a record to be included in the query results.
|
||||
/// Each expression represents a single condition, and the list of expressions is combined using the logical `AND` operator
|
||||
/// to form the final predicate for the query.
|
||||
List<QueryExpression<dynamic, dynamic>> expressions = [];
|
||||
|
||||
/// The value object associated with this query.
|
||||
///
|
||||
/// This property represents the entity instance that will be used as the
|
||||
/// values for the query. It is used to set the values that will be inserted
|
||||
/// or updated when the query is executed.
|
||||
InstanceType? _valueObject;
|
||||
|
||||
/// The list of properties to fetch for this query.
|
||||
///
|
||||
/// This property is initialized to the entity's default properties if it has not
|
||||
/// been explicitly set. The properties are represented as `KeyPath` objects, which
|
||||
/// encapsulate the path to the property within the entity.
|
||||
List<KeyPath>? _propertiesToFetch;
|
||||
|
||||
/// The list of properties to fetch for this query.
|
||||
///
|
||||
/// This property is initialized to the entity's default properties if it has not
|
||||
/// been explicitly set. The properties are represented as `KeyPath` objects, which
|
||||
/// encapsulate the path to the property within the entity.
|
||||
List<KeyPath> get propertiesToFetch =>
|
||||
_propertiesToFetch ??
|
||||
entity.defaultProperties!
|
||||
.map((k) => KeyPath(entity.properties[k]))
|
||||
.toList();
|
||||
|
||||
/// The value object associated with this query.
|
||||
///
|
||||
/// This property represents the entity instance that will be used as the
|
||||
/// values for the query. It is used to set the values that will be inserted
|
||||
/// or updated when the query is executed.
|
||||
///
|
||||
/// If the `_valueObject` is `null`, it is initialized to a new instance of the
|
||||
/// entity, and its `backing` property is set to a new `ManagedBuilderBacking`
|
||||
/// object that is created from the entity and the current `backing` of the
|
||||
/// `_valueObject`.
|
||||
///
|
||||
/// The initialized `_valueObject` is then returned.
|
||||
@override
|
||||
InstanceType get values {
|
||||
if (_valueObject == null) {
|
||||
|
@ -56,6 +168,14 @@ mixin QueryMixin<InstanceType extends ManagedObject>
|
|||
return _valueObject!;
|
||||
}
|
||||
|
||||
/// Sets the value object associated with this query.
|
||||
///
|
||||
/// If the [obj] parameter is `null`, the `_valueObject` property is set to `null`.
|
||||
/// Otherwise, a new instance of the entity is created and its `backing` property
|
||||
/// is set to a new `ManagedBuilderBacking` object that is created from the entity
|
||||
/// and the `backing` of the provided `obj`.
|
||||
///
|
||||
/// The initialized `_valueObject` is then assigned to the `_valueObject` property.
|
||||
@override
|
||||
set values(InstanceType? obj) {
|
||||
if (obj == null) {
|
||||
|
@ -68,6 +188,16 @@ mixin QueryMixin<InstanceType extends ManagedObject>
|
|||
);
|
||||
}
|
||||
|
||||
/// Adds a where clause to the query, which filters the results based on a specified property.
|
||||
///
|
||||
/// The `propertyIdentifier` parameter is a function that takes an instance of the `InstanceType` entity
|
||||
/// and returns a value of type `T` that represents the property to filter on.
|
||||
///
|
||||
/// If the `propertyIdentifier` function references more than one property, an `ArgumentError` will be
|
||||
/// thrown.
|
||||
///
|
||||
/// The returned `QueryExpression` object represents the expression that will be used to filter the results
|
||||
/// of the query. You can call methods on this object to specify the conditions for the filter.
|
||||
@override
|
||||
QueryExpression<T, InstanceType> where<T>(
|
||||
T Function(InstanceType x) propertyIdentifier,
|
||||
|
@ -84,6 +214,23 @@ mixin QueryMixin<InstanceType extends ManagedObject>
|
|||
return expr;
|
||||
}
|
||||
|
||||
/// Joins a related object or set of objects to the current query.
|
||||
///
|
||||
/// This method is used to fetch related objects or sets of objects as part of the
|
||||
/// current query. The related objects or sets are specified using a function that
|
||||
/// takes an instance of the current entity and returns either a single related
|
||||
/// object or a set of related objects.
|
||||
///
|
||||
/// The [object] parameter is a function that takes an instance of the current entity
|
||||
/// and returns a related object of type `T`. The [set] parameter is a function that
|
||||
/// takes an instance of the current entity and returns a set of related objects of
|
||||
/// type `T`.
|
||||
///
|
||||
/// The return value of this method is a new `Query<T>` object that represents the
|
||||
/// subquery for the related objects or set of objects.
|
||||
///
|
||||
/// Throws a `StateError` if the same property is joined more than once, or if the
|
||||
/// join would create a cyclic relationship.
|
||||
@override
|
||||
Query<T> join<T extends ManagedObject>({
|
||||
T? Function(InstanceType x)? object,
|
||||
|
@ -95,6 +242,25 @@ mixin QueryMixin<InstanceType extends ManagedObject>
|
|||
return _createSubquery<T>(desc);
|
||||
}
|
||||
|
||||
/// Sets the page descriptor for the query, which determines the ordering and
|
||||
/// bounding values for the results.
|
||||
///
|
||||
/// The page descriptor is used to paginate the results of the query, allowing
|
||||
/// the client to retrieve the results in smaller chunks rather than all at
|
||||
/// once. It specifies the property to sort the results by, the sort order,
|
||||
/// and an optional bounding value to limit the results to a specific range.
|
||||
///
|
||||
/// The [propertyIdentifier] parameter is a function that takes an instance of
|
||||
/// the `InstanceType` entity and returns a value of type `T` that represents
|
||||
/// the property to sort the results by.
|
||||
///
|
||||
/// The [order] parameter specifies the sort order, which can be either
|
||||
/// `QuerySortOrder.ascending` or `QuerySortOrder.descending`.
|
||||
///
|
||||
/// The [boundingValue] parameter is an optional value that can be used to
|
||||
/// limit the results to a specific range. Only results where the value of the
|
||||
/// specified property is greater than or equal to the bounding value will be
|
||||
/// returned.
|
||||
@override
|
||||
void pageBy<T>(
|
||||
T Function(InstanceType x) propertyIdentifier,
|
||||
|
@ -106,6 +272,16 @@ mixin QueryMixin<InstanceType extends ManagedObject>
|
|||
QueryPage(order, attribute.name, boundingValue: boundingValue);
|
||||
}
|
||||
|
||||
/// Adds a sort descriptor to the query, which determines the order in which the results are returned.
|
||||
///
|
||||
/// The [propertyIdentifier] parameter is a function that takes an instance of the `InstanceType` entity
|
||||
/// and returns a value of type `T` that represents the property to sort the results by.
|
||||
///
|
||||
/// The [order] parameter specifies the sort order, which can be either `QuerySortOrder.ascending` or
|
||||
/// `QuerySortOrder.descending`.
|
||||
///
|
||||
/// This method adds a `QuerySortDescriptor` to the `sortDescriptors` list of the query. The descriptor
|
||||
/// specifies the name of the property to sort by and the sort order to use.
|
||||
@override
|
||||
void sortBy<T>(
|
||||
T Function(InstanceType x) propertyIdentifier,
|
||||
|
@ -116,6 +292,21 @@ mixin QueryMixin<InstanceType extends ManagedObject>
|
|||
sortDescriptors.add(QuerySortDescriptor(attribute.name, order));
|
||||
}
|
||||
|
||||
/// Sets the properties to be fetched by the query.
|
||||
///
|
||||
/// This method allows you to specify the properties of the entity that should be
|
||||
/// fetched by the query. The `propertyIdentifiers` parameter is a function that
|
||||
/// takes an instance of the `InstanceType` entity and returns a list of properties
|
||||
/// to be fetched.
|
||||
///
|
||||
/// Note that you cannot select has-many or has-one relationship properties using
|
||||
/// this method. Instead, you should use the `join` method to fetch related objects.
|
||||
///
|
||||
/// If you attempt to select a has-many or has-one relationship property, an
|
||||
/// `ArgumentError` will be thrown.
|
||||
///
|
||||
/// The specified properties are represented as `KeyPath` objects, which encapsulate
|
||||
/// the path to the property within the entity.
|
||||
@override
|
||||
void returningProperties(
|
||||
List<dynamic> Function(InstanceType x) propertyIdentifiers,
|
||||
|
@ -137,6 +328,25 @@ mixin QueryMixin<InstanceType extends ManagedObject>
|
|||
_propertiesToFetch = entity.identifyProperties(propertyIdentifiers);
|
||||
}
|
||||
|
||||
/// Validates the input values for the query.
|
||||
///
|
||||
/// This method is used to validate the values associated with the query before
|
||||
/// the query is executed. It checks the validity of the values based on the
|
||||
/// specified `Validating` operation (`insert` or `update`).
|
||||
///
|
||||
/// If the `valueMap` is `null`, the method will call the appropriate method
|
||||
/// (`willInsert` or `willUpdate`) on the `values` object to prepare it for
|
||||
/// the specified operation. It then calls the `validate` method on the `values`
|
||||
/// object, passing the specified `Validating` operation as the `forEvent`
|
||||
/// parameter.
|
||||
///
|
||||
/// If the validation context returned by the `validate` method is not valid
|
||||
/// (i.e., `ctx.isValid` is `false`), the method will throw a `ValidationException`
|
||||
/// with the validation errors.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `op`: The `Validating` operation to perform (either `Validating.insert`
|
||||
/// or `Validating.update`).
|
||||
void validateInput(Validating op) {
|
||||
if (valueMap == null) {
|
||||
if (op == Validating.insert) {
|
||||
|
@ -152,6 +362,28 @@ mixin QueryMixin<InstanceType extends ManagedObject>
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a subquery for the specified relationship.
|
||||
///
|
||||
/// This method is used to create a subquery for a related object or set of objects
|
||||
/// that are part of the current query. The subquery is created using the specified
|
||||
/// [fromRelationship], which is a `ManagedRelationshipDescription` object that
|
||||
/// describes the relationship between the current entity and the related entity.
|
||||
///
|
||||
/// If the same property is joined more than once, a `StateError` will be thrown.
|
||||
/// If the join would create a cyclic relationship, a `StateError` will also be
|
||||
/// thrown, with a message that suggests joining on a different property.
|
||||
///
|
||||
/// The returned `Query<T>` object represents the subquery for the related objects
|
||||
/// or set of objects. This subquery can be further customized using the methods
|
||||
/// provided by the `Query` interface.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `fromRelationship`: The `ManagedRelationshipDescription` object that
|
||||
/// describes the relationship between the current entity and the related entity.
|
||||
///
|
||||
/// Returns:
|
||||
/// A `Query<T>` object that represents the subquery for the related objects or
|
||||
/// set of objects.
|
||||
Query<T> _createSubquery<T extends ManagedObject>(
|
||||
ManagedRelationshipDescription fromRelationship,
|
||||
) {
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
|
||||
/// A description of a page of results to be applied to a [Query].
|
||||
|
@ -16,7 +25,7 @@ import 'package:protevus_database/src/query/query.dart';
|
|||
class QueryPage {
|
||||
QueryPage(this.order, this.propertyName, {this.boundingValue});
|
||||
|
||||
/// The order in which rows should be in before the page of values is searched for.
|
||||
/// The order in which rows should be sorted before the page of values is searched for.
|
||||
///
|
||||
/// The rows of a database table will be sorted according to this order on the column backing [propertyName] prior
|
||||
/// to this page being fetched.
|
||||
|
@ -24,7 +33,7 @@ class QueryPage {
|
|||
|
||||
/// The property of the model object to page on.
|
||||
///
|
||||
/// This property must have an inherent order, such as an [int] or [DateTime]. The database must be able to compare the values of this property using comparison operator '<' and '>'.
|
||||
/// This property must have an inherent order, such as an [int] or [DateTime]. The database must be able to compare the values of this property using comparison operators like '<' and '>'.
|
||||
String propertyName;
|
||||
|
||||
/// The point within an ordered set of result values in which rows will begin being fetched from.
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/persistent_store/persistent_store.dart';
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
|
||||
|
@ -13,12 +22,12 @@ import 'package:protevus_database/src/query/query.dart';
|
|||
///
|
||||
/// var predicate = new QueryPredicate("x = @xValue", {"xValue" : 5});
|
||||
class QueryPredicate {
|
||||
/// Default constructor
|
||||
/// Default constructor for [QueryPredicate].
|
||||
///
|
||||
/// The [format] and [parameters] of this predicate. [parameters] may be null.
|
||||
QueryPredicate(this.format, [this.parameters = const {}]);
|
||||
|
||||
/// Creates an empty predicate.
|
||||
/// Creates an empty [QueryPredicate] instance.
|
||||
///
|
||||
/// The format string is the empty string and parameters is the empty map.
|
||||
QueryPredicate.empty()
|
||||
|
@ -27,9 +36,11 @@ class QueryPredicate {
|
|||
|
||||
/// Combines [predicates] with 'AND' keyword.
|
||||
///
|
||||
/// The [format] of the return value is produced by joining together each [predicates]
|
||||
/// [format] string with 'AND'. Each [parameters] from individual [predicates] is combined
|
||||
/// into the returned [parameters].
|
||||
/// This factory method takes an [Iterable] of [QueryPredicate] instances and combines them
|
||||
/// using the 'AND' keyword. The resulting [QueryPredicate] will have a [format] string that
|
||||
/// is the concatenation of each individual [QueryPredicate]'s [format] string, separated by
|
||||
/// the 'AND' keyword. The [parameters] map will be a combination of all the individual
|
||||
/// [QueryPredicate]'s [parameters] maps.
|
||||
///
|
||||
/// If there are duplicate parameter names in [predicates], they will be disambiguated by suffixing
|
||||
/// the parameter name in both [format] and [parameters] with a unique integer.
|
||||
|
@ -37,8 +48,18 @@ class QueryPredicate {
|
|||
/// If [predicates] is null or empty, an empty predicate is returned. If [predicates] contains only
|
||||
/// one predicate, that predicate is returned.
|
||||
factory QueryPredicate.and(Iterable<QueryPredicate> predicates) {
|
||||
/// Filters the provided [predicates] to only include those with a non-empty [QueryPredicate.format].
|
||||
///
|
||||
/// This method creates a new list containing only the [QueryPredicate] instances from the provided [Iterable]
|
||||
/// that have a non-empty [QueryPredicate.format] string.
|
||||
///
|
||||
/// @param predicates The [Iterable] of [QueryPredicate] instances to filter.
|
||||
/// @return A new [List] containing the [QueryPredicate] instances from [predicates] that have a non-empty [QueryPredicate.format].
|
||||
final predicateList = predicates.where((p) => p.format.isNotEmpty).toList();
|
||||
|
||||
/// If the provided [predicateList] is empty, this method returns an empty [QueryPredicate].
|
||||
///
|
||||
/// If the [predicateList] contains only a single predicate, this method returns that single predicate.
|
||||
if (predicateList.isEmpty) {
|
||||
return QueryPredicate.empty();
|
||||
}
|
||||
|
@ -47,10 +68,49 @@ class QueryPredicate {
|
|||
return predicateList.first;
|
||||
}
|
||||
|
||||
// If we have duplicate keys anywhere, we need to disambiguate them.
|
||||
/// If there are duplicate parameter names in [predicates], this variable is used to
|
||||
/// disambiguate them by suffixing the parameter name in both [format] and [parameters]
|
||||
/// with a unique integer.
|
||||
int dupeCounter = 0;
|
||||
|
||||
/// Stores the format strings for each predicate in the `predicateList`.
|
||||
///
|
||||
/// This list is used to build the final `format` string for the combined `QueryPredicate`.
|
||||
final allFormatStrings = [];
|
||||
|
||||
/// A map that stores the values to replace in the [format] string of a [QueryPredicate] at execution time.
|
||||
///
|
||||
/// The keys of this map will be searched for in the [format] string of the [QueryPredicate] and replaced with
|
||||
/// their corresponding values. This allows the [QueryPredicate] to be parameterized, rather than having
|
||||
/// dynamic values directly embedded in the [format] string.
|
||||
final valueMap = <String, dynamic>{};
|
||||
|
||||
/// Combines the provided [QueryPredicate] instances using the 'AND' keyword.
|
||||
///
|
||||
/// This method takes an [Iterable] of [QueryPredicate] instances and combines them
|
||||
/// using the 'AND' keyword. The resulting [QueryPredicate] will have a [format] string that
|
||||
/// is the concatenation of each individual [QueryPredicate]'s [format] string, separated by
|
||||
/// the 'AND' keyword. The [parameters] map will be a combination of all the individual
|
||||
/// [QueryPredicate]'s [parameters] maps.
|
||||
///
|
||||
/// If there are duplicate parameter names in [predicates], they will be disambiguated by suffixing
|
||||
/// the parameter name in both [format] and [parameters] with a unique integer.
|
||||
///
|
||||
/// If [predicates] is null or empty, an empty predicate is returned. If [predicates] contains only
|
||||
/// one predicate, that predicate is returned.
|
||||
///
|
||||
/// The code performs the following steps:
|
||||
/// 1. Filters the provided [predicates] to only include those with a non-empty [QueryPredicate.format].
|
||||
/// 2. If the filtered list is empty, returns an empty [QueryPredicate].
|
||||
/// 3. If the filtered list contains only one predicate, returns that predicate.
|
||||
/// 4. Initializes a `dupeCounter` variable to keep track of duplicate parameter names.
|
||||
/// 5. Iterates through the filtered list of [QueryPredicate] instances:
|
||||
/// - If there are any duplicate parameter names, it replaces them in the `format` string and
|
||||
/// the `parameters` map with a unique identifier.
|
||||
/// - Adds the modified `format` string to the `allFormatStrings` list.
|
||||
/// - Adds the `parameters` map (with any modifications) to the `valueMap`.
|
||||
/// 6. Constructs the final `predicateFormat` string by joining the `allFormatStrings` with the 'AND' keyword.
|
||||
/// 7. Returns a new [QueryPredicate] instance with the `predicateFormat` and the `valueMap`.
|
||||
for (final predicate in predicateList) {
|
||||
final duplicateKeys = predicate.parameters.keys
|
||||
.where((k) => valueMap.keys.contains(k))
|
||||
|
@ -80,7 +140,7 @@ class QueryPredicate {
|
|||
return QueryPredicate(predicateFormat, valueMap);
|
||||
}
|
||||
|
||||
/// The string format of the this predicate.
|
||||
/// The string format of this predicate.
|
||||
///
|
||||
/// This is the predicate text. Do not write dynamic values directly to the format string, instead, prefix an identifier with @
|
||||
/// and add that identifier to the [parameters] map.
|
||||
|
@ -88,12 +148,24 @@ class QueryPredicate {
|
|||
|
||||
/// A map of values to replace in the format string at execution time.
|
||||
///
|
||||
/// Input values should not be in the format string, but instead provided in this map.
|
||||
/// Keys of this map will be searched for in the format string and be replaced by the value in this map.
|
||||
/// This map contains the parameter values that will be used to replace placeholders (prefixed with '@') in the [format] string when the [QueryPredicate] is executed. The keys of this map correspond to the parameter names in the [format] string, and the values are the actual values to be substituted.
|
||||
///
|
||||
/// For example, if the [format] string is `"x = @xValue AND y > @yValue"`, the [parameters] map might look like `{"xValue": 5, "yValue": 10}`. When the [QueryPredicate] is executed, the placeholders `@xValue` and `@yValue` in the [format] string will be replaced with the corresponding values from the [parameters] map.
|
||||
///
|
||||
/// Input values should not be directly embedded in the [format] string, but instead provided in this [parameters] map. This allows the [QueryPredicate] to be parameterized, rather than having dynamic values directly included in the [format] string.
|
||||
Map<String, dynamic> parameters;
|
||||
}
|
||||
|
||||
/// The operator in a comparison matcher.
|
||||
/// The operator used in a comparison-based predicate expression.
|
||||
///
|
||||
/// The available operators are:
|
||||
///
|
||||
/// - `lessThan`: Less than
|
||||
/// - `greaterThan`: Greater than
|
||||
/// - `notEqual`: Not equal to
|
||||
/// - `lessThanEqualTo`: Less than or equal to
|
||||
/// - `greaterThanEqualTo`: Greater than or equal to
|
||||
/// - `equalTo`: Equal to
|
||||
enum PredicateOperator {
|
||||
lessThan,
|
||||
greaterThan,
|
||||
|
@ -103,17 +175,56 @@ enum PredicateOperator {
|
|||
equalTo
|
||||
}
|
||||
|
||||
/// A comparison-based predicate expression that represents a comparison between a value and a predicate operator.
|
||||
///
|
||||
/// This class encapsulates a comparison between a `value` and a `PredicateOperator`. It provides a way to represent
|
||||
/// comparison-based predicates in a query, such as "x < 5" or "y >= 10".
|
||||
///
|
||||
/// The `value` property represents the value being compared, which can be of any type.
|
||||
/// The `operator` property represents the comparison operator, which is defined by the `PredicateOperator` enum.
|
||||
///
|
||||
/// The `inverse` getter returns a new `ComparisonExpression` with the opposite `PredicateOperator`. This allows you
|
||||
/// to easily negate a comparison expression, such as changing "x < 5" to "x >= 5".
|
||||
///
|
||||
/// The `inverseOperator` getter returns the opposite `PredicateOperator` for the current `operator`. This is used
|
||||
/// to implement the `inverse` getter.
|
||||
class ComparisonExpression implements PredicateExpression {
|
||||
/// Constructs a new instance of [ComparisonExpression].
|
||||
///
|
||||
/// The [value] parameter represents the value being compared, which can be of any type.
|
||||
/// The [operator] parameter represents the comparison operator, which is defined by the [PredicateOperator] enum.
|
||||
const ComparisonExpression(this.value, this.operator);
|
||||
|
||||
/// The value being compared in the comparison-based predicate expression.
|
||||
///
|
||||
/// This property represents the value that is being compared to the predicate operator in the [ComparisonExpression].
|
||||
/// The value can be of any type.
|
||||
final dynamic value;
|
||||
|
||||
/// The comparison operator used in the comparison-based predicate expression.
|
||||
///
|
||||
/// This property represents the comparison operator used in the [ComparisonExpression]. The operator is defined by
|
||||
/// the [PredicateOperator] enum, which includes options such as "less than", "greater than", "equal to", and others.
|
||||
final PredicateOperator operator;
|
||||
|
||||
/// Returns a new [ComparisonExpression] with the opposite [PredicateOperator] to the current one.
|
||||
///
|
||||
/// This getter creates a new [ComparisonExpression] instance with the same [value] as the current instance,
|
||||
/// but with the [PredicateOperator] reversed. For example, if the current [operator] is [PredicateOperator.lessThan],
|
||||
/// the returned [ComparisonExpression] will have an [operator] of [PredicateOperator.greaterThanEqualTo].
|
||||
///
|
||||
/// This allows you to easily negate a comparison expression, such as changing "x < 5" to "x >= 5".
|
||||
@override
|
||||
PredicateExpression get inverse {
|
||||
return ComparisonExpression(value, inverseOperator);
|
||||
}
|
||||
|
||||
/// Returns the opposite [PredicateOperator] for the current [operator].
|
||||
///
|
||||
/// This getter is used to implement the `inverse` getter of the [ComparisonExpression] class.
|
||||
/// It returns the opposite operator for the current [operator]. For example, if the current
|
||||
/// [operator] is [PredicateOperator.lessThan], this getter will return
|
||||
/// [PredicateOperator.greaterThanEqualTo].
|
||||
PredicateOperator get inverseOperator {
|
||||
switch (operator) {
|
||||
case PredicateOperator.lessThan:
|
||||
|
@ -132,50 +243,181 @@ class ComparisonExpression implements PredicateExpression {
|
|||
}
|
||||
}
|
||||
|
||||
/// The operator in a string matcher.
|
||||
/// The operator used in a string-based predicate expression.
|
||||
///
|
||||
/// The available operators are:
|
||||
///
|
||||
/// - `beginsWith`: The string must begin with the specified value.
|
||||
/// - `contains`: The string must contain the specified value.
|
||||
/// - `endsWith`: The string must end with the specified value.
|
||||
/// - `equals`: The string must be exactly equal to the specified value.
|
||||
enum PredicateStringOperator { beginsWith, contains, endsWith, equals }
|
||||
|
||||
/// A predicate contains instructions for filtering rows when performing a [Query].
|
||||
///
|
||||
/// Predicates currently are the WHERE clause in a SQL statement and are used verbatim
|
||||
/// by the [PersistentStore]. In general, you should use [Query.where] instead of using this class directly, as [Query.where] will
|
||||
/// use the underlying [PersistentStore] to generate a [QueryPredicate] for you.
|
||||
///
|
||||
/// A predicate has a format and parameters. The format is the [String] that comes after WHERE in a SQL query. The format may
|
||||
/// have parameterized values, for which the corresponding value is in the [parameters] map. A parameter is prefixed with '@' in the format string. Currently,
|
||||
/// the format string's parameter syntax is defined by the [PersistentStore] it is used on. An example of that format:
|
||||
///
|
||||
/// var predicate = new QueryPredicate("x = @xValue", {"xValue" : 5});
|
||||
abstract class PredicateExpression {
|
||||
/// Returns a new instance of the [PredicateExpression] with the opposite condition.
|
||||
///
|
||||
/// This getter creates and returns a new instance of the [PredicateExpression] with the opposite condition to the current one.
|
||||
/// For example, if the current expression is "x < 5", the returned expression would be "x >= 5".
|
||||
PredicateExpression get inverse;
|
||||
}
|
||||
|
||||
/// A predicate expression that represents a range comparison.
|
||||
///
|
||||
/// This class encapsulates a range comparison between a `lhs` (left-hand side) value and an `rhs` (right-hand side) value.
|
||||
/// It provides a way to represent range-based predicates in a query, such as "x between 5 and 10" or "y not between 20 and 30".
|
||||
///
|
||||
/// The `lhs` and `rhs` properties represent the left-hand side and right-hand side values of the range, respectively.
|
||||
/// The `within` property determines whether the comparison is a "within" or "not within" range.
|
||||
///
|
||||
/// The `inverse` getter returns a new `RangeExpression` with the opposite `within` value. This allows you
|
||||
/// to easily negate a range expression, such as changing "x between 5 and 10" to "x not between 5 and 10".
|
||||
class RangeExpression implements PredicateExpression {
|
||||
/// Constructs a new instance of [RangeExpression].
|
||||
///
|
||||
/// The [lhs] parameter represents the left-hand side value of the range comparison.
|
||||
/// The [rhs] parameter represents the right-hand side value of the range comparison.
|
||||
/// The [within] parameter determines whether the comparison is a "within" or "not within" range.
|
||||
/// If [within] is `true` (the default), the range expression will match values that are within the range.
|
||||
/// If [within] is `false`, the range expression will match values that are not within the range.
|
||||
const RangeExpression(this.lhs, this.rhs, {this.within = true});
|
||||
|
||||
/// Determines whether the range comparison is a "within" or "not within" range.
|
||||
///
|
||||
/// If `true` (the default), the range expression will match values that are within the range.
|
||||
/// If `false`, the range expression will match values that are not within the range.
|
||||
final bool within;
|
||||
|
||||
/// The left-hand side value of the range comparison.
|
||||
///
|
||||
/// This property represents the left-hand side value of the range comparison in the [RangeExpression]. The type of this value
|
||||
/// can be anything, as it is represented by the generic `dynamic` type.
|
||||
final dynamic lhs;
|
||||
|
||||
/// The right-hand side value of the range comparison.
|
||||
///
|
||||
/// This property represents the right-hand side value of the range comparison in the [RangeExpression]. The type of this value
|
||||
/// can be anything, as it is represented by the generic `dynamic` type.
|
||||
final dynamic rhs;
|
||||
|
||||
/// Returns a new instance of the [RangeExpression] with the opposite `within` condition.
|
||||
///
|
||||
/// This getter creates and returns a new instance of the [RangeExpression] with the opposite `within` condition to the current one.
|
||||
/// For example, if the current `within` value is `true`, the returned expression would have `within` set to `false`.
|
||||
/// This allows you to easily negate a range expression, such as changing "x between 5 and 10" to "x not between 5 and 10".
|
||||
@override
|
||||
PredicateExpression get inverse {
|
||||
return RangeExpression(lhs, rhs, within: !within);
|
||||
}
|
||||
}
|
||||
|
||||
/// A predicate expression that checks if a value is null or not null.
|
||||
///
|
||||
/// This class encapsulates a null check predicate expression, which can be used to
|
||||
/// filter data based on whether a value is null or not null.
|
||||
///
|
||||
/// The [shouldBeNull] parameter determines whether the expression checks for a null
|
||||
/// value (if true) or a non-null value (if false).
|
||||
///
|
||||
/// The [inverse] getter returns a new [NullCheckExpression] with the opposite
|
||||
/// [shouldBeNull] value. This allows you to easily negate a null check expression,
|
||||
/// such as changing "x is null" to "x is not null".
|
||||
class NullCheckExpression implements PredicateExpression {
|
||||
/// Constructs a new instance of [NullCheckExpression].
|
||||
///
|
||||
/// The [shouldBeNull] parameter determines whether the expression checks for a null
|
||||
/// value (if `true`) or a non-null value (if `false`). The default value is `true`,
|
||||
/// which means the expression will check for a null value.
|
||||
const NullCheckExpression({this.shouldBeNull = true});
|
||||
|
||||
/// Determines whether the expression checks for a null
|
||||
/// value (if `true`) or a non-null value (if `false`). The default value is `true`,
|
||||
/// which means the expression will check for a null value.
|
||||
final bool shouldBeNull;
|
||||
|
||||
/// Returns a new instance of the [NullCheckExpression] with the opposite `shouldBeNull` condition.
|
||||
///
|
||||
/// This getter creates and returns a new instance of the [NullCheckExpression] with the opposite `shouldBeNull` condition to the current one.
|
||||
/// For example, if the current `shouldBeNull` value is `true`, the returned expression would have `shouldBeNull` set to `false`.
|
||||
/// This allows you to easily negate a null check expression, such as changing "x is null" to "x is not null".
|
||||
@override
|
||||
PredicateExpression get inverse {
|
||||
return NullCheckExpression(shouldBeNull: !shouldBeNull);
|
||||
}
|
||||
}
|
||||
|
||||
/// A predicate expression that checks if a value is a member of a set.
|
||||
///
|
||||
/// This class encapsulates a set membership predicate expression, which can be used to
|
||||
/// filter data based on whether a value is a member of a set of values.
|
||||
///
|
||||
/// The [values] parameter represents the set of values to check for membership.
|
||||
/// The [within] parameter determines whether the expression checks for membership
|
||||
/// (if `true`) or non-membership (if `false`). The default value is `true`, which
|
||||
/// means the expression will check for membership.
|
||||
///
|
||||
/// The [inverse] getter returns a new [SetMembershipExpression] with the opposite
|
||||
/// [within] value. This allows you to easily negate a set membership expression,
|
||||
/// such as changing "x is in the set" to "x is not in the set".
|
||||
class SetMembershipExpression implements PredicateExpression {
|
||||
/// Constructs a new instance of [SetMembershipExpression].
|
||||
///
|
||||
/// The [values] parameter represents the set of values to check for membership.
|
||||
/// The [within] parameter determines whether the expression checks for membership
|
||||
/// (if `true`) or non-membership (if `false`). The default value is `true`, which
|
||||
/// means the expression will check for membership.
|
||||
const SetMembershipExpression(this.values, {this.within = true});
|
||||
|
||||
/// The set of values to check for membership.
|
||||
final List<dynamic> values;
|
||||
|
||||
/// Determines whether the expression checks for membership
|
||||
/// (if `true`) or non-membership (if `false`). The default value is `true`,
|
||||
/// which means the expression will check for membership.
|
||||
final bool within;
|
||||
|
||||
/// Returns a new instance of the [SetMembershipExpression] with the opposite `within` condition.
|
||||
///
|
||||
/// This getter creates and returns a new instance of the [SetMembershipExpression] with the opposite `within` condition to the current one.
|
||||
/// For example, if the current `within` value is `true`, the returned expression would have `within` set to `false`.
|
||||
/// This allows you to easily negate a set membership expression, such as changing "x is in the set" to "x is not in the set".
|
||||
@override
|
||||
PredicateExpression get inverse {
|
||||
return SetMembershipExpression(values, within: !within);
|
||||
}
|
||||
}
|
||||
|
||||
/// A predicate expression that represents a string-based comparison.
|
||||
///
|
||||
/// This class encapsulates a string-based predicate expression, which can be used to
|
||||
/// filter data based on string comparisons such as "begins with", "contains", "ends with", or "equals".
|
||||
///
|
||||
/// The [value] property represents the string value to compare against.
|
||||
/// The [operator] property represents the string comparison operator, which is defined by the [PredicateStringOperator] enum.
|
||||
/// The [caseSensitive] property determines whether the comparison should be case-sensitive or not.
|
||||
/// The [invertOperator] property determines whether the operator should be inverted (e.g., "not contains" instead of "contains").
|
||||
/// The [allowSpecialCharacters] property determines whether special characters should be allowed in the string comparison.
|
||||
///
|
||||
/// The [inverse] getter returns a new [StringExpression] with the opposite [invertOperator] value. This allows you
|
||||
/// to easily negate a string expression, such as changing "x contains 'abc'" to "x does not contain 'abc'".
|
||||
class StringExpression implements PredicateExpression {
|
||||
/// Constructs a new instance of [StringExpression].
|
||||
///
|
||||
/// The [value] parameter represents the string value to compare against.
|
||||
/// The [operator] parameter represents the string comparison operator, which is defined by the [PredicateStringOperator] enum.
|
||||
/// The [caseSensitive] parameter determines whether the comparison should be case-sensitive or not. The default value is `true`.
|
||||
/// The [invertOperator] parameter determines whether the operator should be inverted (e.g., "not contains" instead of "contains"). The default value is `false`.
|
||||
/// The [allowSpecialCharacters] parameter determines whether special characters should be allowed in the string comparison. The default value is `true`.
|
||||
const StringExpression(
|
||||
this.value,
|
||||
this.operator, {
|
||||
|
@ -184,12 +426,26 @@ class StringExpression implements PredicateExpression {
|
|||
this.allowSpecialCharacters = true,
|
||||
});
|
||||
|
||||
final PredicateStringOperator operator;
|
||||
final bool invertOperator;
|
||||
final bool caseSensitive;
|
||||
final bool allowSpecialCharacters;
|
||||
/// The string value to compare against.
|
||||
final String value;
|
||||
|
||||
/// The string comparison operator, which is defined by the [PredicateStringOperator] enum.
|
||||
final PredicateStringOperator operator;
|
||||
|
||||
/// Determines whether the operator should be inverted (e.g., "not contains" instead of "contains"). The default value is `false`.
|
||||
final bool invertOperator;
|
||||
|
||||
/// Determines whether the comparison should be case-sensitive or not. The default value is `true`.
|
||||
final bool caseSensitive;
|
||||
|
||||
/// Determines whether special characters should be allowed in the string comparison. The default value is `true`.
|
||||
final bool allowSpecialCharacters;
|
||||
|
||||
/// Returns a new instance of the [StringExpression] with the opposite [invertOperator] condition.
|
||||
///
|
||||
/// This getter creates and returns a new instance of the [StringExpression] with the opposite [invertOperator] condition to the current one.
|
||||
/// For example, if the current [invertOperator] value is `false`, the returned expression would have [invertOperator] set to `true`.
|
||||
/// This allows you to easily negate a string expression, such as changing "x contains 'abc'" to "x does not contain 'abc'".
|
||||
@override
|
||||
PredicateExpression get inverse {
|
||||
return StringExpression(
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:protevus_database/src/managed/object.dart';
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
|
|
|
@ -1,9 +1,23 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
|
||||
/// The order in which a collection of objects should be sorted when returned from a database.
|
||||
///
|
||||
/// See [Query.sortBy] and [Query.pageBy] for more details.
|
||||
class QuerySortDescriptor {
|
||||
/// Creates a new [QuerySortDescriptor] instance with the specified [key] and [order].
|
||||
///
|
||||
/// The [key] parameter represents the name of the property to sort by, and the [order]
|
||||
/// parameter specifies the order in which the values should be sorted, as defined by the
|
||||
/// [QuerySortOrder] class.
|
||||
QuerySortDescriptor(this.key, this.order);
|
||||
|
||||
/// The name of a property to sort by.
|
||||
|
@ -12,5 +26,8 @@ class QuerySortDescriptor {
|
|||
/// The order in which values should be sorted.
|
||||
///
|
||||
/// See [QuerySortOrder] for possible values.
|
||||
/// This property specifies the order in which the values should be sorted, as defined by the
|
||||
/// [QuerySortOrder] class. Possible values include [QuerySortOrder.ascending] and
|
||||
/// [QuerySortOrder.descending].
|
||||
QuerySortOrder order;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,25 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/query/query.dart';
|
||||
|
||||
/// The order in which a collection of objects should be sorted when returned from a database.
|
||||
/// Represents a predicate for sorting a collection of objects in a database query.
|
||||
///
|
||||
/// This class encapsulates the information needed to sort a collection of objects
|
||||
/// retrieved from a database, including the name of the property to sort by and
|
||||
/// the order in which the values should be sorted.
|
||||
class QuerySortPredicate {
|
||||
/// Constructs a new [QuerySortPredicate] instance.
|
||||
///
|
||||
/// The [predicate] parameter specifies the name of the property to sort by.
|
||||
/// The [order] parameter specifies the order in which the values should be
|
||||
/// sorted, using one of the values from the [QuerySortOrder] enum.
|
||||
QuerySortPredicate(
|
||||
this.predicate,
|
||||
this.order,
|
||||
|
@ -12,6 +30,6 @@ class QuerySortPredicate {
|
|||
|
||||
/// The order in which values should be sorted.
|
||||
///
|
||||
/// See [QuerySortOrder] for possible values.
|
||||
/// This property specifies the order in which the values should be sorted, using one of the values from the [QuerySortOrder] enum.
|
||||
QuerySortOrder order;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,33 @@
|
|||
import 'dart:async';
|
||||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:protevus_database/src/persistent_store/persistent_store.dart';
|
||||
import 'package:protevus_database/src/schema/schema.dart';
|
||||
|
||||
/// Thrown when [Migration] encounters an error.
|
||||
/// Thrown when a [Migration] encounters an error.
|
||||
///
|
||||
/// This exception is used to indicate that an error occurred during the execution of a database migration.
|
||||
/// The exception includes a [message] property that provides more information about the error that occurred.
|
||||
class MigrationException implements Exception {
|
||||
/// Creates a new [MigrationException] with the given [message].
|
||||
///
|
||||
/// The [message] parameter is a string that describes the error that occurred.
|
||||
MigrationException(this.message);
|
||||
|
||||
/// A message describing the error that occurred.
|
||||
String message;
|
||||
|
||||
/// Returns a string representation of the [MigrationException] object.
|
||||
///
|
||||
/// The string representation includes the [message] property, which provides
|
||||
/// a description of the error that occurred during the migration.
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
@ -54,6 +74,23 @@ abstract class Migration {
|
|||
/// to a database after this migration version is executed.
|
||||
Future seed();
|
||||
|
||||
/// Generates the source code for a database schema upgrade migration.
|
||||
///
|
||||
/// This method compares an existing [Schema] with a new [Schema] and generates
|
||||
/// the source code for a migration class that can be used to upgrade a database
|
||||
/// from the existing schema to the new schema.
|
||||
///
|
||||
/// The generated migration class will have an `upgrade()` method that contains
|
||||
/// the necessary schema changes, and empty `downgrade()` and `seed()` methods.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `existingSchema`: The current schema of the database.
|
||||
/// - `newSchema`: The new schema that the database should be upgraded to.
|
||||
/// - `version`: The version number of the migration. This is used to name the migration class.
|
||||
/// - `changeList`: An optional list of strings that describe the changes being made in this migration.
|
||||
///
|
||||
/// Returns:
|
||||
/// The source code for the migration class as a string.
|
||||
static String sourceForSchemaUpgrade(
|
||||
Schema existingSchema,
|
||||
Schema newSchema,
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
import 'package:protevus_database/src/schema/schema_table.dart';
|
||||
|
||||
|
@ -55,6 +63,7 @@ class Schema {
|
|||
// Do not set this directly. Use _tables= instead.
|
||||
late List<SchemaTable> _tableStorage;
|
||||
|
||||
/// Sets the tables for this schema and updates each table's schema reference.
|
||||
set _tables(List<SchemaTable> tables) {
|
||||
_tableStorage = tables;
|
||||
for (final t in _tableStorage) {
|
||||
|
@ -89,6 +98,9 @@ class Schema {
|
|||
table.schema = this;
|
||||
}
|
||||
|
||||
/// Replaces an existing table with a new one.
|
||||
///
|
||||
/// Throws a [SchemaException] if the existing table is not found.
|
||||
void replaceTable(SchemaTable existingTable, SchemaTable newTable) {
|
||||
if (!_tableStorage.contains(existingTable)) {
|
||||
throw SchemaException(
|
||||
|
@ -102,19 +114,11 @@ class Schema {
|
|||
existingTable.schema = null;
|
||||
}
|
||||
|
||||
/// Renames a table in the schema.
|
||||
///
|
||||
/// This method is not yet implemented and will throw a [SchemaException].
|
||||
void renameTable(SchemaTable table, String newName) {
|
||||
throw SchemaException("Renaming a table not yet implemented!");
|
||||
//
|
||||
// if (tableForName(newName) != null) {
|
||||
// throw new SchemaException("Table ${newName} already exist.");
|
||||
// }
|
||||
//
|
||||
// if (!tables.contains(table)) {
|
||||
// throw new SchemaException("Table ${table.name} does not exist in schema.");
|
||||
// }
|
||||
//
|
||||
// // Rename indices and constraints
|
||||
// table.name = newName;
|
||||
}
|
||||
|
||||
/// Removes a table from this instance.
|
||||
|
@ -154,7 +158,6 @@ class Schema {
|
|||
/// This class is used for comparing schemas for validation and migration.
|
||||
class SchemaDifference {
|
||||
/// Creates a new instance that represents the difference between [expectedSchema] and [actualSchema].
|
||||
///
|
||||
SchemaDifference(this.expectedSchema, this.actualSchema) {
|
||||
for (final expectedTable in expectedSchema.tables) {
|
||||
final actualTable = actualSchema[expectedTable.name!];
|
||||
|
@ -197,6 +200,7 @@ class SchemaDifference {
|
|||
/// The differences, if any, between tables in [expectedSchema] and [actualSchema].
|
||||
List<SchemaTableDifference> get tableDifferences => _differingTables;
|
||||
|
||||
/// Returns a list of tables that need to be added to the actual schema.
|
||||
List<SchemaTable?> get tablesToAdd {
|
||||
return _differingTables
|
||||
.where((diff) => diff.expectedTable == null && diff.actualTable != null)
|
||||
|
@ -204,6 +208,7 @@ class SchemaDifference {
|
|||
.toList();
|
||||
}
|
||||
|
||||
/// Returns a list of tables that need to be deleted from the actual schema.
|
||||
List<SchemaTable?> get tablesToDelete {
|
||||
return _differingTables
|
||||
.where((diff) => diff.expectedTable != null && diff.actualTable == null)
|
||||
|
@ -211,21 +216,28 @@ class SchemaDifference {
|
|||
.toList();
|
||||
}
|
||||
|
||||
/// Returns a list of tables that need to be modified in the actual schema.
|
||||
List<SchemaTableDifference> get tablesToModify {
|
||||
return _differingTables
|
||||
.where((diff) => diff.expectedTable != null && diff.actualTable != null)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Internal storage for differing tables.
|
||||
final List<SchemaTableDifference> _differingTables = [];
|
||||
}
|
||||
|
||||
/// Thrown when a [Schema] encounters an error.
|
||||
class SchemaException implements Exception {
|
||||
/// Creates a new [SchemaException] with the given [message].
|
||||
SchemaException(this.message);
|
||||
|
||||
/// The error message describing the schema exception.
|
||||
String message;
|
||||
|
||||
/// Returns a string representation of this exception.
|
||||
///
|
||||
/// The returned string includes the phrase "Invalid schema." followed by the [message].
|
||||
@override
|
||||
String toString() => "Invalid schema. $message";
|
||||
}
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/persistent_store/persistent_store.dart';
|
||||
import 'package:protevus_database/src/schema/schema.dart';
|
||||
|
||||
|
@ -20,7 +29,7 @@ The logic that goes into testing that the commands generated to build a valid sc
|
|||
|
||||
/// Generates SQL or Dart code that modifies a database schema.
|
||||
class SchemaBuilder {
|
||||
/// Creates a builder starting from an existing schema.
|
||||
/// Creates a new [SchemaBuilder] instance from an existing [Schema].
|
||||
///
|
||||
/// If [store] is null, this builder will emit [commands] that are Dart statements that replicate the methods invoked on this object.
|
||||
/// Otherwise, [commands] are SQL commands (for the database represented by [store]) that are equivalent to the method invoked on this object.
|
||||
|
@ -31,7 +40,14 @@ class SchemaBuilder {
|
|||
/// Creates a builder starting from the empty schema.
|
||||
///
|
||||
/// If [store] is null, this builder will emit [commands] that are Dart statements that replicate the methods invoked on this object.
|
||||
/// Otherwise, [commands] are SQL commands (for the database represented by [store]) that are equivalent to the method invoked on this object.
|
||||
/// Otherwise, [commands] are SQL commands (for the database represented by [store]) that are equivalent to the method invoked on this object.
|
||||
///
|
||||
/// The [targetSchema] parameter specifies the desired schema to be built. The [SchemaDifference] between the empty schema and the [targetSchema]
|
||||
/// will be used to generate the necessary commands to transform the empty schema into the [targetSchema].
|
||||
///
|
||||
/// The [isTemporary] flag determines whether the generated schema changes should create temporary tables.
|
||||
///
|
||||
/// The optional [changeList] parameter is a list that will be populated with human-readable descriptions of the schema changes as they are generated.
|
||||
SchemaBuilder.toSchema(
|
||||
PersistentStore? store,
|
||||
Schema targetSchema, {
|
||||
|
@ -44,7 +60,21 @@ class SchemaBuilder {
|
|||
changeList: changeList,
|
||||
);
|
||||
|
||||
// Creates a builder
|
||||
/// Creates a new [SchemaBuilder] instance from the given [SchemaDifference].
|
||||
///
|
||||
/// The [SchemaDifference] represents the changes that need to be made to the
|
||||
/// input schema to arrive at the target schema. This constructor will generate
|
||||
/// the necessary SQL or Dart code commands to apply those changes.
|
||||
///
|
||||
/// If [store] is not null, the generated commands will be SQL commands for the
|
||||
/// underlying database. If [store] is null, the generated commands will be
|
||||
/// Dart expressions that replicate the method calls to build the schema.
|
||||
///
|
||||
/// The [isTemporary] flag determines whether the generated schema changes
|
||||
/// should create temporary tables.
|
||||
///
|
||||
/// The optional [changeList] parameter is a list that will be populated with
|
||||
/// human-readable descriptions of the schema changes as they are generated.
|
||||
SchemaBuilder.fromDifference(
|
||||
this.store,
|
||||
SchemaDifference difference, {
|
||||
|
@ -60,9 +90,15 @@ class SchemaBuilder {
|
|||
}
|
||||
|
||||
/// The starting schema of this builder.
|
||||
///
|
||||
/// This property holds the initial schema that the [SchemaBuilder] instance will use as a starting point. As operations are performed on the
|
||||
/// builder, the [schema] property will be updated to reflect the resulting schema.
|
||||
late Schema inputSchema;
|
||||
|
||||
/// The resulting schema of this builder as operations are applied to it.
|
||||
///
|
||||
/// This property holds the final schema that the [SchemaBuilder] instance will generate after applying all the requested operations.
|
||||
/// As operations are performed on the builder, the [schema] property will be updated to reflect the resulting schema.
|
||||
late Schema schema;
|
||||
|
||||
/// The persistent store to validate and construct operations.
|
||||
|
@ -72,6 +108,10 @@ class SchemaBuilder {
|
|||
PersistentStore? store;
|
||||
|
||||
/// Whether or not this builder should create temporary tables.
|
||||
///
|
||||
/// When this flag is set to `true`, the schema commands generated by this builder will create temporary tables
|
||||
/// instead of permanent tables. This can be useful for testing or other scenarios where the schema changes are
|
||||
/// not intended to be persisted.
|
||||
bool isTemporary;
|
||||
|
||||
/// A list of commands generated by operations performed on this builder.
|
||||
|
@ -81,6 +121,14 @@ class SchemaBuilder {
|
|||
List<String> commands = [];
|
||||
|
||||
/// Validates and adds a table to [schema].
|
||||
///
|
||||
/// This method adds the given [table] to the current [schema] and generates the necessary SQL or Dart code
|
||||
/// commands to create the table. If [store] is not null, the generated commands will be SQL commands for
|
||||
/// the underlying database. If [store] is null, the generated commands will be Dart expressions that
|
||||
/// replicate the method calls to build the schema.
|
||||
///
|
||||
/// The [isTemporary] flag, which is inherited from the [SchemaBuilder] instance, determines whether the
|
||||
/// generated schema changes should create temporary tables.
|
||||
void createTable(SchemaTable table) {
|
||||
schema.addTable(table);
|
||||
|
||||
|
@ -92,6 +140,14 @@ class SchemaBuilder {
|
|||
}
|
||||
|
||||
/// Validates and renames a table in [schema].
|
||||
///
|
||||
/// This method renames the table with the [currentTableName] to the [newName].
|
||||
/// If the [currentTableName] does not exist in the [schema], a [SchemaException]
|
||||
/// will be thrown.
|
||||
///
|
||||
/// If [store] is not null, the generated SQL commands to rename the table
|
||||
/// will be added to the [commands] list. If [store] is null, a Dart expression
|
||||
/// that replicates the table renaming will be added to the [commands] list.
|
||||
void renameTable(String currentTableName, String newName) {
|
||||
final table = schema.tableForName(currentTableName);
|
||||
if (table == null) {
|
||||
|
@ -107,6 +163,13 @@ class SchemaBuilder {
|
|||
}
|
||||
|
||||
/// Validates and deletes a table in [schema].
|
||||
///
|
||||
/// This method removes the specified [tableName] from the current [schema] and generates the necessary SQL or Dart code
|
||||
/// commands to delete the table. If [store] is not null, the generated commands will be SQL commands for
|
||||
/// the underlying database. If [store] is null, the generated commands will be Dart expressions that
|
||||
/// replicate the method call to delete the table.
|
||||
///
|
||||
/// If the specified [tableName] does not exist in the [schema], a [SchemaException] will be thrown.
|
||||
void deleteTable(String tableName) {
|
||||
final table = schema.tableForName(tableName);
|
||||
if (table == null) {
|
||||
|
@ -123,6 +186,24 @@ class SchemaBuilder {
|
|||
}
|
||||
|
||||
/// Alters a table in [schema].
|
||||
///
|
||||
/// This method allows you to modify the properties of an existing table in the schema.
|
||||
/// It takes a [tableName] parameter to identify the table to be modified, and a
|
||||
/// [modify] callback function that accepts a [SchemaTable] parameter and allows you
|
||||
/// to make changes to the table.
|
||||
///
|
||||
/// If the specified [tableName] does not exist in the [schema], a [SchemaException]
|
||||
/// will be thrown.
|
||||
///
|
||||
/// The changes made to the table through the [modify] callback function will be
|
||||
/// reflected in the [schema] and the necessary SQL commands (if [store] is not null)
|
||||
/// or Dart expressions (if [store] is null) will be added to the [commands] list.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// database.alterTable("users", (t) {
|
||||
/// t.uniqueColumnSet = ["email", "username"];
|
||||
/// });
|
||||
void alterTable(
|
||||
String tableName,
|
||||
void Function(SchemaTable targetTable) modify,
|
||||
|
@ -182,6 +263,15 @@ class SchemaBuilder {
|
|||
}
|
||||
|
||||
/// Validates and adds a column to a table in [schema].
|
||||
///
|
||||
/// This method adds the given [column] to the table with the specified [tableName] in the current [schema].
|
||||
/// If the specified [tableName] does not exist in the [schema], a [SchemaException] will be thrown.
|
||||
///
|
||||
/// If [store] is not null, the necessary SQL commands to add the column will be added to the [commands] list.
|
||||
/// If [store] is null, a Dart expression that replicates the call to add the column will be added to the [commands] list.
|
||||
///
|
||||
/// The optional [unencodedInitialValue] parameter can be used to specify an initial value for the column when it is
|
||||
/// added to a table that already has rows. This is useful when adding a non-nullable column to an existing table.
|
||||
void addColumn(
|
||||
String tableName,
|
||||
SchemaColumn column, {
|
||||
|
@ -209,6 +299,14 @@ class SchemaBuilder {
|
|||
}
|
||||
|
||||
/// Validates and deletes a column in a table in [schema].
|
||||
///
|
||||
/// This method removes the specified [columnName] from the table with the given [tableName] in the current [schema]
|
||||
/// and generates the necessary SQL or Dart code commands to delete the column. If [store] is not null, the generated
|
||||
/// commands will be SQL commands for the underlying database. If [store] is null, the generated commands will be
|
||||
/// Dart expressions that replicate the method call to delete the column.
|
||||
///
|
||||
/// If the specified [tableName] does not exist in the [schema], a [SchemaException] will be thrown. If the specified
|
||||
/// [columnName] does not exist in the table, a [SchemaException] will also be thrown.
|
||||
void deleteColumn(String tableName, String columnName) {
|
||||
final table = schema.tableForName(tableName);
|
||||
if (table == null) {
|
||||
|
@ -230,6 +328,15 @@ class SchemaBuilder {
|
|||
}
|
||||
|
||||
/// Validates and renames a column in a table in [schema].
|
||||
///
|
||||
/// This method renames the column with the [columnName] to the [newName] in the
|
||||
/// table with the specified [tableName]. If the [tableName] does not exist in
|
||||
/// the [schema], a [SchemaException] will be thrown. If the [columnName] does
|
||||
/// not exist in the table, a [SchemaException] will also be thrown.
|
||||
///
|
||||
/// If [store] is not null, the generated SQL commands to rename the column
|
||||
/// will be added to the [commands] list. If [store] is null, a Dart expression
|
||||
/// that replicates the column renaming will be added to the [commands] list.
|
||||
void renameColumn(String tableName, String columnName, String newName) {
|
||||
final table = schema.tableForName(tableName);
|
||||
if (table == null) {
|
||||
|
@ -377,18 +484,44 @@ class SchemaBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generates the necessary schema commands to apply the given [SchemaDifference].
|
||||
///
|
||||
/// This method is responsible for generating the SQL or Dart code commands
|
||||
/// required to transform the input schema into the target schema represented
|
||||
/// by the [SchemaDifference].
|
||||
///
|
||||
/// The generated commands are added to the [commands] list of this [SchemaBuilder]
|
||||
/// instance. If [store] is not null, the commands will be SQL commands for the
|
||||
/// underlying database. If [store] is null, the commands will be Dart expressions
|
||||
/// that replicate the method calls to build the schema.
|
||||
///
|
||||
/// The [changeList] parameter is an optional list that will be populated with
|
||||
/// human-readable descriptions of the schema changes as they are generated.
|
||||
///
|
||||
/// The [temporary] flag determines whether the generated schema changes should
|
||||
/// create temporary tables instead of permanent tables.
|
||||
void _generateSchemaCommands(
|
||||
SchemaDifference difference, {
|
||||
List<String>? changeList,
|
||||
bool temporary = false,
|
||||
}) {
|
||||
// We need to remove foreign keys from the initial table add and defer
|
||||
// them until after all tables in the schema have been created.
|
||||
// These can occur in both columns and multi column unique.
|
||||
// We'll split the creation of those tables into two different sets
|
||||
// of commands and run the difference afterwards
|
||||
/// This code handles the case where a table being added to the schema
|
||||
/// has a foreign key constraint. To avoid issues with the foreign key
|
||||
/// constraint during the initial table creation, the foreign key
|
||||
/// information is extracted and deferred until after all tables have
|
||||
/// been created. This is done by creating a list of `SchemaTableDifference`
|
||||
/// objects, which represent the differences between the actual and expected
|
||||
/// tables, including the foreign key information. These differences are
|
||||
/// then processed separately after the initial table creation.
|
||||
final fkDifferences = <SchemaTableDifference>[];
|
||||
|
||||
/// Handles the case where a table being added to the schema has a foreign key constraint.
|
||||
///
|
||||
/// To avoid issues with the foreign key constraint during the initial table creation, the foreign key
|
||||
/// information is extracted and deferred until after all tables have been created. This is done by
|
||||
/// creating a list of `SchemaTableDifference` objects, which represent the differences between the
|
||||
/// actual and expected tables, including the foreign key information. These differences are then
|
||||
/// processed separately after the initial table creation.
|
||||
for (final t in difference.tablesToAdd) {
|
||||
final copy = SchemaTable.from(t!);
|
||||
if (copy.hasForeignKeyInUniqueSet) {
|
||||
|
@ -402,20 +535,59 @@ class SchemaBuilder {
|
|||
fkDifferences.add(SchemaTableDifference(copy, t));
|
||||
}
|
||||
|
||||
/// Generates the necessary schema commands for the foreign key constraints in the given [SchemaDifference].
|
||||
///
|
||||
/// This method is called after all tables have been created to handle the case where a table being added to the schema
|
||||
/// has a foreign key constraint. The foreign key information is extracted and deferred until after the initial table
|
||||
/// creation to avoid issues with the foreign key constraint during the initial table creation process.
|
||||
///
|
||||
/// The [fkDifferences] list contains `SchemaTableDifference` objects, which represent the differences between the
|
||||
/// actual and expected tables, including the foreign key information. These differences are processed separately
|
||||
/// after the initial table creation.
|
||||
///
|
||||
/// The [changeList] parameter is an optional list that will be populated with human-readable descriptions of the
|
||||
/// schema changes as they are generated.
|
||||
for (final td in fkDifferences) {
|
||||
_generateTableCommands(td, changeList: changeList);
|
||||
}
|
||||
|
||||
/// Deletes the tables specified in the [difference.tablesToDelete] list.
|
||||
///
|
||||
/// For each table in the [difference.tablesToDelete] list, this method:
|
||||
/// 1. Adds a human-readable description of the table deletion to the [changeList] (if provided).
|
||||
/// 2. Calls the [deleteTable] method to delete the table from the schema.
|
||||
for (final t in difference.tablesToDelete) {
|
||||
changeList?.add("Deleting table '${t!.name}'");
|
||||
deleteTable(t!.name!);
|
||||
}
|
||||
|
||||
/// Generates the necessary schema commands for the tables specified in the given [SchemaDifference].
|
||||
///
|
||||
/// This method is responsible for generating the SQL or Dart code commands required to modify the
|
||||
/// tables in the input schema according to the changes specified in the [SchemaDifference].
|
||||
///
|
||||
/// The generated commands are added to the [commands] list of the [SchemaBuilder] instance. If [store]
|
||||
/// is not null, the commands will be SQL commands for the underlying database. If [store] is null,
|
||||
/// the commands will be Dart expressions that replicate the method calls to build the schema.
|
||||
///
|
||||
/// The [changeList] parameter is an optional list that will be populated with human-readable
|
||||
/// descriptions of the schema changes as they are generated.
|
||||
for (final t in difference.tablesToModify) {
|
||||
_generateTableCommands(t, changeList: changeList);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the necessary schema commands for the tables specified in the given [SchemaDifference].
|
||||
///
|
||||
/// This method is responsible for generating the SQL or Dart code commands required to modify the
|
||||
/// tables in the input schema according to the changes specified in the [SchemaDifference].
|
||||
///
|
||||
/// The generated commands are added to the [commands] list of the [SchemaBuilder] instance. If [store]
|
||||
/// is not null, the commands will be SQL commands for the underlying database. If [store] is null,
|
||||
/// the commands will be Dart expressions that replicate the method calls to build the schema.
|
||||
///
|
||||
/// The [changeList] parameter is an optional list that will be populated with human-readable
|
||||
/// descriptions of the schema changes as they are generated.
|
||||
void _generateTableCommands(
|
||||
SchemaTableDifference difference, {
|
||||
List<String>? changeList,
|
||||
|
@ -478,6 +650,16 @@ class SchemaBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generates a Dart expression that creates a new [SchemaTable] instance with the specified columns and unique column set.
|
||||
///
|
||||
/// This method is used by the [SchemaBuilder] class to generate Dart code that replicates the operations performed on the builder.
|
||||
///
|
||||
/// The generated Dart expression will create a new [SchemaTable] instance with the specified table name and columns. If the table
|
||||
/// has a unique column set, the expression will also include the unique column set names.
|
||||
///
|
||||
/// The [table] parameter is the [SchemaTable] instance for which the Dart expression should be generated.
|
||||
///
|
||||
/// Returns the generated Dart expression as a [String].
|
||||
static String _getNewTableExpression(SchemaTable table) {
|
||||
final builder = StringBuffer();
|
||||
builder.write('database.createTable(SchemaTable("${table.name}", [');
|
||||
|
@ -493,6 +675,18 @@ class SchemaBuilder {
|
|||
return builder.toString();
|
||||
}
|
||||
|
||||
/// Generates a Dart expression that creates a new [SchemaColumn] instance with the specified properties.
|
||||
///
|
||||
/// This method is used by the [SchemaBuilder] class to generate Dart code that replicates the operations performed
|
||||
/// on the builder.
|
||||
///
|
||||
/// The generated Dart expression will create a new [SchemaColumn] instance with the specified name, type, and other
|
||||
/// properties. If the column is a foreign key, the expression will include the related table name, related column
|
||||
/// name, and delete rule.
|
||||
///
|
||||
/// The [column] parameter is the [SchemaColumn] instance for which the Dart expression should be generated.
|
||||
///
|
||||
/// Returns the generated Dart expression as a [String].
|
||||
static String _getNewColumnExpression(SchemaColumn column) {
|
||||
final builder = StringBuffer();
|
||||
if (column.relatedTableName != null) {
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
import 'package:protevus_database/src/schema/schema.dart';
|
||||
|
||||
|
@ -5,7 +14,16 @@ import 'package:protevus_database/src/schema/schema.dart';
|
|||
///
|
||||
/// Instances of this type contain the database-only details of a [ManagedPropertyDescription].
|
||||
class SchemaColumn {
|
||||
/// Creates an instance of this type from [name], [type] and other properties.
|
||||
/// Creates a new instance of [SchemaColumn] with the specified properties.
|
||||
///
|
||||
/// The [name] parameter is the name of the column.
|
||||
/// The [type] parameter is the [ManagedPropertyType] of the column.
|
||||
/// The [isIndexed] parameter specifies whether the column should be indexed.
|
||||
/// The [isNullable] parameter specifies whether the column can be null.
|
||||
/// The [autoincrement] parameter specifies whether the column should be auto-incremented.
|
||||
/// The [isUnique] parameter specifies whether the column should be unique.
|
||||
/// The [defaultValue] parameter specifies the default value of the column.
|
||||
/// The [isPrimaryKey] parameter specifies whether the column should be the primary key.
|
||||
SchemaColumn(
|
||||
this.name,
|
||||
ManagedPropertyType type, {
|
||||
|
@ -20,6 +38,16 @@ class SchemaColumn {
|
|||
}
|
||||
|
||||
/// A convenience constructor for properties that represent foreign key relationships.
|
||||
///
|
||||
/// This constructor creates a [SchemaColumn] instance with the specified properties for a foreign key relationship.
|
||||
///
|
||||
/// The [name] parameter is the name of the column.
|
||||
/// The [type] parameter is the [ManagedPropertyType] of the column.
|
||||
/// The [isNullable] parameter specifies whether the column can be null.
|
||||
/// The [isUnique] parameter specifies whether the column should be unique.
|
||||
/// The [relatedTableName] parameter specifies the name of the related table.
|
||||
/// The [relatedColumnName] parameter specifies the name of the related column.
|
||||
/// The [rule] parameter specifies the [DeleteRule] for the foreign key constraint.
|
||||
SchemaColumn.relationship(
|
||||
this.name,
|
||||
ManagedPropertyType type, {
|
||||
|
@ -34,7 +62,19 @@ class SchemaColumn {
|
|||
_deleteRule = deleteRuleStringForDeleteRule(rule);
|
||||
}
|
||||
|
||||
/// Creates an instance of this type to mirror [desc].
|
||||
/// Creates a new [SchemaColumn] instance that mirrors the properties of the provided [ManagedPropertyDescription].
|
||||
///
|
||||
/// This constructor is used to create a [SchemaColumn] instance that represents the same database column as the
|
||||
/// provided [ManagedPropertyDescription]. The properties of the [SchemaColumn] instance are set based on the
|
||||
/// properties of the [ManagedPropertyDescription].
|
||||
///
|
||||
/// If the [ManagedPropertyDescription] is a [ManagedRelationshipDescription], the [SchemaColumn] instance
|
||||
/// will be set up as a foreign key column with the appropriate related table and column names, as well as the
|
||||
/// delete rule. If the [ManagedPropertyDescription] is a [ManagedAttributeDescription], the [SchemaColumn] instance
|
||||
/// will be set up with the appropriate type, nullability, autoincrement, uniqueness, and indexing properties, as well
|
||||
/// as the default value if it exists.
|
||||
///
|
||||
/// @param desc The [ManagedPropertyDescription] to mirror.
|
||||
SchemaColumn.fromProperty(ManagedPropertyDescription desc) {
|
||||
name = desc.name;
|
||||
|
||||
|
@ -57,7 +97,11 @@ class SchemaColumn {
|
|||
isIndexed = desc.isIndexed;
|
||||
}
|
||||
|
||||
/// Creates a copy of [otherColumn].
|
||||
/// Creates a new instance of [SchemaColumn] that is a copy of [otherColumn].
|
||||
///
|
||||
/// This constructor creates a new [SchemaColumn] instance with the same properties as the provided [otherColumn].
|
||||
/// The new instance will have the same name, type, indexing, nullability, autoincrement, uniqueness, default value,
|
||||
/// primary key status, related table name, related column name, and delete rule as the [otherColumn].
|
||||
SchemaColumn.from(SchemaColumn otherColumn) {
|
||||
name = otherColumn.name;
|
||||
_type = otherColumn._type;
|
||||
|
@ -72,7 +116,7 @@ class SchemaColumn {
|
|||
_deleteRule = otherColumn._deleteRule;
|
||||
}
|
||||
|
||||
/// Creates an instance of this type from [map].
|
||||
/// Creates an instance of [SchemaColumn] from the provided [map].
|
||||
///
|
||||
/// Where [map] is typically created by [asMap].
|
||||
SchemaColumn.fromMap(Map<String, dynamic> map) {
|
||||
|
@ -89,7 +133,12 @@ class SchemaColumn {
|
|||
_deleteRule = map["deleteRule"] as String?;
|
||||
}
|
||||
|
||||
/// Creates an empty instance of this type.
|
||||
/// Creates a new, empty instance of [SchemaColumn].
|
||||
///
|
||||
/// This constructor creates a new [SchemaColumn] instance with all properties set to their default values.
|
||||
///
|
||||
/// The new instance will have no name, no type, no indexing, be nullable, not be autoincremented, not be unique,
|
||||
/// have no default value, not be a primary key, have no related table or column names, and no delete rule.
|
||||
SchemaColumn.empty();
|
||||
|
||||
/// The name of this column.
|
||||
|
@ -97,7 +146,8 @@ class SchemaColumn {
|
|||
|
||||
/// The [SchemaTable] this column belongs to.
|
||||
///
|
||||
/// May be null if not assigned to a table.
|
||||
/// This property indicates the [SchemaTable] that the [SchemaColumn] instance is associated with.
|
||||
/// If the [SchemaColumn] is not assigned to a specific table, this property will be `null`.
|
||||
SchemaTable? table;
|
||||
|
||||
/// The [String] representation of this column's type.
|
||||
|
@ -157,19 +207,54 @@ class SchemaColumn {
|
|||
}
|
||||
|
||||
/// Whether or not this column is a foreign key column.
|
||||
///
|
||||
/// This property returns `true` if the [relatedTableName] and [relatedColumnName] properties are not `null`,
|
||||
/// indicating that this column represents a foreign key relationship. Otherwise, it returns `false`.
|
||||
bool get isForeignKey {
|
||||
return relatedTableName != null && relatedColumnName != null;
|
||||
}
|
||||
|
||||
/// The type of this column as a string.
|
||||
String? _type;
|
||||
|
||||
/// The delete rule for this column if it is a foreign key column.
|
||||
///
|
||||
/// Undefined if not a foreign key column.
|
||||
String? _deleteRule;
|
||||
|
||||
/// The differences between two columns.
|
||||
/// Compares the current [SchemaColumn] instance with the provided [column] and returns a [SchemaColumnDifference] object
|
||||
/// that represents the differences between the two columns.
|
||||
///
|
||||
/// This method is used to determine the differences between the expected and actual database schema when performing
|
||||
/// schema validation or database migrations. The returned [SchemaColumnDifference] object contains information about
|
||||
/// any differences in the properties of the two columns, such as name, type, nullability, indexing, uniqueness,
|
||||
/// default value, and delete rule.
|
||||
///
|
||||
/// @param column The [SchemaColumn] instance to compare with the current instance.
|
||||
/// @return A [SchemaColumnDifference] object that represents the differences between the two columns.
|
||||
SchemaColumnDifference differenceFrom(SchemaColumn column) {
|
||||
return SchemaColumnDifference(this, column);
|
||||
}
|
||||
|
||||
/// Returns string representation of [ManagedPropertyType].
|
||||
/// Returns the string representation of the provided [ManagedPropertyType].
|
||||
///
|
||||
/// This method takes a [ManagedPropertyType] instance and returns the corresponding string representation of the
|
||||
/// property type. The mapping between the [ManagedPropertyType] and its string representation is as follows:
|
||||
///
|
||||
/// - `ManagedPropertyType.integer` -> `"integer"`
|
||||
/// - `ManagedPropertyType.doublePrecision` -> `"double"`
|
||||
/// - `ManagedPropertyType.bigInteger` -> `"bigInteger"`
|
||||
/// - `ManagedPropertyType.boolean` -> `"boolean"`
|
||||
/// - `ManagedPropertyType.datetime` -> `"datetime"`
|
||||
/// - `ManagedPropertyType.string` -> `"string"`
|
||||
/// - `ManagedPropertyType.list` -> `null`
|
||||
/// - `ManagedPropertyType.map` -> `null`
|
||||
/// - `ManagedPropertyType.document` -> `"document"`
|
||||
///
|
||||
/// If the provided [ManagedPropertyType] is not recognized, this method will return `null`.
|
||||
///
|
||||
/// @param type The [ManagedPropertyType] to convert to a string representation.
|
||||
/// @return The string representation of the provided [ManagedPropertyType], or `null` if it is not recognized.
|
||||
static String? typeStringForType(ManagedPropertyType? type) {
|
||||
switch (type) {
|
||||
case ManagedPropertyType.integer:
|
||||
|
@ -195,7 +280,24 @@ class SchemaColumn {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns inverse of [typeStringForType].
|
||||
/// Returns the [ManagedPropertyType] that corresponds to the provided string representation.
|
||||
///
|
||||
/// This method takes a string representation of a property type and returns the corresponding
|
||||
/// [ManagedPropertyType] instance. The mapping between the string representation and the
|
||||
/// [ManagedPropertyType] is as follows:
|
||||
///
|
||||
/// - `"integer"` -> `ManagedPropertyType.integer`
|
||||
/// - `"double"` -> `ManagedPropertyType.doublePrecision`
|
||||
/// - `"bigInteger"` -> `ManagedPropertyType.bigInteger`
|
||||
/// - `"boolean"` -> `ManagedPropertyType.boolean`
|
||||
/// - `"datetime"` -> `ManagedPropertyType.datetime`
|
||||
/// - `"string"` -> `ManagedPropertyType.string`
|
||||
/// - `"document"` -> `ManagedPropertyType.document`
|
||||
///
|
||||
/// If the provided string representation is not recognized, this method will return `null`.
|
||||
///
|
||||
/// @param type The string representation of the property type to convert to a [ManagedPropertyType].
|
||||
/// @return The [ManagedPropertyType] that corresponds to the provided string representation, or `null` if it is not recognized.
|
||||
static ManagedPropertyType? typeFromTypeString(String? type) {
|
||||
switch (type) {
|
||||
case "integer":
|
||||
|
@ -217,7 +319,18 @@ class SchemaColumn {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns string representation of [DeleteRule].
|
||||
/// Returns a string representation of the provided [DeleteRule].
|
||||
///
|
||||
/// This method takes a [DeleteRule] value and returns the corresponding string representation.
|
||||
/// The mapping between the [DeleteRule] and its string representation is as follows:
|
||||
///
|
||||
/// - [DeleteRule.cascade] -> `"cascade"`
|
||||
/// - [DeleteRule.nullify] -> `"nullify"`
|
||||
/// - [DeleteRule.restrict] -> `"restrict"`
|
||||
/// - [DeleteRule.setDefault] -> `"default"`
|
||||
///
|
||||
/// @param rule The [DeleteRule] value to convert to a string representation.
|
||||
/// @return The string representation of the provided [DeleteRule], or `null` if the [DeleteRule] is not recognized.
|
||||
static String? deleteRuleStringForDeleteRule(DeleteRule rule) {
|
||||
switch (rule) {
|
||||
case DeleteRule.cascade:
|
||||
|
@ -231,7 +344,20 @@ class SchemaColumn {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns inverse of [deleteRuleStringForDeleteRule].
|
||||
/// Converts a string representation of a [DeleteRule] to the corresponding [DeleteRule] value.
|
||||
///
|
||||
/// This method takes a string representation of a [DeleteRule] and returns the corresponding [DeleteRule] value.
|
||||
/// The mapping between the string representation and the [DeleteRule] value is as follows:
|
||||
///
|
||||
/// - `"cascade"` -> [DeleteRule.cascade]
|
||||
/// - `"nullify"` -> [DeleteRule.nullify]
|
||||
/// - `"restrict"` -> [DeleteRule.restrict]
|
||||
/// - `"default"` -> [DeleteRule.setDefault]
|
||||
///
|
||||
/// If the provided string representation is not recognized, this method will return `null`.
|
||||
///
|
||||
/// @param rule The string representation of the [DeleteRule] to convert.
|
||||
/// @return The [DeleteRule] value that corresponds to the provided string representation, or `null` if it is not recognized.
|
||||
static DeleteRule? deleteRuleForDeleteRuleString(String? rule) {
|
||||
switch (rule) {
|
||||
case "cascade":
|
||||
|
@ -246,7 +372,25 @@ class SchemaColumn {
|
|||
return null;
|
||||
}
|
||||
|
||||
/// Returns portable representation of this instance.
|
||||
/// Returns a map representation of the current [SchemaColumn] instance.
|
||||
///
|
||||
/// The map contains the following key-value pairs:
|
||||
///
|
||||
/// - "name": the name of the column
|
||||
/// - "type": the string representation of the column's [ManagedPropertyType]
|
||||
/// - "nullable": whether the column is nullable
|
||||
/// - "autoincrement": whether the column is auto-incremented
|
||||
/// - "unique": whether the column is unique
|
||||
/// - "defaultValue": the default value of the column
|
||||
/// - "primaryKey": whether the column is the primary key
|
||||
/// - "relatedTableName": the name of the related table (for foreign key columns)
|
||||
/// - "relatedColumnName": the name of the related column (for foreign key columns)
|
||||
/// - "deleteRule": the delete rule for the foreign key constraint (for foreign key columns)
|
||||
/// - "indexed": whether the column is indexed
|
||||
///
|
||||
/// This method is used to create a portable representation of the [SchemaColumn] instance that can be easily
|
||||
/// serialized and deserialized, for example, when storing schema information in a database or
|
||||
/// transferring it over a network.
|
||||
Map<String, dynamic> asMap() {
|
||||
return {
|
||||
"name": name,
|
||||
|
@ -263,6 +407,15 @@ class SchemaColumn {
|
|||
};
|
||||
}
|
||||
|
||||
/// Returns a string representation of the SchemaColumn instance.
|
||||
///
|
||||
/// The format of the string is "[name] (-> [relatedTableName].[relatedColumnName])", where:
|
||||
///
|
||||
/// - [name] is the name of the column
|
||||
/// - [relatedTableName] is the name of the related table, if the column is a foreign key
|
||||
/// - [relatedColumnName] is the name of the related column, if the column is a foreign key
|
||||
///
|
||||
/// If the column is not a foreign key, the string will only include the column name.
|
||||
@override
|
||||
String toString() => "$name (-> $relatedTableName.$relatedColumnName)";
|
||||
}
|
||||
|
@ -272,6 +425,24 @@ class SchemaColumn {
|
|||
/// This class is used for comparing database columns for validation and migration.
|
||||
class SchemaColumnDifference {
|
||||
/// Creates a new instance that represents the difference between [expectedColumn] and [actualColumn].
|
||||
///
|
||||
/// This constructor creates a new [SchemaColumnDifference] instance that represents the differences between the
|
||||
/// provided [expectedColumn] and [actualColumn]. The constructor compares the properties of the two columns and
|
||||
/// populates the [_differingProperties] list with any differences found.
|
||||
///
|
||||
/// If the [actualColumn] and [expectedColumn] have different primary key, related column name, related table name,
|
||||
/// type, or autoincrement behavior, a [SchemaException] is thrown with an appropriate error message.
|
||||
///
|
||||
/// The following properties are compared between the [expectedColumn] and [actualColumn]:
|
||||
/// - Name (case-insensitive)
|
||||
/// - Indexing
|
||||
/// - Uniqueness
|
||||
/// - Nullability
|
||||
/// - Default value
|
||||
/// - Delete rule (for foreign key columns)
|
||||
///
|
||||
/// @param expectedColumn The expected [SchemaColumn] instance.
|
||||
/// @param actualColumn The actual [SchemaColumn] instance.
|
||||
SchemaColumnDifference(this.expectedColumn, this.actualColumn) {
|
||||
if (actualColumn != null && expectedColumn != null) {
|
||||
if (actualColumn!.isPrimaryKey != expectedColumn!.isPrimaryKey) {
|
||||
|
@ -370,21 +541,28 @@ class SchemaColumnDifference {
|
|||
|
||||
/// The expected column.
|
||||
///
|
||||
/// May be null if there is no column expected.
|
||||
/// This property represents the expected [SchemaColumn] instance that is being compared to the [actualColumn].
|
||||
/// If there is no expected column, this property will be `null`.
|
||||
final SchemaColumn? expectedColumn;
|
||||
|
||||
/// The actual column.
|
||||
/// The actual [SchemaColumn] instance being compared.
|
||||
///
|
||||
/// May be null if there is no actual column.
|
||||
final SchemaColumn? actualColumn;
|
||||
|
||||
/// Whether or not [expectedColumn] and [actualColumn] are different.
|
||||
///
|
||||
/// This property returns `true` if there are any differences between the [expectedColumn] and [actualColumn],
|
||||
/// as determined by the [_differingProperties] list. It also returns `true` if one of the columns is `null`
|
||||
/// while the other is not.
|
||||
///
|
||||
/// The [_differingProperties] list contains the specific properties that differ between the two columns.
|
||||
bool get hasDifferences =>
|
||||
_differingProperties.isNotEmpty ||
|
||||
(expectedColumn == null && actualColumn != null) ||
|
||||
(actualColumn == null && expectedColumn != null);
|
||||
|
||||
/// Human-readable list of differences between [expectedColumn] and [actualColumn].
|
||||
/// Provides a human-readable list of differences between the expected and actual database columns.
|
||||
///
|
||||
/// Empty is there are no differences.
|
||||
List<String> get errorMessages {
|
||||
|
@ -406,16 +584,84 @@ class SchemaColumnDifference {
|
|||
}).toList();
|
||||
}
|
||||
|
||||
/// A list that stores the differences between expected and actual database columns.
|
||||
///
|
||||
/// This list stores the specific properties that differ between the expected [SchemaColumn] and the actual [SchemaColumn]
|
||||
/// being compared. Each difference is represented by a [_PropertyDifference] instance, which contains the name of the
|
||||
/// property, the expected value, and the actual value.
|
||||
final List<_PropertyDifference> _differingProperties = [];
|
||||
}
|
||||
|
||||
/// Represents a difference between an expected and actual database column property.
|
||||
///
|
||||
/// This class is used within the `SchemaColumnDifference` class to track the specific properties that differ
|
||||
/// between an expected [SchemaColumn] and an actual [SchemaColumn] being compared.
|
||||
///
|
||||
/// The [name] property represents the name of the property that is different, such as "name", "isIndexed",
|
||||
/// "isUnique", "isNullable", "defaultValue", or "deleteRule".
|
||||
///
|
||||
/// The [expectedValue] property represents the expected value of the property, as defined in the schema.
|
||||
///
|
||||
/// The [actualValue] property represents the actual value of the property, as found in the database.
|
||||
///
|
||||
/// The [getErrorMessage] method returns a human-readable error message that describes the difference between
|
||||
/// the expected and actual values for the property, including the name of the table and column.
|
||||
class _PropertyDifference {
|
||||
/// Represents a difference between an expected and actual database column property.
|
||||
///
|
||||
/// This class is used within the `SchemaColumnDifference` class to track the specific properties that differ
|
||||
/// between an expected [SchemaColumn] and an actual [SchemaColumn] being compared.
|
||||
///
|
||||
/// The [name] property represents the name of the property that is different, such as "name", "isIndexed",
|
||||
/// "isUnique", "isNullable", "defaultValue", or "deleteRule".
|
||||
///
|
||||
/// The [expectedValue] property represents the expected value of the property, as defined in the schema.
|
||||
///
|
||||
/// The [actualValue] property represents the actual value of the property, as found in the database.
|
||||
///
|
||||
/// The [getErrorMessage] method returns a human-readable error message that describes the difference between
|
||||
/// the expected and actual values for the property, including the name of the table and column.
|
||||
_PropertyDifference(this.name, this.expectedValue, this.actualValue);
|
||||
|
||||
/// The name of the database column.
|
||||
final String name;
|
||||
|
||||
/// The expected value of the database column property.
|
||||
///
|
||||
/// This represents the value that is expected for the database column property,
|
||||
/// as defined in the schema. It is used to compare against the actual value
|
||||
/// found in the database.
|
||||
final dynamic expectedValue;
|
||||
|
||||
/// The actual value of the database column property.
|
||||
///
|
||||
/// This represents the value that is actually found in the database for the
|
||||
/// column property. It is used to compare against the expected value defined
|
||||
/// in the schema.
|
||||
final dynamic actualValue;
|
||||
|
||||
/// Generates an error message for a column mismatch in the database schema.
|
||||
///
|
||||
/// This method constructs a detailed error message when there's a discrepancy
|
||||
/// between the expected and actual values for a specific column property.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [actualTableName]: The name of the table where the mismatch occurred.
|
||||
/// - [expectedColumnName]: The name of the column with the mismatched property.
|
||||
///
|
||||
/// Returns:
|
||||
/// A formatted error message string that includes:
|
||||
/// - The table name
|
||||
/// - The column name
|
||||
/// - The expected value for the property
|
||||
/// - The actual value found in the migration files
|
||||
///
|
||||
/// The message follows the format:
|
||||
/// "Column '[expectedColumnName]' in table '[actualTableName]' expected
|
||||
/// '[expectedValue]' for '[name]', but migration files yield '[actualValue]'"
|
||||
///
|
||||
/// This method is typically used during schema validation to provide clear
|
||||
/// and actionable error messages for database administrators or developers.
|
||||
String getErrorMessage(String? actualTableName, String? expectedColumnName) {
|
||||
return "Column '$expectedColumnName' in table '$actualTableName' expected "
|
||||
"'$expectedValue' for '$name', but migration files yield '$actualValue'";
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:protevus_database/src/managed/managed.dart';
|
||||
import 'package:protevus_database/src/managed/relationship_type.dart';
|
||||
|
|
Loading…
Reference in a new issue