Skip to content

QueryEngineBase

Wyatt Greenway edited this page Dec 13, 2022 · 7 revisions

class QueryEngineBase extends ProxyClass 📜

QueryEngineBase is the class that all other query engine classes inherit from. It provides "proxy" support for all class instances. QueryEngine, ModelScope, and FieldScope all inherit from this class.

Notes:

  • QueryEngineBase is a sub-part of the QueryEngine, and so is generally referred to simply as the QueryEngine as a whole. This is also the case for ModelScope, and FieldScope, which also make up the QueryEngine as sub-parts, and so are also often referred to simply as "the query engine".

method QueryEngineBase::_fetchScope(
    ...scopeNames: Array<string>,
): QueryEngine | ModelScope | FieldScope
📜

Fetch a previous named scope.

This is useful when you want to change scopes during query operations. For example, if you just did a field-level operation inside a FieldScope, such as query.field.EQ('test'), then you need to return a "model scope" so the user can continue chaining operations. Do to so, you would call: return this._fetchScope('model'); which will fetch and return the current "model scope".

If the specified scope is not found (as a previous scope in the "operation stack"), then this method will simply return this. Because this isn't often desired, and could be a problem, this method takes scopeNames as an array of scopes to "fall back" to if the specified scope can not be found. For example, one could do: return this._fetchScope('model', 'queryEngine'); to request the 'model' scope, but to fall-back to the 'queryEngine' scope if there is no 'model' scope to return.

Arguments:

  • ...scopeNames: Array<string>

    Specify the scopes you wish to fetch, in order, from left-to-right. The first scope found will be returned.

Return value: QueryEngine | ModelScope | FieldScope

Return the specified scope, the first found in the list of provided scopes. If no scope specified is found, then return this instead.

See also: QueryEngineBase._newQueryEngineScope, QueryEngineBase._newModelScope, QueryEngineBase._newFieldScope


method QueryEngineBase::_inheritContext(
    context: object,
    name?: string,
    ...args?: Array<object>,
): object
📜

This "stacks" an incoming "operation context" on top the previous context in the "operation stack". It will also reset the value of the context to undefined, and will generate a new contextID for this new context.

Notes:

Arguments:

Return value: object

Return the new "operation context", with the args objects merged in. This new context will have a prototype that is set to the context provided.

See also: QueryEngineBase._fetchScope


method QueryEngineBase::_newFieldScope(
    Field: Field,
): FieldScope
📜

Create a new "field scope" (FieldScope scope), push it onto the "operation stack", and return the newly created scope.

Arguments:

  • Field: Field

    The field for this "field scope".

Return value: FieldScope

Return a new "field scope" (FieldScope instance), after pushing it onto the internal "operation stack". This also "names" the operation on the stack, so a call to this._fetchScope('field') after this will return this newly created scope.

See also: QueryEngineBase._fetchScope


method QueryEngineBase::_newModelScope(
    Model: class Model,
): ModelScope
📜

Create a new "model scope" (ModelScope scope), push it onto the "operation stack", and return the newly created scope.

Arguments:

  • Model: class Model

    The model class for this "model scope".

Return value: ModelScope

Return a new "model scope" (ModelScope instance), after pushing it onto the internal "operation stack". This also "names" the operation on the stack, so a call to this._fetchScope('model') after this will return this newly created scope.

See also: QueryEngineBase._fetchScope


method QueryEngineBase::_newQueryEngineScope(): QueryEngine 📜

Create a new "root scope" (QueryEngine scope), push it onto the "operation stack", and return the newly created scope.

Return value: QueryEngine

Return a new "root scope" (QueryEngine instance), after pushing it onto the internal "operation stack". This also "names" the operation on the stack, so a call to this._fetchScope('queryEngine') after this will return this newly created scope.

See also: QueryEngineBase._fetchScope


method QueryEngineBase::_pushOperationOntoStack(
    frame: object,
    context?: object,
)
📜

Push a new operation onto the internal "operation stack" for the query.

Arguments:

  • frame: object

    The new "frame" to push onto the stack. This should just be a simple object containing the correct properties for the operation being added. This method will then ensure your new object is added as an "operation context", setting the prototype to the previous "operation context" (frame) in the stack.

  • context?: object

    The context to set as the prototype for this new frame. If not provided, then this will default to the top-most "operation context" already on top of the internal "operation stack".


method QueryEngineBase::clone(): ModelScope | QueryEngine 📜

Clone this query.

Return value: ModelScope | QueryEngine

The query, cloned. By default this will return the most recent "model scope" from the cloned query... if one is found.


method QueryEngineBase::constructor(
    context: object,
): QueryEngineBase
📜

Construct a new QueryEngine instance.

Arguments:

  • context: object

    The "root" "operation context" for the query. This is required to contain a connection property, with a valid Connection instance as a value. Besides that, you can apply any properties you want here, and they will be available on all "operation contexts".

Return value: QueryEngineBase


method QueryEngineBase::filter(
    callback: (operationContext: object, index: number, parts: Array<object>, query: QueryEngine) => boolean;,
): FieldScope | ModelScope | QueryEngine
📜

Clone this query, filtering the internal "operation stack" while doing so. This allows the user to entirely alter the nature of the query. You can filter out any operations you want to filter out. For example, you could choose to filter out all ORDER operations to ensure a query never has any order specified.

The signature for the provided callback nearly matches the signature for Javascript's Array.prototype.filter method: callback(operationContext: object, index: number, operationStack: Array<object>, query: QueryEngine)

Arguments:

  • callback: (operationContext: object, index: number, parts: Array<object>, query: QueryEngine) => boolean;

    The callback to use for filtering. If this callback returns a "falsy" value, then the operation will be filtered out.

Return value: FieldScope | ModelScope | QueryEngine

Return the last (top-most) scope of the original query... unless it was filtered out. The returned query will be identical to the original query... minus any operation that was filtered out.


method QueryEngineBase::getAllModelsUsedInQuery(
    options?: object,
): Array<class Model>
📜

Fetch all models used in the query.

By default, this will return all unique models used across the root query, including models used for table-joining. You can however pass the options { subQueries: true } to return all models used in the query, including those used in sub-queries.

Arguments:

  • options?: object

    Options for the operation. The only option supported is { subQueries: true }, which if enabled, will request that this method also walk sub-queries.

Return value: Array<class Model>

An array of all model classes used in the query.


method QueryEngineBase::getConnection(): Connection 📜

Get the connection supplied to the query engine when it was first created.

Return value: Connection

The connection that was supplied to the query engine when it was created.


method QueryEngineBase::getFieldScopeClass(): class inherits FieldScope 📜

Get the FieldScope class that should be used for the query. By default this will be FieldScope.

This is provided so that the end-user can overload the query engine, adding custom functionality. You could for example inherit from the default FieldScope class and add extra functionality to the query engine. To do so, you would overload this method on your own custom implementation of QueryEngine, and would return your own custom FieldScope class for use in the query engine.

A "field scope" is a sub-scope of the QueryEngine that defines "field level operations". A field scope is returned by the query engine as soon as a field is accessed, i.e. User.where.id would return a FieldScope relating to the User model (the id field). Scopes are used in the Mythix ORM query engine so that you can't do anything funky... such as User.where.EQ('value')... how can a model be equal to a value? It can't... so scopes are used in the engine to ensure you can't do anything silly. This scope defines all operations that can be applied to model fields.

Return value: class inherits FieldScope

The class to use for "field scopes".


method QueryEngineBase::getModel(
    modelName,
): class Model
📜

Get a model class by name.

This will fetch the connection using QueryEngineBase.getConnection, and then will request the model by name from the connection itself.

Arguments:

  • modelName

Return value: class Model

Return the named model class.


method QueryEngineBase::getModelScopeClass(): class inherits ModelScope 📜

Get the ModelScope class that should be used for the query. By default this will be ModelScope.

This is provided so that the end-user can overload the query engine, adding custom functionality. You could for example inherit from the default ModelScope class and add extra functionality to the query engine. To do so, you would overload this method on your own custom implementation of QueryEngine, and would return your own custom ModelScope class for use in the query engine.

A "model scope" is a sub-scope of the QueryEngine that defines "model level operations". A model scope is returned by the query engine as soon as a model is accessed, i.e. User.where would return a ModelScope relating to the User model. A conditional operation, such as EQ will also return a ModelScope... ready for the next field to be specified. Scopes are used in the Mythix ORM query engine so that you can't do anything funky... such as User.where.EQ('value')... how can a model be equal to a value? It can't... so scopes are used in the engine to ensure you can't do anything silly. This scope defines all operations that can be applied to models.

Return value: class inherits ModelScope

The class to use for "model scopes".


method QueryEngineBase::getOperationContext(): object 📜

Return the top-most "operation context" for the query.

The internal operation stack looks like this [ root context, <- context1, <- context2, <- context3, ... ]

Operation contexts are simple objects defining the query operations. Each new context added to the query is pushed on top the "operation stack" internal to the query. Each "operation context" also has its prototype set to the previous "frame" in the stack. This means from the top-most context, you can access attributes from the bottom-most context--assuming the property you are trying to access hasn't also be re-set in a higher-level "frame". For example, you could access a distinct, order, or projections attribute from the top-most operation context, which will always be the "most current" value for the operation you are requesting data for.

Operation contexts always have at least the following properties: operator, value, and queryProp (used for replaying query operations). Each context might also have custom properties... for example, a DISTINCT operation will also have a custom distinct property it sets that is the distinct literal itself.

Return value: object

The top-most "operation context" on the "operation stack".


method QueryEngineBase::getOperationStack(): Array<context> 📜

Return the entire "operation stack" for the query.

The internal operation stack looks like this [ root context, <- context1, <- context2, <- context3, ... ]

Each "frame" on the stack is itself an "operation context". Each "frame" defines an operation for the query, for example a MODEL, EQ, or DISTINCT operation. Essentially a Mythix ORM query is just an array of operations internally--in order. When the query is being used to interface with the underlying database, the "operation stack" is walked, and a part generated for each operation in the stack. For example, a query such as User.where.id.EQ(1) would contain the following operations: [ { MODEL = User } <- { FIELD = id } <- { operator = EQ, value = 1 } ].

Notes:

  • You can dynamically alter the operation stack of a query by using one of QueryEngineBase.filter, or QueryEngineBase.map. Since queries are essentially just arrays of operations, they can be treated much like arrays.

Return value: Array<context>

The entire "operation stack" for the query. This is not a copy of the stack, but the entire stack directly... so do not mutate this value unless you know exactly what you are doing.


method QueryEngineBase::getQueryEngineClass(): class QueryEngine 📜

Get the QueryEngine class that is being used for the query.

This works by calling QueryEngineBase.getQueryEngineScope and returning the constructor property used by this scope. The constructor property will be the QueryEngine class itself.

Return value: class QueryEngine

The custom QueryEngine class being used for the query, or the QueryEngine class Mythix ORM uses (the default).


method QueryEngineBase::getQueryEngineScope(): QueryEngine 📜

Fetch the top-most "root scope" or "queryEngine scope".

Return value: QueryEngine

The top-most "query engine" scope.


method QueryEngineBase::getQueryEngineScopeClass(): class inherits QueryEngine 📜

Get the QueryEngine class that should be used for the query. By default this will be QueryEngine.

This is provided so that the end-user can overload the query engine, adding custom functionality. You could for example inherit from the default QueryEngine class and add extra functionality to the query engine. To do so, you would overload this method on your own custom implementation of QueryEngine, and would return your own custom QueryEngine class for use in the query engine.

A "query scope" is the "root scope" of a QueryEngine that defines "root level operations". A root scope is returned by the query engine as soon as the query is created, i.e. new QueryEngine({ connection }) would return a QueryEngine. Scopes are used in the Mythix ORM query engine so that you can't do anything funky... such as User.where.EQ('value')... how can a model be equal to a value? It can't... so scopes are used in the engine to ensure you can't do anything silly. This scope defines all operations that can be applied to the "root query", such as fetching the operation stack, the operation context, mapping or filtering the query, etc...

Return value: class inherits QueryEngine

The class to use for "root scopes". By default this method will return the class used to initially create the query engine. So for example if you create the query with your own custom class, such as new MyCustomQueryEngine({ connection }), then this will automatically return your class for you (MyCustomQueryEngine in this example).


method QueryEngineBase::getQueryID(): number 📜

Fetch the current (top most) context id of the query.

Each "operation context" gets its own unique ID. This is primarily for caching and comparison operations. Since a new context id is generated for each operation of the query, one can detect if two queries are identical simply by comparing their ids. This id can also be used for cache... since the same id always equates to the exact same query.

Return value: number

The unique "operation context" id for this query. This will always be the id assigned to the top-most "operation context" of the "operation stack".


method QueryEngineBase::isLastOperationCondition(): boolean 📜

Check if the very last operation in the internal "operation stack" is a "condition" operation. "condition" operations are operations that are conditions for the query, for example EQ, GT, LT, etc...

Return value: boolean

Return true if the very last operation on the "operation stack" is categorized as a condition level operation.


method QueryEngineBase::isLastOperationControl(): boolean 📜

Check if the very last operation in the internal "operation stack" is a "control" operation. "control" operations are operations that change how the query behaves, and include LIMIT, OFFSET, ORDER, GROUP_BY, HAVING, and PROJECT.

Return value: boolean

Return true if the very last operation on the "operation stack" is categorized as a control level operation.

See also: ModelScope.LIMIT, ModelScope.OFFSET, ModelScope.ORDER, ModelScope.GROUP_BY, ModelScope.HAVING, ModelScope.PROJECT


method QueryEngineBase::isModelUsedInQuery(
    Model: class Model,
    options?: object,
): boolean
📜

Check if the model specified is used in the query.

By default, this will check if the provided Model is used in the root query, or any table-joins in the root query. You can optionally pass the options { subQueries: true } to also check if the provided Model is used in any sub-queries.

Notes:

Arguments:

  • Model: class Model

    The model class to check for.

  • options?: object

    Options for the operation. The only option supported is { subQueries: true }, which if enabled, will request that this method also walk sub-queries.

Return value: boolean

Return true if the specified Model is used in the query, or any table-joins... false otherwise.


method QueryEngineBase::logQueryOperations() 📜

Debug a query.

This will call console.log for every operation on the internal "operation stack", allowing you to debug each query part--in order.


method QueryEngineBase::map(
    callback: (operationContext: object, index: number, parts: Array<object>, query: QueryEngine) => object;,
): FieldScope | ModelScope | QueryEngine
📜

Clone this query, mapping the internal "operation stack" while doing so. This allows the user to entirely alter the nature of the query. You can map any operations you want to alter. For example, you could choose to alter all ORDER operations, forcing a different order for the query.

The signature for the provided callback nearly matches the signature for Javascript's Array.prototype.map method: callback(operationContext: object, index: number, operationStack: Array<object>, query: QueryEngine) The operationContext here is a copy of the original operation context, so it has its prototype disconnected from the context chain, and you can feel free to modify it (without effecting the original query).

Arguments:

  • callback: (operationContext: object, index: number, parts: Array<object>, query: QueryEngine) => object;

    The callback used to map each "operation context". If the return value is "falsy", or a non-object, then it will be discarded (narrowing the "operation stack" of the final mapped query).

Return value: FieldScope | ModelScope | QueryEngine

Return the last (top-most) scope of the original query. The returned query will be identical to the original query... excepting any operation that was modified.


method QueryEngineBase::queryHasConditions(): boolean 📜

Check if the query has any conditions.

A query might not have conditions... if for example it is defining a table-join.

Return value: boolean

Return true if the query has any conditions, i.e. EQ, or GT.


method QueryEngineBase::queryHasJoins(): boolean 📜

Check if the query has any table joins.

Return value: boolean

Return true if the query is joining on tables, or false otherwise.


static method QueryEngineBase::generateID(): number 📜

Used to generate unique "operation ids" for the query operation stack.

Return value: number

A unique id.


static method QueryEngineBase::getQueryOperationInfo(
    query: QueryEngine,
): QueryInfo
📜

Get information about the query.

This method will return an object containing certain keys that will report on the status of the query.

Interface:

  • interface QueryInfo {
      hasDistinct: boolean;
      hasOrder: boolean;
      hasProjection: boolean;
      hasGroupBy: boolean;
      hasHaving: boolean;
      hasCondition: boolean;
      hasTableJoins: boolean;
      hasField: boolean;
      hasModel: boolean;
    }

Arguments:

Return value: QueryInfo

Return information relating to the query.


static method QueryEngineBase::isQuery(
    value: any,
): boolean
📜

Check to see if the provided value is an instance of a Mythix ORM QueryEngineBase. It will return true if the provided value is an instanceof QueryEngineBase, if the value's constructor property has a truthy _isMythixQueryEngine property (value.constructor._isMythixQueryEngine), or if the provided value has a getOperationContext method.

Arguments:

  • value: any

    Value to check.

Return value: boolean


static method QueryEngineBase::isQueryOperationContext(
    value: object,
): boolean
📜

Check if the provided value is an "operation context".

The query engine works by creating an "operation stack", that is an array of "operation contexts". Any time an operation is carried out on the query engine, such as selecting a new model for example new QueryEngine({ connection }).Model('User') then an "operation" will be pushed onto the operation stack. Each operation on the stack sets its prototype to the previous operation on the stack using Object.create. This means that all "operation contexts" on the stack also include all attributes from all previous operations on the stack via the operation prototype.

Use this method to check if any object is a valid "operation context" from the query engine.

Arguments:

  • value: object

    The value to check.

Return value: boolean

Return true if the provided value is a query engine "operation context", or false otherwise.


method QueryEngineBase::walk(
    callback: (subQuery: QueryEngine, parentOperationContext: object, contextKey: string, depth: number) => void;,
    contextKeys?: Array<string> = [ 'value' ],
): undefined
📜

This method recursively walks the query, calling the provided callback for every sub-query encountered. The provided callback will never be called with this query (the root query being walked).

The callback signature is callback(subQuery: QueryEngine, parentOperationContext: object, contextKey: string, depth: number): undefined where the subQuery is the query found, the parentOperationContext is the parent "operation context" that the sub-query was found on, contextKey is the key the sub-query was found on (usually 'value') inside the parentOperationContext, and depth is the depth at which the sub-query was found, which will always be greater than or equal to 1 (0 is reserved for the root query itself).

Notes:

  • Though you wouldn't generally modify the query while walking it (for that you should instead use Connection.finalizeQuery) it is possible by setting a new sub-query on the contextKey of the parentOperationContext. i.e. parentOperationContext[contextKey] = newSubQuery.

Arguments:

  • callback: (subQuery: QueryEngine, parentOperationContext: object, contextKey: string, depth: number) => void;

    The callback that will be called for each sub-query encountered.

  • contextKeys?: Array<string> (Default: [ 'value' ])

    The "operation context" keys to check for sub-queries. This will almost always be "value" for each operation... however, if you add custom operations that store sub-queries on other operation context property names, then you would want to supply the names of those properties here.

Return value: undefined

Nothing is returned from this method.



Clone this wiki locally