mythix-orm
Advanced tools
Comparing version 1.11.6 to 1.11.7
@@ -5,3 +5,48 @@ 'use strict'; | ||
/// Define an "average" literal for the underlying database. | ||
/// | ||
/// Literals are special types in Mythix ORM that are used to | ||
/// define "literal values" for the underlying database. | ||
/// | ||
/// This literal defines an "average" operation across a single | ||
/// column. It is used by <see>Connection.average</see> to get | ||
/// the average across all rows for a single column in the underlying | ||
/// database. When serialized using the <see>QueryGenerator</see> | ||
/// for the connection, it will turn into a database method that | ||
/// is appropriate for the underlying database. For example, with | ||
/// SQL type databases this would turn into `AVG(column)`. This is | ||
/// often used by the projection engine, to project it as a column | ||
/// to be selected. For example `SELECT AVG(column) ...`. It can | ||
/// be used in other places in the query however, such as `ORDER`, | ||
/// `GROUP BY`, and `HAVING` clauses. | ||
/// | ||
/// There are two primary ways to access literals in Mythix ORM. The | ||
/// first is to simply import them. The second way literals can be | ||
/// accessed is via the connection class itself. All Mythix ORM connection | ||
/// classes export all literals on the `static Literals` attribute of | ||
/// the class. So for example, you could access literals like `SQLiteConnection.Literals.AverageLiteral`. | ||
/// | ||
/// All built-in Mythix ORM literals--except `Literal` and `LiteralBase`--accept a field | ||
/// as their first argument. This field can be a fully qualified field name, an actual | ||
/// <see>Field</see> instance, or another literal. The second argument to all literal constructors | ||
/// is an `options` object, that generally contains connection-specific (and operation-specific) options... | ||
/// however, there are common options that can be supplied, such as `as: string;` which allows you to | ||
/// define an alias for the defined field, and `noProjectionAliases: boolean;`, which allows you to disable | ||
/// the column alias entirely. | ||
/// | ||
/// Example: | ||
/// const { Literals } = require('mythix-orm'); | ||
/// const { SQLiteConnection } = require('mythix-orm-sqlite'); | ||
/// let literal1 = new Literals.AverageLiteral('User:age'); | ||
/// let literal2 = new SQLiteConnection.Literals.AverageLiteral('User:age'); | ||
/// | ||
/// See: LiteralFieldBase | ||
/// | ||
/// See: LiteralBase | ||
class AverageLiteral extends LiteralFieldBase { | ||
/// Return `true`, letting the caller know that | ||
/// this is an "aggregating literal". | ||
/// | ||
/// Return: boolean | ||
/// Return `true`, informing the caller that this literal is used for aggregate operations. | ||
static isAggregate() { | ||
@@ -11,2 +56,24 @@ return true; | ||
/// Convert this literal to a string to be used in a database query. | ||
/// | ||
/// This method proxies the conversion of this literal to the connection | ||
/// by calling <see>Connection.literalToString</see>. If no connection | ||
/// is provided when this is called, then the literal will be converted | ||
/// to a string representing it for debugging, i.e. `'AverageLiteral {}'`. | ||
/// | ||
/// Note: | ||
/// Ultimately, for most connections, this will end up calling | ||
/// <see>QueryGenerator._averageLiteralToString</see>. | ||
/// | ||
/// Arguments: | ||
/// connection?: <see>Connection</see> | ||
/// The connection to use to stringify this literal. If none is provided, | ||
/// then a string representing this object will be returned instead. | ||
/// options?: object | ||
/// A connection and operation specific set of options that can be provided. | ||
/// This might for example be `{ as: 'name' }` to provided a field alias, or | ||
/// `{ isProjection: true }` to define that this is being stringified for use | ||
/// as a field in the query projection. Normally the end-user won't care about | ||
/// any literal options, except `as`, which is commonly used to give your literal | ||
/// an alias. | ||
toString(connection, options) { | ||
@@ -13,0 +80,0 @@ if (!connection) |
@@ -5,3 +5,56 @@ 'use strict'; | ||
/// Define a "count" literal for the underlying database. | ||
/// | ||
/// Literals are special types in Mythix ORM that are used to | ||
/// define "literal values" for the underlying database. | ||
/// | ||
/// This literal defines a "count" operation across all matching | ||
/// rows. It is used by <see>Connection.count</see> to get | ||
/// the number of matching rows for a query. When serialized using | ||
/// the <see>QueryGenerator</see> for the connection, it will turn | ||
/// into a database method that is appropriate for the underlying database. | ||
/// For example, with SQL type databases this would turn into `COUNT(column)`, | ||
/// our `COUNT(*)`. This is often used by the projection engine, to project | ||
/// it as a column to be selected. For example `SELECT COUNT(column) AS count ...`. | ||
/// It can be used in other places in the query however, such as `ORDER`, | ||
/// `GROUP BY`, and `HAVING` clauses. | ||
/// | ||
/// Note: | ||
/// If no field is provided to `CountLiteral` | ||
/// then all fields (`*`) is assumed. | ||
/// | ||
/// Note: | ||
/// It is common to use the `{ as: 'count' }` option to give the count | ||
/// literal an alias (name). | ||
/// | ||
/// There are two primary ways to access literals in Mythix ORM. The | ||
/// first is to simply import them. The second way literals can be | ||
/// accessed is via the connection class itself. All Mythix ORM connection | ||
/// classes export all literals on the `static Literals` attribute of | ||
/// the class. So for example, you could access literals like `SQLiteConnection.Literals.CountLiteral`. | ||
/// | ||
/// All built-in Mythix ORM literals--except `Literal` and `LiteralBase`--accept a field | ||
/// as their first argument. This field can be a fully qualified field name, an actual | ||
/// <see>Field</see> instance, or another literal. The second argument to all literal constructors | ||
/// is an `options` object, that generally contains connection-specific (and operation-specific) options... | ||
/// however, there are common options that can be supplied, such as `as: string;` which allows you to | ||
/// define an alias for the defined field, and `noProjectionAliases: boolean;`, which allows you to disable | ||
/// the column alias entirely. | ||
/// | ||
/// Example: | ||
/// const { Literals } = require('mythix-orm'); | ||
/// const { SQLiteConnection } = require('mythix-orm-sqlite'); | ||
/// let literal1 = new Literals.CountLiteral('*', { as: 'count' }); | ||
/// let literal2 = new SQLiteConnection.CountLiteral.CountLiteral('*', { as: 'count' }); | ||
/// | ||
/// See: LiteralFieldBase | ||
/// | ||
/// See: LiteralBase | ||
class CountLiteral extends LiteralFieldBase { | ||
/// Return `false`, informing the engine that | ||
/// a field is not required for this literal type. | ||
/// | ||
/// Return: boolean | ||
/// Return `false`, informing the caller that this literal | ||
/// does not require a field. | ||
static isFieldRequired() { | ||
@@ -11,2 +64,7 @@ return false; | ||
/// Return `true`, letting the caller know that | ||
/// this is an "aggregating literal". | ||
/// | ||
/// Return: boolean | ||
/// Return `true`, informing the caller that this literal is used for aggregate operations. | ||
static isAggregate() { | ||
@@ -16,2 +74,24 @@ return true; | ||
/// Convert this literal to a string to be used in a database query. | ||
/// | ||
/// This method proxies the conversion of this literal to the connection | ||
/// by calling <see>Connection.literalToString</see>. If no connection | ||
/// is provided when this is called, then the literal will be converted | ||
/// to a string representing it for debugging, i.e. `'CountLiteral {}'`. | ||
/// | ||
/// Note: | ||
/// Ultimately, for most connections, this will end up calling | ||
/// <see>QueryGenerator._countLiteralToString</see>. | ||
/// | ||
/// Arguments: | ||
/// connection?: <see>Connection</see> | ||
/// The connection to use to stringify this literal. If none is provided, | ||
/// then a string representing this object will be returned instead. | ||
/// options?: object | ||
/// A connection and operation specific set of options that can be provided. | ||
/// This might for example be `{ as: 'name' }` to provided a field alias, or | ||
/// `{ isProjection: true }` to define that this is being stringified for use | ||
/// as a field in the query projection. Normally the end-user won't care about | ||
/// any literal options, except `as`, which is commonly used to give your literal | ||
/// an alias. | ||
toString(connection, options) { | ||
@@ -18,0 +98,0 @@ if (!connection) |
@@ -5,3 +5,74 @@ 'use strict'; | ||
/// Define a "distinct" literal for the underlying database. | ||
/// | ||
/// Literals are special types in Mythix ORM that are used to | ||
/// define "literal values" for the underlying database. | ||
/// | ||
/// This literal defines a "distinct" operation across a single | ||
/// column, or entire rows. Distinct is a little different than | ||
/// most other literals. Whereas most other literals are simply | ||
/// used as "values"--either identifiers, methods, or values--this | ||
/// `DistinctLiteral` defines an entire state for a query. It can modify | ||
/// the projection, the order, or modify the query in other ways. On | ||
/// some database drivers, it may cause the engine to take an entirely | ||
/// different path to fetch the data requested. `DistinctLiteral` requires | ||
/// a field, or another literal. It may or may not use this field in | ||
/// underlying database operations. However, to remain | ||
/// consistent in the interface and across databases, it is required. | ||
/// There are some cases in which a `DistinctLiteral` may not alter how | ||
/// a query is carried out. One of these is if it is used as an argument to another | ||
/// literal. For example, if one were to use a `DistinctLiteral` inside a | ||
/// `CountLiteral`, then it would count distinctly across rows, not modify how the | ||
/// query is carried out: `new CountLiteral(new DistinctLiteral('User:id'))`. | ||
/// | ||
/// There are two primary ways to access literals in Mythix ORM. The | ||
/// first is to simply import them. The second way literals can be | ||
/// accessed is via the connection class itself. All Mythix ORM connection | ||
/// classes export all literals on the `static Literals` attribute of | ||
/// the class. So for example, you could access literals like `SQLiteConnection.Literals.DistinctLiteral`. | ||
/// | ||
/// All built-in Mythix ORM literals--except `Literal` and `LiteralBase`--accept a field | ||
/// as their first argument. This field can be a fully qualified field name, an actual | ||
/// <see>Field</see> instance, or another literal. The second argument to all literal constructors | ||
/// is an `options` object, that generally contains connection-specific (and operation-specific) options... | ||
/// however, there are common options that can be supplied, such as `as: string;` which allows you to | ||
/// define an alias for the defined field, and `noProjectionAliases: boolean;`, which allows you to disable | ||
/// the column alias entirely. | ||
/// | ||
/// Note: | ||
/// The `DistinctLiteral` is rarely used directly. Normally you would want to | ||
/// call `query.DISTINCT` instead, which internally uses this literal. | ||
/// | ||
/// Example: | ||
/// const { Literals } = require('mythix-orm'); | ||
/// const { SQLiteConnection } = require('mythix-orm-sqlite'); | ||
/// let literal1 = new Literals.DistinctLiteral('User:firstName'); | ||
/// let literal2 = new SQLiteConnection.Literals.DistinctLiteral('User:firstName'); | ||
/// | ||
/// See: LiteralFieldBase | ||
/// | ||
/// See: LiteralBase | ||
class DistinctLiteral extends LiteralFieldBase { | ||
/// Convert this literal to a string to be used in a database query. | ||
/// | ||
/// This method proxies the conversion of this literal to the connection | ||
/// by calling <see>Connection.literalToString</see>. If no connection | ||
/// is provided when this is called, then the literal will be converted | ||
/// to a string representing it for debugging, i.e. `'CountLiteral {}'`. | ||
/// | ||
/// Note: | ||
/// Ultimately, for most connections, this will end up calling | ||
/// <see>QueryGenerator._distinctLiteralToString</see>. | ||
/// | ||
/// Arguments: | ||
/// connection?: <see>Connection</see> | ||
/// The connection to use to stringify this literal. If none is provided, | ||
/// then a string representing this object will be returned instead. | ||
/// options?: object | ||
/// A connection and operation specific set of options that can be provided. | ||
/// This might for example be `{ as: 'name' }` to provided a field alias, or | ||
/// `{ isProjection: true }` to define that this is being stringified for use | ||
/// as a field in the query projection. Normally the end-user won't care about | ||
/// any literal options, except `as`, which is commonly used to give your literal | ||
/// an alias. | ||
toString(connection, options) { | ||
@@ -8,0 +79,0 @@ if (!connection) |
@@ -5,3 +5,57 @@ 'use strict'; | ||
/// Define a "field" literal for the underlying database. | ||
/// | ||
/// Literals are special types in Mythix ORM that are used to | ||
/// define "literal values" for the underlying database. | ||
/// | ||
/// This literal defines a "field" in the underlying database. | ||
/// It could for example be used in a projection, an `ORDER`, | ||
/// or a `GROUP BY` clause. | ||
/// | ||
/// There are two primary ways to access literals in Mythix ORM. The | ||
/// first is to simply import them. The second way literals can be | ||
/// accessed is via the connection class itself. All Mythix ORM connection | ||
/// classes export all literals on the `static Literals` attribute of | ||
/// the class. So for example, you could access literals like `SQLiteConnection.Literals.FieldLiteral`. | ||
/// | ||
/// All built-in Mythix ORM literals--except `Literal` and `LiteralBase`--accept a field | ||
/// as their first argument. This field can be a fully qualified field name, an actual | ||
/// <see>Field</see> instance, or another literal. The second argument to all literal constructors | ||
/// is an `options` object, that generally contains connection-specific (and operation-specific) options... | ||
/// however, there are common options that can be supplied, such as `as: string;` which allows you to | ||
/// define an alias for the defined field, and `noProjectionAliases: boolean;`, which allows you to disable | ||
/// the column alias entirely. | ||
/// | ||
/// Example: | ||
/// const { Literals } = require('mythix-orm'); | ||
/// const { SQLiteConnection } = require('mythix-orm-sqlite'); | ||
/// let literal1 = new Literals.FieldLiteral('User:age'); | ||
/// let literal2 = new SQLiteConnection.Literals.FieldLiteral('User:age'); | ||
/// | ||
/// See: LiteralFieldBase | ||
/// | ||
/// See: LiteralBase | ||
class FieldLiteral extends LiteralFieldBase { | ||
/// Convert this literal to a string to be used in a database query. | ||
/// | ||
/// This method proxies the conversion of this literal to the connection | ||
/// by calling <see>Connection.literalToString</see>. If no connection | ||
/// is provided when this is called, then the literal will be converted | ||
/// to a string representing it for debugging, i.e. `'FieldLiteral {}'`. | ||
/// | ||
/// Note: | ||
/// Ultimately, for most connections, this will end up calling | ||
/// <see>QueryGenerator._fieldLiteralToString</see>. | ||
/// | ||
/// Arguments: | ||
/// connection?: <see>Connection</see> | ||
/// The connection to use to stringify this literal. If none is provided, | ||
/// then a string representing this object will be returned instead. | ||
/// options?: object | ||
/// A connection and operation specific set of options that can be provided. | ||
/// This might for example be `{ as: 'name' }` to provided a field alias, or | ||
/// `{ isProjection: true }` to define that this is being stringified for use | ||
/// as a field in the query projection. Normally the end-user won't care about | ||
/// any literal options, except `as`, which is commonly used to give your literal | ||
/// an alias. | ||
toString(connection, options) { | ||
@@ -8,0 +62,0 @@ if (!connection) |
@@ -12,2 +12,3 @@ import Field from '../../field'; | ||
public static isLiteralType(value: any): boolean; | ||
public static isAggregate(): boolean; | ||
@@ -14,0 +15,0 @@ public constructor(literal: any, options?: GenericObject); |
@@ -6,5 +6,42 @@ 'use strict'; | ||
/// `LiteralBase` is the class all other literals inherit from. | ||
/// | ||
/// Literals are special types in Mythix ORM that are used to | ||
/// define "literal values" for the underlying database. | ||
/// | ||
/// This literal--being the top most ancestor of all other literals-- | ||
/// defines common behavior for literals. | ||
/// | ||
/// See: AverageLiteral | ||
/// | ||
/// See: CountLiteral | ||
/// | ||
/// See: DistinctLiteral | ||
/// | ||
/// See: FieldLiteral | ||
/// | ||
/// See: Literal | ||
/// | ||
/// See: MaxLiteral | ||
/// | ||
/// See: MinLiteral | ||
/// | ||
/// See: SumLiteral | ||
/// | ||
class LiteralBase { | ||
/// Assist with type-checking | ||
static _isMythixLiteral = true; | ||
/// Use this method to check if a class | ||
/// is a Mythix ORM Literal. It will return | ||
/// `true` if the provided value is a class | ||
/// that inherits from <see>LiteralBase</see>, or | ||
/// if the provided value has an attribute | ||
/// named `_isMythixLiteral` that is truthy. | ||
/// | ||
/// Return: boolean | ||
/// | ||
/// Arguments: | ||
/// value: Function | ||
/// Value to check. | ||
static isLiteralClass(value) { | ||
@@ -23,2 +60,21 @@ if (!value) | ||
/// Check to see if the provided value is | ||
/// an *instance* of a Mythix ORM <see>LiteralBase</see>. | ||
/// Unlike <see>LiteralBase.static isLiteralClass</see>, which | ||
/// checks if a *class* is a <see>LiteralBase</see>, this will check | ||
/// to see if an *instance* is an instance of a | ||
/// Mythix ORM <see>LiteralBase</see>. It will return | ||
/// `true` if the provided value is an `instanceof` | ||
/// <see>LiteralBase</see>, or if the value's `constructor` | ||
/// property has a truthy `_isMythixLiteral` property | ||
/// (`value.constructor._isMythixLiteral`) | ||
/// | ||
/// Note: | ||
/// This method is also a matching instance method. | ||
/// | ||
/// Return: boolean | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// Value to check | ||
static isLiteral(value) { | ||
@@ -44,6 +100,45 @@ if (!value) | ||
/// Check to see if the provided value is | ||
/// an *instance* of *`this`* literal type. | ||
/// Unlike <see>LiteralBase.static isLiteral</see>, which | ||
/// checks if an *instance* is any <see>LiteralBase</see> | ||
/// type, this will check if the provided literal is | ||
/// exactly *`this`* type of literal. It will return | ||
/// `true` if the provided value is an `instanceof` | ||
/// `this` literal class, or if the value's `constructor` | ||
/// property has a name that is equal to `this` literal's | ||
/// class name. | ||
/// (`value.constructor.name === this.name`) | ||
/// | ||
/// Example: | ||
/// console.log(CountLiteral.isLiteralType(new AverageLiteral('User:age'))) | ||
/// // false | ||
/// | ||
/// console.log(CountLiteral.isLiteralType(new CountLiteral('*'))) | ||
/// // true | ||
/// | ||
/// Note: | ||
/// This method is also a matching instance method. | ||
/// | ||
/// Return: boolean | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// Value to check | ||
static isLiteralType(value) { | ||
if (value && value instanceof this) | ||
return true; | ||
return (this.isLiteral(value) && value.constructor && value.constructor.name === this.name); | ||
} | ||
/// Used internally in the engine to know if | ||
/// a literal being operated upon is an aggregating | ||
/// literal or not. | ||
/// | ||
/// Note: | ||
/// This method is also a matching instance method. | ||
/// | ||
/// Return: boolean | ||
/// Return `true`, informing the caller that this literal is used for aggregate operations. | ||
static isAggregate() { | ||
@@ -57,2 +152,9 @@ return false; | ||
/// Construct a new literal. | ||
/// | ||
/// Arguments: | ||
/// literal: string | ||
/// The literal value you wish to insert into the query. | ||
/// options?: object | ||
/// Literal, connection, and operation specific options. | ||
constructor(literal, options) { | ||
@@ -75,2 +177,13 @@ Object.defineProperties(this, { | ||
/// Take the value provided as either a <see>Field</see> | ||
/// instance, or a fully qualified field name, then return the field | ||
/// definition for it. | ||
/// | ||
/// If a literal is provided instead, then simply return the literal | ||
/// without modifying it. | ||
/// | ||
/// Note: | ||
/// This is the inverse operation of <see>LiteralBase.definitionToField</see> | ||
/// | ||
/// See: ModelUtils.parseQualifiedName | ||
fullyQualifiedNameToDefinition(fullyQualifiedName) { | ||
@@ -99,2 +212,12 @@ if (LiteralBase.isLiteral(fullyQualifiedName)) | ||
/// Take a field definition and return the actual <see>Field</see> | ||
/// instance for it. | ||
/// | ||
/// If a literal is provided... or a value that isn't understood | ||
/// (such as a string), then it is simply returned unmodified. | ||
/// | ||
/// Note: | ||
/// This is the inverse operation of <see>LiteralBase.fullyQualifiedNameToDefinition</see> | ||
/// | ||
/// See: ModelUtils.parseQualifiedName | ||
definitionToField(connection, definition) { | ||
@@ -104,3 +227,3 @@ if (LiteralBase.isLiteral(definition)) | ||
if (!definition.fieldNames) | ||
if (!definition || !definition.fieldNames) | ||
return definition; | ||
@@ -127,6 +250,18 @@ | ||
/// Convert the literal value provided to the `constructor` | ||
/// to a string. | ||
/// | ||
/// Return: string | ||
/// The value provided to the `constructor` as a string. | ||
toString() { | ||
return this.literal; | ||
if (!this.literal) | ||
return ('' + this.literal); | ||
return this.literal.toString(); | ||
} | ||
/// Return the raw value provided to the `constructor`. | ||
/// | ||
/// Return: any | ||
/// The raw value provided to the `constructor`. | ||
valueOf() { | ||
@@ -133,0 +268,0 @@ return this.literal; |
@@ -6,3 +6,22 @@ 'use strict'; | ||
/// LiteralFieldBase is the ancestor that all | ||
/// literals dealing with fields inherit from | ||
/// (which is nearly all literals). Its primary | ||
/// function is to enforce being provided fields | ||
/// when the child literal requires them, and to | ||
/// fetch those fields when the engine needs them. | ||
/// | ||
/// See: LiteralBase | ||
/// | ||
/// See: Literal | ||
class LiteralFieldBase extends LiteralBase { | ||
/// This is provided so each child class | ||
/// can overload it. By default it returns | ||
/// `true`. However, any literal that inherits | ||
/// from this literal may override it to instead | ||
/// return `false`. The <see>CountLiteral</see> | ||
/// does exactly this for example. | ||
/// | ||
/// Return: boolean | ||
/// Returns `true` by default, children can change the return value. | ||
static isFieldRequired() { | ||
@@ -12,2 +31,14 @@ return true; | ||
/// Construct a new field-based literal. The | ||
/// first argument should be a fully qualified field name, | ||
/// a <see>Field</see> instance, or another literal. | ||
/// | ||
/// Arguments: | ||
/// field: string | <see>Field</see> | <see>LiteralBase</see> | ||
/// The "field" that this literal is representing. If this is a | ||
/// string, then it is expected to be a fully qualified field name. | ||
/// If instead this is a <see>Field</see>, then that defines the | ||
/// field. Finally, this can also be another literal. | ||
/// options?: object | ||
/// Connection, Literal, and operation specific options for this literal. | ||
constructor(fullyQualifiedName, options) { | ||
@@ -40,2 +71,11 @@ super(undefined, options); | ||
/// Get the fully qualified field name of the field | ||
/// that this literal is representing. | ||
/// | ||
/// Return: string | ||
/// The fully qualified field name of the field this literal | ||
/// is representing. This would be the same field provided to the | ||
/// `constructor` when the literal was first created. | ||
/// | ||
/// See: ModelUtils.parseQualifiedName | ||
getFullyQualifiedFieldName() { | ||
@@ -49,2 +89,8 @@ let definition = this.definition || {}; | ||
/// Get the field represented by this literal. | ||
/// | ||
/// Return: <see>Field</see> | ||
/// The field that this literal is representing. This will | ||
/// be the field given to the `constructor` when the literal | ||
/// was first created. | ||
getField(connection) { | ||
@@ -57,2 +103,6 @@ if (!this.definition) | ||
/// Return the raw field definition for | ||
/// the provided field. | ||
/// | ||
/// See: ModelUtils.parseQualifiedName | ||
valueOf() { | ||
@@ -59,0 +109,0 @@ return this.definition; |
@@ -5,3 +5,18 @@ 'use strict'; | ||
/// This literal type simply defines a pure "literal". | ||
/// It will represent any "raw" value you want to insert | ||
/// directly into any query. This can be useful for custom | ||
/// database operations, math, or anything else you need | ||
/// in "raw" form in the database. | ||
/// | ||
/// Note: | ||
/// **Caution should be taken if using this for values. Values defined by this literal won't ever be escaped.** | ||
class Literal extends LiteralBase { | ||
/// Construct a pure vanilla "literal" for the underlying database. | ||
/// | ||
/// Arguments: | ||
/// literal: any | ||
/// The literal value you want to stringify for the underlying database query engine. | ||
/// options?: object | ||
/// Connection, literal, and operation specific options. | ||
constructor(literal, options) { | ||
@@ -8,0 +23,0 @@ super(literal, options); |
@@ -5,3 +5,48 @@ 'use strict'; | ||
/// Define an "average" literal for the underlying database. | ||
/// | ||
/// Literals are special types in Mythix ORM that are used to | ||
/// define "literal values" for the underlying database. | ||
/// | ||
/// This literal defines an "average" operation across a single | ||
/// column. It is used by <see>Connection.average</see> to get | ||
/// the average across all rows for a single column in the underlying | ||
/// database. When serialized using the <see>QueryGenerator</see> | ||
/// for the connection, it will turn into a database method that | ||
/// is appropriate for the underlying database. For example, with | ||
/// SQL type databases this would turn into `MAX(column)`. This is | ||
/// often used by the projection engine, to project it as a column | ||
/// to be selected. For example `SELECT MAX(column) ...`. It can | ||
/// be used in other places in the query however, such as `ORDER`, | ||
/// `GROUP BY`, and `HAVING` clauses. | ||
/// | ||
/// There are two primary ways to access literals in Mythix ORM. The | ||
/// first is to simply import them. The second way literals can be | ||
/// accessed is via the connection class itself. All Mythix ORM connection | ||
/// classes export all literals on the `static Literals` attribute of | ||
/// the class. So for example, you could access literals like `SQLiteConnection.Literals.MaxLiteral`. | ||
/// | ||
/// All built-in Mythix ORM literals--except `Literal` and `LiteralBase`--accept a field | ||
/// as their first argument. This field can be a fully qualified field name, an actual | ||
/// <see>Field</see> instance, or another literal. The second argument to all literal constructors | ||
/// is an `options` object, that generally contains connection-specific (and operation-specific) options... | ||
/// however, there are common options that can be supplied, such as `as: string;` which allows you to | ||
/// define an alias for the defined field, and `noProjectionAliases: boolean;`, which allows you to disable | ||
/// the column alias entirely. | ||
/// | ||
/// Example: | ||
/// const { Literals } = require('mythix-orm'); | ||
/// const { SQLiteConnection } = require('mythix-orm-sqlite'); | ||
/// let literal1 = new Literals.MaxLiteral('User:age'); | ||
/// let literal2 = new SQLiteConnection.Literals.MaxLiteral('User:age'); | ||
/// | ||
/// See: LiteralFieldBase | ||
/// | ||
/// See: LiteralBase | ||
class MaxLiteral extends LiteralFieldBase { | ||
/// Return `true`, letting the caller know that | ||
/// this is an "aggregating literal". | ||
/// | ||
/// Return: boolean | ||
/// Return `true`, informing the caller that this literal is used for aggregate operations. | ||
static isAggregate() { | ||
@@ -11,2 +56,24 @@ return true; | ||
/// Convert this literal to a string to be used in a database query. | ||
/// | ||
/// This method proxies the conversion of this literal to the connection | ||
/// by calling <see>Connection.literalToString</see>. If no connection | ||
/// is provided when this is called, then the literal will be converted | ||
/// to a string representing it for debugging, i.e. `'MaxLiteral {}'`. | ||
/// | ||
/// Note: | ||
/// Ultimately, for most connections, this will end up calling | ||
/// <see>QueryGenerator._maxLiteralToString</see>. | ||
/// | ||
/// Arguments: | ||
/// connection?: <see>Connection</see> | ||
/// The connection to use to stringify this literal. If none is provided, | ||
/// then a string representing this object will be returned instead. | ||
/// options?: object | ||
/// A connection and operation specific set of options that can be provided. | ||
/// This might for example be `{ as: 'name' }` to provided a field alias, or | ||
/// `{ isProjection: true }` to define that this is being stringified for use | ||
/// as a field in the query projection. Normally the end-user won't care about | ||
/// any literal options, except `as`, which is commonly used to give your literal | ||
/// an alias. | ||
toString(connection, options) { | ||
@@ -13,0 +80,0 @@ if (!connection) |
@@ -5,3 +5,48 @@ 'use strict'; | ||
/// Define an "average" literal for the underlying database. | ||
/// | ||
/// Literals are special types in Mythix ORM that are used to | ||
/// define "literal values" for the underlying database. | ||
/// | ||
/// This literal defines an "average" operation across a single | ||
/// column. It is used by <see>Connection.average</see> to get | ||
/// the average across all rows for a single column in the underlying | ||
/// database. When serialized using the <see>QueryGenerator</see> | ||
/// for the connection, it will turn into a database method that | ||
/// is appropriate for the underlying database. For example, with | ||
/// SQL type databases this would turn into `MIN(column)`. This is | ||
/// often used by the projection engine, to project it as a column | ||
/// to be selected. For example `SELECT MIN(column) ...`. It can | ||
/// be used in other places in the query however, such as `ORDER`, | ||
/// `GROUP BY`, and `HAVING` clauses. | ||
/// | ||
/// There are two primary ways to access literals in Mythix ORM. The | ||
/// first is to simply import them. The second way literals can be | ||
/// accessed is via the connection class itself. All Mythix ORM connection | ||
/// classes export all literals on the `static Literals` attribute of | ||
/// the class. So for example, you could access literals like `SQLiteConnection.Literals.MinLiteral`. | ||
/// | ||
/// All built-in Mythix ORM literals--except `Literal` and `LiteralBase`--accept a field | ||
/// as their first argument. This field can be a fully qualified field name, an actual | ||
/// <see>Field</see> instance, or another literal. The second argument to all literal constructors | ||
/// is an `options` object, that generally contains connection-specific (and operation-specific) options... | ||
/// however, there are common options that can be supplied, such as `as: string;` which allows you to | ||
/// define an alias for the defined field, and `noProjectionAliases: boolean;`, which allows you to disable | ||
/// the column alias entirely. | ||
/// | ||
/// Example: | ||
/// const { Literals } = require('mythix-orm'); | ||
/// const { SQLiteConnection } = require('mythix-orm-sqlite'); | ||
/// let literal1 = new Literals.MinLiteral('User:age'); | ||
/// let literal2 = new SQLiteConnection.Literals.MinLiteral('User:age'); | ||
/// | ||
/// See: LiteralFieldBase | ||
/// | ||
/// See: LiteralBase | ||
class MinLiteral extends LiteralFieldBase { | ||
/// Return `true`, letting the caller know that | ||
/// this is an "aggregating literal". | ||
/// | ||
/// Return: boolean | ||
/// Return `true`, informing the caller that this literal is used for aggregate operations. | ||
static isAggregate() { | ||
@@ -11,2 +56,24 @@ return true; | ||
/// Convert this literal to a string to be used in a database query. | ||
/// | ||
/// This method proxies the conversion of this literal to the connection | ||
/// by calling <see>Connection.literalToString</see>. If no connection | ||
/// is provided when this is called, then the literal will be converted | ||
/// to a string representing it for debugging, i.e. `'MinLiteral {}'`. | ||
/// | ||
/// Note: | ||
/// Ultimately, for most connections, this will end up calling | ||
/// <see>QueryGenerator._minLiteralToString</see>. | ||
/// | ||
/// Arguments: | ||
/// connection?: <see>Connection</see> | ||
/// The connection to use to stringify this literal. If none is provided, | ||
/// then a string representing this object will be returned instead. | ||
/// options?: object | ||
/// A connection and operation specific set of options that can be provided. | ||
/// This might for example be `{ as: 'name' }` to provided a field alias, or | ||
/// `{ isProjection: true }` to define that this is being stringified for use | ||
/// as a field in the query projection. Normally the end-user won't care about | ||
/// any literal options, except `as`, which is commonly used to give your literal | ||
/// an alias. | ||
toString(connection, options) { | ||
@@ -13,0 +80,0 @@ if (!connection) |
@@ -5,3 +5,48 @@ 'use strict'; | ||
/// Define an "average" literal for the underlying database. | ||
/// | ||
/// Literals are special types in Mythix ORM that are used to | ||
/// define "literal values" for the underlying database. | ||
/// | ||
/// This literal defines an "average" operation across a single | ||
/// column. It is used by <see>Connection.average</see> to get | ||
/// the average across all rows for a single column in the underlying | ||
/// database. When serialized using the <see>QueryGenerator</see> | ||
/// for the connection, it will turn into a database method that | ||
/// is appropriate for the underlying database. For example, with | ||
/// SQL type databases this would turn into `SUM(column)`. This is | ||
/// often used by the projection engine, to project it as a column | ||
/// to be selected. For example `SELECT SUM(column) ...`. It can | ||
/// be used in other places in the query however, such as `ORDER`, | ||
/// `GROUP BY`, and `HAVING` clauses. | ||
/// | ||
/// There are two primary ways to access literals in Mythix ORM. The | ||
/// first is to simply import them. The second way literals can be | ||
/// accessed is via the connection class itself. All Mythix ORM connection | ||
/// classes export all literals on the `static Literals` attribute of | ||
/// the class. So for example, you could access literals like `SQLiteConnection.Literals.SumLiteral`. | ||
/// | ||
/// All built-in Mythix ORM literals--except `Literal` and `LiteralBase`--accept a field | ||
/// as their first argument. This field can be a fully qualified field name, an actual | ||
/// <see>Field</see> instance, or another literal. The second argument to all literal constructors | ||
/// is an `options` object, that generally contains connection-specific (and operation-specific) options... | ||
/// however, there are common options that can be supplied, such as `as: string;` which allows you to | ||
/// define an alias for the defined field, and `noProjectionAliases: boolean;`, which allows you to disable | ||
/// the column alias entirely. | ||
/// | ||
/// Example: | ||
/// const { Literals } = require('mythix-orm'); | ||
/// const { SQLiteConnection } = require('mythix-orm-sqlite'); | ||
/// let literal1 = new Literals.SumLiteral('User:age'); | ||
/// let literal2 = new SQLiteConnection.Literals.SumLiteral('User:age'); | ||
/// | ||
/// See: LiteralFieldBase | ||
/// | ||
/// See: LiteralBase | ||
class SumLiteral extends LiteralFieldBase { | ||
/// Return `true`, letting the caller know that | ||
/// this is an "aggregating literal". | ||
/// | ||
/// Return: boolean | ||
/// Return `true`, informing the caller that this literal is used for aggregate operations. | ||
static isAggregate() { | ||
@@ -11,2 +56,24 @@ return true; | ||
/// Convert this literal to a string to be used in a database query. | ||
/// | ||
/// This method proxies the conversion of this literal to the connection | ||
/// by calling <see>Connection.literalToString</see>. If no connection | ||
/// is provided when this is called, then the literal will be converted | ||
/// to a string representing it for debugging, i.e. `'SumLiteral {}'`. | ||
/// | ||
/// Note: | ||
/// Ultimately, for most connections, this will end up calling | ||
/// <see>QueryGenerator._sumLiteralToString</see>. | ||
/// | ||
/// Arguments: | ||
/// connection?: <see>Connection</see> | ||
/// The connection to use to stringify this literal. If none is provided, | ||
/// then a string representing this object will be returned instead. | ||
/// options?: object | ||
/// A connection and operation specific set of options that can be provided. | ||
/// This might for example be `{ as: 'name' }` to provided a field alias, or | ||
/// `{ isProjection: true }` to define that this is being stringified for use | ||
/// as a field in the query projection. Normally the end-user won't care about | ||
/// any literal options, except `as`, which is commonly used to give your literal | ||
/// an alias. | ||
toString(connection, options) { | ||
@@ -13,0 +80,0 @@ if (!connection) |
@@ -136,3 +136,3 @@ 'use strict'; | ||
/// As a method, it can have numerous different "flags" set on it, via | ||
/// the <see>DefaultHelpers.defaultValueFlags</see> factory. Reference this | ||
/// the <see>Helpers.defaultValueFlags</see> factory. Reference this | ||
/// methods documentation for a better understanding of what default value | ||
@@ -139,0 +139,0 @@ /// flags are, and what they do. |
@@ -60,16 +60,76 @@ 'use strict'; | ||
/// `FieldScope` is the "field level" of a query. | ||
/// It manages conditions, such as things like `EQ`, `NEQ`, | ||
/// `GT`, `LT`, etc... | ||
/// | ||
/// Any operator completed on this "field scope" will return | ||
/// the previous `ModelScope` so the user can continue chaining | ||
/// operations on the query. All operators at this level (except | ||
/// `NOT`, `AND`, and `OR`) must be called and provided a value. | ||
/// | ||
/// See: QueryEngineBase | ||
/// | ||
/// See: QueryEngine | ||
/// | ||
/// See: ModelScope | ||
/// | ||
/// Note: | ||
/// `FieldScope` 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 <see>QueryEngineBase</see>, | ||
/// and <see>ModelScope</see>, which also make up the `QueryEngine` as sub-parts, | ||
/// and so are also often referred to simply as "the query engine". | ||
class FieldScope extends QueryEngineBase { | ||
/// Invert the logic of the following operator. | ||
/// | ||
/// This method does not need to be called. | ||
/// | ||
/// Unlike `AND` and `OR` operators, this is not a permanent toggle. | ||
/// As soon as a following operator is encountered, the `NOT` operation | ||
/// will be "turned off". `NOT` will invert any operation, so for example | ||
/// if you did a `User.where.id.NOT.EQ('test')` then this is the same as | ||
/// `User.where.id.NEQ('test')`--selecting everything NOT EQUAL TO `'test'`. | ||
/// `NOT` can also be used in combination with `EXISTS`, `ANY`, `ALL`, `IN`, | ||
/// etc... inverting any operator following `NOT`. | ||
/// | ||
/// Note: | ||
/// `NOT` is only ever used once, and then it is toggled back off. For example, | ||
/// if we did: `User.where.id.NOT.EQ('test').AND.lastName.EQ('Smith')`, then | ||
/// we would have a query where `id != 'test' AND lastName = 'Smith'`. As you | ||
/// can see, the `NOT` doesn't apply to the second `lastName` operator, as it | ||
/// was turned off as soon as the first `EQ` operator was encountered on the `id` | ||
/// field. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
NOT = ProxyClass.autoCall(function() { | ||
this._pushOperationOntoStack({ logical: true, operator: 'NOT', queryProp: 'NOT', not: !this.currentContext.not }); | ||
this._pushOperationOntoStack({ logical: true, operator: 'NOT', queryProp: 'NOT', not: !this.getOperationContext().not }); | ||
return this[ProxyClass.PROXY]; | ||
}); | ||
unscoped() { | ||
return this.currentContext.queryEngineScope.unscoped(this.currentContext); | ||
} | ||
toString(...args) { | ||
return this.currentContext.queryEngineScope.toString(...args); | ||
} | ||
/// This method assists in conversion of provided values to conditions. | ||
/// | ||
/// For example, if you provide a conditional operator (i.e. `EQ`) with | ||
/// a model, i.e. `Role.where.userID.EQ(User)`, then this method will | ||
/// look up the primary key field of the model, and use that to create | ||
/// a table-join. | ||
/// | ||
/// If instead you provided a model *instance* as a value, then the primary | ||
/// key field of that model will be looked up, and the resulting value from | ||
/// the instance will be used as the value. For example: `Role.where.userID.EQ(userInstance)` | ||
/// is equivalent to `Role.where.userID.EQ(userInstance.id)`. | ||
/// | ||
/// Aside from these two exceptions (model classes, and model instances) | ||
/// this method will simply return the value it was provided. | ||
/// | ||
/// This method is called on **every** value provided to **every** call | ||
/// to a conditional operator. It can be overloaded if you want to mutate | ||
/// specific values going into conditional operations. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The value being provided to a conditional operator. | ||
/// | ||
/// Return: any | ||
/// If `value` is a model class, then return a table-join query with that model. If | ||
/// `value` is a model instance, then return the value of the model's primary key field. | ||
/// Otherwise, return the `value` provided. | ||
_fetchOperatorValue(_value) { | ||
@@ -102,2 +162,62 @@ let value = _value; | ||
/// "equals" condition. | ||
/// | ||
/// This will check equality with the value(s) provided. | ||
/// | ||
/// If `null`, `true`, or `false` are provided as a value, | ||
/// then the underlying database driver will convert it to | ||
/// the proper query. For example, given a SQL-type database | ||
/// driver, these values would be generated in the query as | ||
/// `"table"."column" IS NULL`, `"table"."column" IS TRUE`, or `"table"."column" IS FALSE`. | ||
/// | ||
/// If an array is provided as the value, then this stops being an "equals", | ||
/// and instead will turn into an `IN` operator (in SQL-type databases). | ||
/// For example, given the following query: `User.where.id.EQ([ 1, 2, 3, 4 ])`, | ||
/// the resulting SQL query would be `... WHERE "users"."id" IN(1,2,3,4)`. If a `null`, | ||
/// `true`, or `false` value is encountered in the array, then the condition | ||
/// will be grouped, and these values will be split out and handled separately. | ||
/// For example: `User.where.id.EQ([ 1, 2, 3, null, true, false ])` would result | ||
/// in the following SQL: `("users"."id" IS NULL OR "users"."id" IS TRUE OR "users"."id" IS FALSE OR "users"."id" IN(1,2,3))`. | ||
/// If the array provided is empty, than an exception will be thrown. This is for safety reasons. | ||
/// For example, if one were to do `User.where.id.EQ([]).destroy()`, then the result | ||
/// would be `DELETE FROM USERS`... truncating your entire table. This is obviously not | ||
/// at all ideal... so Mythix ORM will throw an exception if an empty array is encountered, | ||
/// requiring that the user be explicit. | ||
/// | ||
/// This condition also has two other variants, `EQ.ALL`, and `EQ.ANY` to check | ||
/// equality with a sub-query. For example, a `User.where.firstName.EQ.ANY(User.where.age.GTE(18).PROJECT('firstName'))` | ||
/// would generate the following SQL query: `... WHERE "users"."firstName" = ANY(SELECT "users"."firstName" FROM "users" WHERE "users"."age" >= 18)`, | ||
/// checking if the users first name matches any of the adult user names in the database. | ||
/// `ALL` is identical in how it functions, except it uses the `ALL` SQL operator instead of the `ANY` operator. | ||
/// `ANY` will check to see if any of the resulting rows match, `ALL` (for this conditional only) will also check | ||
/// if any of the resulting rows of the sub-query match. `ALL` behaves differently than `ANY` for `GT`, `GTE`, `LT`, or `LTE` operators. | ||
/// For `EQ` and `NEQ` operators, `ANY` and `ALL` behave identically. | ||
/// | ||
/// If a sub-query is provided as a value, then one of two things will happen: | ||
/// 1) If the sub-query has no conditions, then a table-join operation will be the result. | ||
/// 2) If the sub-query has conditions, then a sub-query will be executed using an `IN` operator. | ||
/// For example, given the following query: `User.where.id.EQ(User.where.firstName.EQ('Bob').PROJECT('id'))`, | ||
/// the sub-query **does have conditions** (`EQ('Bob')`), so the following SQL query that would be generated | ||
/// would be `... WHERE "users"."id" IN (SELECT "users"."id" FROM "users" WHERE "users"."firstName" = 'Bob')`. | ||
/// If instead a sub-query is provided as a value that **does not have conditions**, such as | ||
/// `User.where.id.EQ(Role.where.userID)`, then this is specifying a table join, joining on the `"roles"` table | ||
/// where `"users"."id" = "roles"."userID"`. | ||
/// | ||
/// Note: | ||
/// Mythix ORM has no `IN` operator built into the query engine by design. It is expected | ||
/// that you will use `EQ` or `NEQ` in combination with an array to get `IN` and `NOT IN` | ||
/// functionality. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The value to check against for equality. If an array is provided, then this operator will | ||
/// turn into an `IN` operator instead. `null`, `true`, and `false` values are treated separately. | ||
/// options?: object | ||
/// Options to supply to the operation. This is not used by Mythix ORM, but is provided should | ||
/// the user or underlying database driver need options. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
EQ = wrapAnyAll.call(this, (value, options) => { | ||
@@ -107,2 +227,62 @@ return addOperatorToQuery.call(this, 'EQ', 'NEQ', value, options); | ||
/// "not equals" condition. | ||
/// | ||
/// This will check inverse (not) equality with the value(s) provided. | ||
/// | ||
/// If `null`, `true`, or `false` are provided as a value, | ||
/// then the underlying database driver will convert it to | ||
/// the proper query. For example, given a SQL-type database | ||
/// driver, these values would be generated in the query as | ||
/// `"table"."column" IS NOT NULL`, `"table"."column" IS NOT TRUE`, or `"table"."column" IS NOT FALSE`. | ||
/// | ||
/// If an array is provided as the value, then this stops being a "not equals", | ||
/// and instead will turn into a `NOT IN` operator (in SQL-type databases). | ||
/// For example, given the following query: `User.where.id.NEQ([ 1, 2, 3, 4 ])`, | ||
/// the resulting SQL query would be `... WHERE "users"."id" NOT IN(1,2,3,4)`. If a `null`, | ||
/// `true`, or `false` value is encountered in the array, then the condition | ||
/// will be grouped, and these values will be split out and handled separately. | ||
/// For example: `User.where.id.NEQ([ 1, 2, 3, null, true, false ])` would result | ||
/// in the following SQL: `("users"."id" IS NOT NULL AND "users"."id" IS NOT TRUE AND "users"."id" IS NOT FALSE AND "users"."id" NOT IN(1,2,3))`. | ||
/// If the array provided is empty, than an exception will be thrown. This is for safety reasons. | ||
/// For example, if one were to do `User.where.id.NEQ([]).destroy()`, then the result | ||
/// would be `DELETE FROM USERS`... truncating your entire table. This is obviously not | ||
/// at all ideal... so Mythix ORM will throw an exception if an empty array is encountered, | ||
/// requiring that the user be explicit. | ||
/// | ||
/// This condition also has two other variants, `NEQ.ALL`, and `NEQ.ANY` to check | ||
/// equality with a sub-query. For example, a `User.where.firstName.NEQ.ANY(User.where.age.GTE(18).PROJECT('firstName'))` | ||
/// would generate the following SQL query: `... WHERE "users"."firstName" != ANY(SELECT "users"."firstName" FROM "users" WHERE "users"."age" >= 18)`, | ||
/// checking if the users first name does not match any of the adult user names in the database. | ||
/// `ALL` is identical in how it functions, except it uses the `ALL` SQL operator instead of the `ANY` operator. | ||
/// `ANY` will check to see if any of the resulting rows do not match, `ALL` (for this conditional only) will also check | ||
/// if any of the resulting rows of the sub-query do not match. `ALL` behaves differently than `ANY` for `GT`, `GTE`, `LT`, or `LTE` operators. | ||
/// For `EQ` and `NEQ` operators, `ANY` and `ALL` behave identically. | ||
/// | ||
/// If a sub-query is provided as a value, then one of two things will happen: | ||
/// 1) If the sub-query has no conditions, then a table-join operation will be the result. | ||
/// 2) If the sub-query has conditions, then a sub-query will be executed using an `IN` operator. | ||
/// For example, given the following query: `User.where.id.NEQ(User.where.firstName.EQ('Bob').PROJECT('id'))`, | ||
/// the sub-query **does have conditions** (`EQ('Bob')`), so the following SQL query that would be generated | ||
/// would be `... WHERE "users"."id" NOT IN (SELECT "users"."id" FROM "users" WHERE "users"."firstName" = 'Bob')`. | ||
/// If instead a sub-query is provided as a value that **does not have conditions**, such as | ||
/// `User.where.id.NEQ(Role.where.userID)`, then this is specifying a table join, joining on the `"roles"` table | ||
/// where `"users"."id" != "roles"."userID"`. | ||
/// | ||
/// Note: | ||
/// Mythix ORM has no `IN` operator built into the query engine by design. It is expected | ||
/// that you will use `EQ` or `NEQ` in combination with an array to get `IN` and `NOT IN` | ||
/// functionality. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The value to check against for inverse (not) equality. If an array is provided, then this operator will | ||
/// turn into an `NOT IN` operator instead. `null`, `true`, and `false` values are treated separately. | ||
/// options?: object | ||
/// Options to supply to the operation. This is not used by Mythix ORM, but is provided should | ||
/// the user or underlying database driver need options. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
NEQ = wrapAnyAll.call(this, (value, options) => { | ||
@@ -112,2 +292,37 @@ return addOperatorToQuery.call(this, 'NEQ', 'EQ', value, options); | ||
/// "greater than" condition. | ||
/// | ||
/// This will check if values in the database are greater than the value provided. | ||
/// | ||
/// This condition also has two other variants, `GT.ALL`, and `GT.ANY` to compare | ||
/// with a sub-query. For example, a `User.where.age.GT.ANY(User.where.age.GTE(18).PROJECT('age'))` | ||
/// would generate the following SQL query: `... WHERE "users"."age" > ANY(SELECT "users"."age" FROM "users" WHERE "users"."age" >= 18)`, | ||
/// checking if the users age is greater than any of the adult aged users in the database. | ||
/// `ALL` is identical in how it functions, except it uses the `ALL` SQL operator instead of the `ANY` operator. | ||
/// `ANY` will check to see if any of the resulting rows of the sub-query match the condition. `ALL` will compare all returned rows | ||
/// from the sub-query, and check to see if the value provided is greater than the *largest value* across all | ||
/// rows of the sub-query. | ||
/// | ||
/// If a sub-query is provided as a value, then one of two things will happen: | ||
/// 1) If the sub-query has no conditions, then a table-join operation will be the result. | ||
/// 2) If the sub-query has conditions, then any exception will be thrown. | ||
/// For example, if a sub-query is provided as a value that **does not have conditions**, such as | ||
/// `User.where.createdAt.GT(Role.where.createdAt)`, then this is specifying a table join, joining on the `"roles"` table | ||
/// where `"users"."createdAt" > "roles"."createdAt"`. | ||
/// | ||
/// Note: | ||
/// If you want to compare "greater than" on a sub-query, use `GT.ANY`, or `GT.ALL`. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The value to compare against for a "greater than" operation. Providing an array, a `null`, a `true`, or | ||
/// a `false` value will generally throw an exception... unless this means something to the underlying database driver. | ||
/// options?: object | ||
/// Options to supply to the operation. This is not used by Mythix ORM, but is provided should | ||
/// the user or underlying database driver need options. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
GT = wrapAnyAll.call(this, (value, options) => { | ||
@@ -117,2 +332,37 @@ return addOperatorToQuery.call(this, 'GT', 'LTE', value, options); | ||
/// "greater than or equals to" condition. | ||
/// | ||
/// This will check if values in the database are greater than or equal to the value provided. | ||
/// | ||
/// This condition also has two other variants, `GTE.ALL`, and `GTE.ANY` to compare | ||
/// with a sub-query. For example, a `User.where.age.GTE.ANY(User.where.age.GTE(18).PROJECT('age'))` | ||
/// would generate the following SQL query: `... WHERE "users"."age" >= ANY(SELECT "users"."age" FROM "users" WHERE "users"."age" >= 18)`, | ||
/// checking if the users age is greater than or equal to any of the adult aged users in the database. | ||
/// `ALL` is identical in how it functions, except it uses the `ALL` SQL operator instead of the `ANY` operator. | ||
/// `ANY` will check to see if any of the resulting rows of the sub-query match the condition. `ALL` will compare all returned rows | ||
/// from the sub-query, and check to see if the value provided is greater than or equal to the *largest value* across all | ||
/// rows of the sub-query. | ||
/// | ||
/// If a sub-query is provided as a value, then one of two things will happen: | ||
/// 1) If the sub-query has no conditions, then a table-join operation will be the result. | ||
/// 2) If the sub-query has conditions, then any exception will be thrown. | ||
/// For example, if a sub-query is provided as a value that **does not have conditions**, such as | ||
/// `User.where.createdAt.GTE(Role.where.createdAt)`, then this is specifying a table join, joining on the `"roles"` table | ||
/// where `"users"."createdAt" >= "roles"."createdAt"`. | ||
/// | ||
/// Note: | ||
/// If you want to compare "greater than or equal to" on a sub-query, use `GTE.ANY`, or `GTE.ALL`. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The value to compare against for a "greater than or equal to" operation. Providing an array, a `null`, a `true`, or | ||
/// a `false` value will generally throw an exception... unless this means something to the underlying database driver. | ||
/// options?: object | ||
/// Options to supply to the operation. This is not used by Mythix ORM, but is provided should | ||
/// the user or underlying database driver need options. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
GTE = wrapAnyAll.call(this, (value, options) => { | ||
@@ -122,2 +372,37 @@ return addOperatorToQuery.call(this, 'GTE', 'LT', value, options); | ||
/// "less than" condition. | ||
/// | ||
/// This will check if values in the database are less than the value provided. | ||
/// | ||
/// This condition also has two other variants, `LT.ALL`, and `LT.ANY` to compare | ||
/// with a sub-query. For example, a `User.where.age.LT.ANY(User.where.age.GTE(18).PROJECT('age'))` | ||
/// would generate the following SQL query: `... WHERE "users"."age" < ANY(SELECT "users"."age" FROM "users" WHERE "users"."age" >= 18)`, | ||
/// checking if the users age is less than any of the adult aged users in the database. | ||
/// `ALL` is identical in how it functions, except it uses the `ALL` SQL operator instead of the `ANY` operator. | ||
/// `ANY` will check to see if any of the resulting rows of the sub-query match the condition. `ALL` will compare all returned rows | ||
/// from the sub-query, and check to see if the value provided is less than the *smallest value* across all | ||
/// rows of the sub-query. | ||
/// | ||
/// If a sub-query is provided as a value, then one of two things will happen: | ||
/// 1) If the sub-query has no conditions, then a table-join operation will be the result. | ||
/// 2) If the sub-query has conditions, then any exception will be thrown. | ||
/// For example, if a sub-query is provided as a value that **does not have conditions**, such as | ||
/// `User.where.createdAt.LT(Role.where.createdAt)`, then this is specifying a table join, joining on the `"roles"` table | ||
/// where `"users"."createdAt" < "roles"."createdAt"`. | ||
/// | ||
/// Note: | ||
/// If you want to compare "less than" on a sub-query, use `LT.ANY`, or `LT.ALL`. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The value to compare against for a "less than" operation. Providing an array, a `null`, a `true`, or | ||
/// a `false` value will generally throw an exception... unless this means something to the underlying database driver. | ||
/// options?: object | ||
/// Options to supply to the operation. This is not used by Mythix ORM, but is provided should | ||
/// the user or underlying database driver need options. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
LT = wrapAnyAll.call(this, (value, options) => { | ||
@@ -127,2 +412,37 @@ return addOperatorToQuery.call(this, 'LT', 'GTE', value, options); | ||
/// "less than or equals to" condition. | ||
/// | ||
/// This will check if values in the database are less than or equal to the value provided. | ||
/// | ||
/// This condition also has two other variants, `LTE.ALL`, and `LTE.ANY` to compare | ||
/// with a sub-query. For example, a `User.where.age.LTE.ANY(User.where.age.GTE(18).PROJECT('age'))` | ||
/// would generate the following SQL query: `... WHERE "users"."age" <= ANY(SELECT "users"."age" FROM "users" WHERE "users"."age" >= 18)`, | ||
/// checking if the users age is less than or equal to any of the adult aged users in the database. | ||
/// `ALL` is identical in how it functions, except it uses the `ALL` SQL operator instead of the `ANY` operator. | ||
/// `ANY` will check to see if any of the resulting rows of the sub-query match the condition. `ALL` will compare all returned rows | ||
/// from the sub-query, and check to see if the value provided is less than or equal to the *smallest value* across all | ||
/// rows of the sub-query. | ||
/// | ||
/// If a sub-query is provided as a value, then one of two things will happen: | ||
/// 1) If the sub-query has no conditions, then a table-join operation will be the result. | ||
/// 2) If the sub-query has conditions, then any exception will be thrown. | ||
/// For example, if a sub-query is provided as a value that **does not have conditions**, such as | ||
/// `User.where.createdAt.LTE(Role.where.createdAt)`, then this is specifying a table join, joining on the `"roles"` table | ||
/// where `"users"."createdAt" <= "roles"."createdAt"`. | ||
/// | ||
/// Note: | ||
/// If you want to compare "less than or equal to" on a sub-query, use `LTE.ANY`, or `LTE.ALL`. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The value to compare against for a "less than or equal to" operation. Providing an array, a `null`, a `true`, or | ||
/// a `false` value will generally throw an exception... unless this means something to the underlying database driver. | ||
/// options?: object | ||
/// Options to supply to the operation. This is not used by Mythix ORM, but is provided should | ||
/// the user or underlying database driver need options. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
LTE = wrapAnyAll.call(this, (value, options) => { | ||
@@ -132,2 +452,36 @@ return addOperatorToQuery.call(this, 'LTE', 'GT', value, options); | ||
/// A "like" wildcard condition. | ||
/// | ||
/// This will check if values in the database are "like" the value provided, using wildcards and pattern matching. | ||
/// | ||
/// The `LIKE` operator in Mythix ORM follows PostgreSQL design, using `%` for a "zero or more" match, and an `_` | ||
/// for a "single character" match. All database engines are required to follow this pattern, even if the underlying | ||
/// database uses a different syntax. If that is the case, then the underlying database driver is required to behave | ||
/// correctly by translating the value to the input it expects. In short, the interface here in Mythix ORM is unified, | ||
/// and it is up to the underlying database driver to carry out the operation correctly. | ||
/// | ||
/// The "escape character" for this operation is always set to a backslash `\` for all database drivers. So if you | ||
/// need to match against a literal `%` or `_` character, you would do so by using the escape character, i.e. `LIKE('100\\%')`. | ||
/// Two escape characters are needed, because Javascript also interprets `\` as an escape character, so a double backslash | ||
/// `'\\'` will "escape" to a single backslash in Javascript. | ||
/// | ||
/// For all databases, the Mythix ORM `LIKE` is case-insensitive. For databases like PostgreSQL, a standard Mythix ORM `LIKE` operation | ||
/// is actually carried out as an `ILIKE` operation to follow this convention of case-insensitivity. There is | ||
/// a boolean `options` that can be supplied to this operator, named `caseSensitive`. If supplied, for example | ||
/// `LIKE('%something%', { caseSensitive: true })`, then the operation will be case-sensitive... but only if the underlying database | ||
/// supports case-sensitivity for `LIKE`... and not all databases do. `LIKE` operations in most SQL databases are case-insensitive by default. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The value to compare against for a "LIKE" operation. Providing any non-string value will | ||
/// likely throw an exception... unless the underlying database driver supports other pattern | ||
/// formats (i.e. `RegExp` is supported in Mongo). | ||
/// options?: object | ||
/// Options to supply to the operation. The only option supported for this operator is the boolean | ||
/// option `caseSensitive`. If `true`, then the database driver will attempt to carry out a case-sensitive | ||
/// operation. If not supported by the database, then the database driver should throw an exception. | ||
/// If a `RegExp` value is used (for databases that support it), then this option will be ignored. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
LIKE(value, options) { | ||
@@ -138,2 +492,36 @@ let caseSensitive = ((options && options.caseSensitive) === true); | ||
/// A "not like" wildcard condition. | ||
/// | ||
/// This will check if values in the database are "not like" the value provided, using wildcards and pattern matching. | ||
/// | ||
/// The `LIKE` operator in Mythix ORM follows PostgreSQL design, using `%` for a "zero or more" match, and an `_` | ||
/// for a "single character" match. All database engines are required to follow this pattern, even if the underlying | ||
/// database uses a different syntax. If that is the case, then the underlying database driver is required to behave | ||
/// correctly by translating the value to the input it expects. In short, the interface here in Mythix ORM is unified, | ||
/// and it is up to the underlying database driver to carry out the operation correctly. | ||
/// | ||
/// The "escape character" for this operation is always set to a backslash `\` for all database drivers. So if you | ||
/// need to match against a literal `%` or `_` character, you would do so by using the escape character, i.e. `NOT LIKE('100\\%')`. | ||
/// Two escape characters are needed, because Javascript also interprets `\` as an escape character, so a double backslash | ||
/// `'\\'` will "escape" to a single backslash in Javascript. | ||
/// | ||
/// For all databases, the Mythix ORM `NOT_LIKE` is case-insensitive. For databases like PostgreSQL, a standard Mythix ORM `NOT_LIKE` operation | ||
/// is actually carried out as an `NOT ILIKE` operation to follow this convention of case-insensitivity. There is | ||
/// a boolean `options` that can be supplied to this operator, named `caseSensitive`. If supplied, for example | ||
/// `NOT_LIKE('%something%', { caseSensitive: true })`, then the operation will be case-sensitive... but only if the underlying database | ||
/// supports case-sensitivity for `NOT LIKE`... and not all databases do. `NOT LIKE` operations in most SQL databases are case-insensitive by default. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The value to compare against for a "NOT LIKE" operation. Providing any non-string value will | ||
/// likely throw an exception... unless the underlying database driver supports other pattern | ||
/// formats (i.e. `RegExp` is supported in Mongo). | ||
/// options?: object | ||
/// Options to supply to the operation. The only option supported for this operator is the boolean | ||
/// option `caseSensitive`. If `true`, then the database driver will attempt to carry out a case-sensitive | ||
/// operation. If not supported by the database, then the database driver should throw an exception. | ||
/// If a `RegExp` value is used (for databases that support it), then this option will be ignored. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
NOT_LIKE(value, options) { | ||
@@ -148,4 +536,8 @@ let caseSensitive = ((options && options.caseSensitive) === true); | ||
} | ||
toString(...args) { | ||
return this.getOperationContext().queryEngineScope.toString(...args); | ||
} | ||
} | ||
module.exports = FieldScope; |
@@ -128,3 +128,62 @@ 'use strict'; | ||
/// `ModelScope` is the "model level" of a query. | ||
/// It manages things like `EXISTS`, `PROJECT`, | ||
/// `ORDER`, `INNER_JOIN`, etc... | ||
/// | ||
/// Being a Proxy, it will "listen" for key access, | ||
/// and lookup fields if there is a key access where | ||
/// the key name isn't found on the class instance itself. | ||
/// In this case, it will check the previous model specified | ||
/// on the top of the internal "operation stack" to see if it | ||
/// owns a field with the name of the missing key. | ||
/// If there is a matching field on the top-most model of the stack, | ||
/// then it will use the field found to push a <see>FieldScope</see> | ||
/// onto the "operation stack", and then return that to the user. | ||
/// Take the following example: | ||
/// ```javascript | ||
/// let queryRoot = new QueryEngine({ connection }); | ||
/// let userIDFieldScope = queryRoot.User.id; | ||
/// ``` | ||
/// When we attempt to access the key `'id'` on | ||
/// the `User` "model scope", we will find that no such key | ||
/// exists on the `ModelScope` class. Now that no such property is found | ||
/// on the `ModelScope` class, the <see>ProxyClass</see> will call the method | ||
/// `MISSING` on the `ModelScope`, and this `MISSING` method | ||
/// will check if the current model (`User`) has a field named `'id'`. | ||
/// The <see>ModelScope._getField</see> method finds this field, and | ||
/// returns it. The `ModelScope` then takes this field instance, | ||
/// and uses it to create and return a <see>FieldScope</see> | ||
/// using <see>QueryEngineBase._newFieldScope</see>. Now | ||
/// that we have a <see>FieldScope</see>, we can continue | ||
/// chaining, now using "field level" operators to act on | ||
/// the field we just looked up. | ||
/// | ||
/// See: QueryEngineBase | ||
/// | ||
/// See: QueryEngine | ||
/// | ||
/// See: FieldScope | ||
/// | ||
/// Note: | ||
/// `ModelScope` 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 <see>QueryEngineBase</see>, | ||
/// and <see>FieldScope</see>, which also make up the `QueryEngine` as sub-parts, | ||
/// and so are also often referred to simply as "the query engine". | ||
class ModelScope extends QueryEngineBase { | ||
/// Get the specified field (by name) from the | ||
/// top-most model on the internal "operation stack". | ||
/// | ||
/// This method is called when a key can not be found | ||
/// on the `ModelScope` instance. The <see>ProxyClass</see> | ||
/// will call the `MISSING` method when the key is not | ||
/// found, and that method in turn calls this `_getField` | ||
/// method to see if the key specified was actually a field | ||
/// name on the most recent model in the "operation stack". | ||
/// | ||
/// Arguments: | ||
/// fieldName: string | ||
/// The field name of the field to fetch from the top-most model on the stack. | ||
/// | ||
/// Return: <see>Field</see> | ||
/// The field found, or `undefined` if no such field is found. | ||
_getField(fieldName) { | ||
@@ -135,6 +194,37 @@ let Model = this.currentContext.Model; | ||
_getQueryEngineClass() { | ||
return this.currentContext.queryEngine; | ||
} | ||
/// Specify a <see>FieldScope</see> directly. | ||
/// | ||
/// This can be useful if you have a name collision, or if | ||
/// you just want to go the direct route to specify a <see>FieldScope</see>. | ||
/// | ||
/// This will find the field specified on the most recent (top-most) | ||
/// model on the internal "operation stack". If the field is found, then | ||
/// it will be used to create a new <see>FieldScope</see>, which will | ||
/// then be pushed onto the "operation stack", and returned to the user. | ||
/// | ||
/// Example: | ||
/// // The two queries below are equivalent. The latter | ||
/// // can be a good way to request a field if the field | ||
/// // name being specified happens to be a name collision | ||
/// // (i.e. has the same name as a QueryEngine or ModelScope | ||
/// // property... such as "ORDER" or "OFFSET" for example). | ||
/// let query1 = User.where.id.EQ('test'); | ||
/// let query2 = User.where.Field('id').EQ('test'); | ||
/// | ||
/// Note: | ||
/// An exception will be thrown in the specified field can not be found. | ||
/// | ||
/// Note: | ||
/// This is one of the rare places in Mythix ORM where a fully qualified field | ||
/// name **SHOULD NOT** be used. The reason should be clear: The model in the | ||
/// operation should already be known. | ||
/// | ||
/// Arguments: | ||
/// fieldName: string | ||
/// A field name (NOT fully qualified), as a string. It must be a field | ||
/// that exists on the model from the top-most <see>ModelScope</see> on | ||
/// the internal "operation stack". | ||
/// | ||
/// Return: <see>FieldScope</see> | ||
/// A new <see>FieldScope</see>, targeted to the field specified by `fieldName`. | ||
Field(fieldName) { | ||
@@ -160,10 +250,91 @@ let field = this._getField(fieldName); | ||
unscoped() { | ||
return this.currentContext.queryEngineScope.unscoped(this.currentContext); | ||
} | ||
toString(...args) { | ||
return this.currentContext.queryEngineScope.toString(...args); | ||
} | ||
/// Merge fields together, replacing, adding, or subtracting from | ||
/// the field set. | ||
/// | ||
/// This method is used by <see>ModelScope.ORDER</see>, <see>ModelScope.GROUP_BY</see>, | ||
/// and <see>ModelScope.PROJECT</see>. It works be replacing, adding, or subtracting | ||
/// from the current set of fields defined by any of these operations. | ||
/// | ||
/// If the first field encountered in the provided `incomingFields` isn't prefixed with | ||
/// a `+` or `-`, and if a `+` or `-` operation doesn't come before the encountered field, then | ||
/// the operation is considered a "replace" operation. For example, in `query.ORDER('User:id')` | ||
/// we are "replacing" the order clause with only a single field: `'User:id'`. If however, | ||
/// we did `query.ORDER('+User:id')` then the `'User:id'` field would be *added* to any current | ||
/// order clause in the query. We could also prefix the entire operation with a single `'+'` | ||
/// string, and then all following fields in the list would be in "add" mode. For example, the | ||
/// following two operations are equivalent: `query.ORDER('+', 'User:firstName', 'User:lastName')`, and | ||
/// `query.ORDER('+User:firstName', '+User:lastName')`. The subtraction operator `'-'` works the | ||
/// same, but in reverse, specifying that we want to remove fields instead of add them: | ||
/// `query.ORDER('-', 'User:firstName', 'User:lastName')`, and `query.ORDER('-User:firstName', '-User:lastName')`. | ||
/// It is also possible to mix the two together: `query.ORDER('+', 'User:firstName', '-', 'User:lastName')`, or | ||
/// `query.ORDER('+User:firstName', '-User:lastName')`. | ||
/// | ||
/// There is one special operator, `'*'`. This operator will add *all* fields from all models that | ||
/// are currently in use on the query. For example, if we did: `Role.where.userID(User.where.id).PROJECT('*')`, | ||
/// this would add *all* `Role` and `User` fields to the projection. Replace, add, and subtract rules still | ||
/// apply with a wildcard. So in our example above we are "replacing" the projection, since no `+` or `-` was | ||
/// encountered before the operation. If you instead wanted to *add* all model fields to the projection, | ||
/// you should instead do: `Role.where.userID(User.where.id).PROJECT('+*')` or `Role.where.userID(User.where.id).PROJECT('+', '*')`. | ||
/// Generally, it probably wouldn't matter much, since you are replacing "every field" anyhow... but it could | ||
/// matter if for example you had previously projected a literal... in which case it would be replaced | ||
/// with all model fields. Subtraction rules also apply to wildcard selectors. | ||
/// | ||
/// The only truly important thing to remember here is that if **no** operation is specified (add or subtract), | ||
/// then the default operation is "replace", so the field set will be replaced entirely for the operation | ||
/// in question. | ||
/// | ||
/// There is one exception to this behavior, for `ORDER.ADD` and `ORDER.REPLACE` **only**. These two operations | ||
/// allow the field sort direction itself to be defined with `+` and `-`... so a | ||
/// `query.ORDER.ADD('+User:firstName', '-User:lastName')` is not requesting that we add the `'User:firstName'` | ||
/// field, and subtract the `'User:lastName'` field... but instead is specifying that we want | ||
/// `'User:firstName'` in `ASC` sort order, whereas we want `'User:lastName'` in `DESC` sort order. | ||
/// `ORDER.ADD` and `ORDER.REPLACE` are the only exceptions to the normal logic defined here. There are | ||
/// two methods so that a user can add to the field set, or replace the field set. A `SUB` (subtract) | ||
/// operation is not needed, because that can be done with `ORDER` anyway, i.e. | ||
/// `query.ORDER.ADD('+User:firstName', '+User:lastName').ORDER('-Role')`, which would "add" the | ||
/// `'User:firstName'` and `'User:lastName'` fields, simultaneously specifying their short direction, | ||
/// and then `ORDER('-Role')` is subtracting every field from the `Role` model. | ||
/// | ||
/// Note: | ||
/// A solo `+` or `-` operation is a no-op: `query.ORDER('+')` and `query.ORDER('-')` | ||
/// will do nothing at all, and will not modify the operation in the slightest. | ||
/// | ||
/// Note: | ||
/// This method supports a short-cut for literals. You can specify a string prefixed | ||
/// with an `@` symbol to specify a literal. For example, `query.PROJECT('@COUNT("users"."firstName") AS "count"')`. | ||
/// Doing so will create a new <see>Literal</see> instance with the value provided. This | ||
/// is not the best way to provide a literal however, because the <see>Literal</see> class | ||
/// defines a "raw" literal, whereas the typed literal classes (such as <see>CountLiteral</see>) | ||
/// actually store the field they are operating on, and can report that field back to the engine. | ||
/// | ||
/// Arguments: | ||
/// currentFields: Map<string, { value: Field | Literal | string; direction?: '+' | '-'; ... }> | ||
/// A map of the current fields for the operation. This is the value from the operation | ||
/// context itself. For example, the `ORDER` method provides this argument via: | ||
/// `this.getOperationContext().order`, providing any previous "order" operation | ||
/// as the current fields. | ||
/// incomingFields: Array<<see>Model</see> | <see>Field</see> | string | literal | '+' | '-' | '*'> | ||
/// Incoming fields to replace, add, or subtract from the operation in question. | ||
/// If no fields at all are provided, then this will reset/nullify the operation. For example, | ||
/// an `ORDER()` would clear any `ORDER BY` clause entirely. If a model is provided, either | ||
/// as a name, i.e. `'User'`, or as the raw model, i.e. `User`, then *all the fields* from the | ||
/// specified model will be added or subtracted. A field can be specified as a fully qualified | ||
/// field name, a raw <see>Field</see> instance, or just the field name itself, i.e. `'firstName'`. | ||
/// If no model is specified (i.e. a non-fully-qualified field name), then the engine will attempt | ||
/// to fetch the field specified from the root model of the query. Literals can be used for all | ||
/// supported operations, `ORDER`, `GROUP_BY`, and `PROJECT`, and they also follow the replace, add | ||
/// and subtract rules of the engine. | ||
/// extraData?: object | ||
/// Extra data to apply to the operation. For example, the `ORDER` operation applies a `direction` property | ||
/// to each field in the map. The field map is a `Map` instance, where each key is the fully qualified field | ||
/// name, or expanded literal value. Each value on the map is an object, containing at least a `value` property | ||
/// that is the field or literal specified. This object can also contain any ancillary operation info, such | ||
/// as in the case of the `ORDER` operation, which will also add a `direction` property to each field in the | ||
/// map to specify the field's (or literal's) sort order. | ||
/// options?: object | ||
/// An options object to pass off to `Literal.toString` when expanding literals for use as `Map` keys. | ||
/// | ||
/// Return: Map<string, { value: Field | Literal | string; direction?: '+' | '-'; ... }> | ||
/// Return the new field set. A `Map` will always be returned, but it is possible | ||
/// for the `Map` to be empty. | ||
margeFields(currentFields, incomingFields, extraData, options) { | ||
@@ -173,7 +344,56 @@ return QueryUtils.margeFields(this, currentFields, incomingFields, extraData, options); | ||
/// Invert the logic of the following operator. | ||
/// | ||
/// This method does not need to be called. | ||
/// | ||
/// Unlike `AND` and `OR` operators, this is not a permanent toggle. | ||
/// As soon as a following operator is encountered, the `NOT` operation | ||
/// will be "turned off". `NOT` will invert any operation, so for example | ||
/// if you did a `User.where.id.NOT.EQ('test')` then this is the same as | ||
/// `User.where.id.NEQ('test')`--selecting everything NOT EQUAL TO `'test'`. | ||
/// `NOT` can also be used in combination with `EXISTS`, `ANY`, `ALL`, `IN`, | ||
/// etc... inverting any operator following `NOT`. | ||
/// | ||
/// Note: | ||
/// `NOT` is only ever used once, and then it is toggled back off. For example, | ||
/// if we did: `User.where.id.NOT.EQ('test').AND.lastName.EQ('Smith')`, then | ||
/// we would have a query where `id != 'test' AND lastName = 'Smith'`. As you | ||
/// can see, the `NOT` doesn't apply to the second `lastName` operator, as it | ||
/// was turned off as soon as the first `EQ` operator was encountered on the `id` | ||
/// field. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
NOT = ProxyClass.autoCall(function() { | ||
this._pushOperationOntoStack({ logical: true, operator: 'NOT', queryProp: 'NOT', not: !this.currentContext.not }); | ||
this._pushOperationOntoStack({ logical: true, operator: 'NOT', queryProp: 'NOT', not: !this.getOperationContext().not }); | ||
return this._fetchScope('model'); | ||
}); | ||
/// Logical `AND`, for ANDing operations together. | ||
/// | ||
/// This method does not need to be called, but it can | ||
/// be called if desired. | ||
/// | ||
/// This is a "toggle", so as soon as it is used, | ||
/// it will continue to be "active" for all following | ||
/// operations. In Mythix ORM you don't need to specify | ||
/// `AND` or `OR` unless you actually need them. By default | ||
/// `AND` is enabled for all queries. So for example you | ||
/// can do `User.where.id.EQ('test').firstName.EQ('John').lastName.EQ('Smith')`, | ||
/// which is exactly the same as `User.where.id.EQ('test').AND.firstName.EQ('John').AND.lastName.EQ('Smith')`. | ||
/// | ||
/// `AND` can also be called to group conditions. For example, you could create | ||
/// the following query: `User.where.id.EQ('test').AND(User.where.lastName.EQ('John').OR.lastName.EQ('Brown'))` | ||
/// to create the following SQL query: `WHERE id = 'test' AND (lastName = 'John' OR lastName = 'Brown')`. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
/// | ||
/// Arguments: | ||
/// query?: <see>QueryEngine</see> | ||
/// An optional query, which if provided, will create a "condition group" as a result. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
AND = ProxyClass.autoCall(function(value) { | ||
@@ -184,2 +404,27 @@ this._pushOperationOntoStack({ logical: true, operator: 'AND', queryProp: 'AND', and: true, or: false, not: false, value }); | ||
/// Logical `OR`, for ORing operations together. | ||
/// | ||
/// This method does not need to be called, but it can | ||
/// be called if desired. | ||
/// | ||
/// This is a "toggle", so as soon as it is used, | ||
/// it will continue to be "active" for all following | ||
/// operations. In Mythix ORM you don't need to specify | ||
/// `AND` or `OR` unless you actually need them. By default | ||
/// `AND` is enabled for all queries. For example you | ||
/// can do `User.where.id.EQ('test').OR.firstName.EQ('John').lastName.EQ('Smith')`, | ||
/// which is exactly the same as `User.where.id.EQ('test').OR.firstName.EQ('John').OR.lastName.EQ('Smith')`. | ||
/// | ||
/// `OR` can also be called to group conditions. For example, you could create | ||
/// the following query: `User.where.id.EQ('test').OR(User.where.firstName.EQ('John').OR.lastName.EQ('Brown'))` | ||
/// to create the following SQL query: `WHERE id = 'test' OR (firstName = 'John' AND lastName = 'Brown')`. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
/// | ||
/// Arguments: | ||
/// query?: <see>QueryEngine</see> | ||
/// An optional query, which if provided, will create a "condition group" as a result. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
OR = ProxyClass.autoCall(function(value) { | ||
@@ -190,2 +435,19 @@ this._pushOperationOntoStack({ logical: true, operator: 'OR', queryProp: 'OR', and: false, or: true, not: false, value }); | ||
/// Apply a `LIMIT` clause to the query. | ||
/// | ||
/// Any valid positive integer is acceptable, as well as `Infinity`. | ||
/// If `Infinity` is used, then the `LIMIT` will either turn into its | ||
/// max possible value (in the billions... depending on the underlying database), | ||
/// or it will be stripped from the query entirely. | ||
/// `NaN`, or anything that isn't a valid positive integer will throw an error. | ||
/// | ||
/// Note: | ||
/// Positive floating point numbers are rounded with `Math.round`. | ||
/// | ||
/// Arguments: | ||
/// limit: number | ||
/// The limit to apply to the query. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
LIMIT(_value) { | ||
@@ -202,2 +464,16 @@ let value = _value; | ||
/// Apply an `OFFSET` clause to the query. | ||
/// | ||
/// Any valid positive integer is acceptable. `Infinity`, `NaN`, | ||
/// or anything that isn't a valid positive integer will throw an error. | ||
/// | ||
/// Note: | ||
/// Positive floating point numbers are rounded with `Math.round`. | ||
/// | ||
/// Arguments: | ||
/// offset: number | ||
/// The offset to apply to the query. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
OFFSET(_value) { | ||
@@ -214,2 +490,29 @@ let value = _value; | ||
/// Apply an `ORDER BY` clause to the query, to sort | ||
/// the results on the fields specified, in either | ||
/// `ASC` or `DESC` order. | ||
/// | ||
/// There are five variants to this method, `ORDER.ASC`, | ||
/// `ORDER.DESC`, `ORDER.ADD`, `ORDER.REPLACE`, and `ORDER` (which is an alias for `ORDER.ASC`), . | ||
/// 1) `ORDER` - Alias for `ORDER.ASC`. | ||
/// 2) `ORDER.ASC` - Follow the rules of <see>ModelScope.margeFields</see>. Each field/literal added is in `ASC` order. | ||
/// 3) `ORDER.DESC` - Follow the rules of <see>ModelScope.margeFields</see>. Each field/literal added is in `DESC` order. | ||
/// 4) `ORDER.ADD` - **DO NOT** follow the rules of <see>ModelScope.margeFields</see>, and instead **add** all fields specified, with their sort order being specified instead by the `+` or `-` prefixes on each field. | ||
/// 5) `ORDER.REPLACE` - **DO NOT** follow the rules of <see>ModelScope.margeFields</see>, and instead **replace** the operation fields to the fields specified, with their sort order being specified instead by the `+` or `-` prefixes on each field. | ||
/// | ||
/// See <see>ModelScope.margeFields</see> to better understand how this method works. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
/// | ||
/// Note: | ||
/// This method will flatten all provided arguments into a one dimensional array, | ||
/// so you can provide arrays or deeply nested arrays for the fields specified. | ||
/// | ||
/// Arguments: | ||
/// ...args: Array<<see>Field</see> | <see>LiteralBase</see> | string | '+' | '-' | '*'> | ||
/// New "fields" to supply to `ORDER`, replacing, adding, or subtracting the | ||
/// specified fields from the `ORDER` operation. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
ORDER = wrapOrderClause.call(this, (...args) => { | ||
@@ -219,2 +522,17 @@ return applyOrderClause.call(this, { direction: '+' }, ...args); | ||
/// Apply a `GROUP BY` clause to the query. | ||
/// | ||
/// See <see>ModelScope.margeFields</see> to better understand how this method works. | ||
/// | ||
/// Note: | ||
/// This method will flatten all provided arguments into a one dimensional array, | ||
/// so you can provide arrays or deeply nested arrays for the fields specified. | ||
/// | ||
/// Arguments: | ||
/// ...args: Array<<see>Field</see> | <see>LiteralBase</see> | string | '+' | '-' | '*'> | ||
/// New "fields" to supply to `GROUP BY`, replacing, adding, or subtracting the | ||
/// specified fields from the `GROUP_BY` operation. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
GROUP_BY(...args) { | ||
@@ -260,2 +578,21 @@ let entities = Nife.arrayFlatten(args); | ||
/// Apply a `HAVING` clause to the query. | ||
/// | ||
/// This will only be applied in the underlying database query | ||
/// if it is also paired with a <see>ModelScope.GROUP_BY</see> operation, | ||
/// otherwise it will be ignored. | ||
/// | ||
/// Example: | ||
/// let adultCountByAge = await User.where | ||
/// .GROUP_BY('User:age') | ||
/// .HAVING(User.where.age.GTE(18)) | ||
/// .PROJECT('User:age', new CountLiteral('User:age', { as: 'count' })) | ||
/// .all(); | ||
/// | ||
/// Arguments: | ||
/// query: <see>QueryEngine</see> | ||
/// The query to use to apply conditions to the `HAVING` clause. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
HAVING(query) { | ||
@@ -266,2 +603,24 @@ this._pushOperationOntoStack({ control: true, operator: 'HAVING', queryProp: 'HAVING', value: query, having: query }); | ||
/// Check if any rows match the query provided. | ||
/// | ||
/// This is on the <see>ModelScope</see> itself because | ||
/// it is never paired with a field, and is an operator that | ||
/// stands all on its own. | ||
/// | ||
/// It can be used to check the existence of any value in the | ||
/// database. For example, you might want to query on users, | ||
/// but only if those users have an "admin" role: | ||
/// `await User.where.email.EQ('test@example.com').EXISTS(Role.where.name.EQ("admin").userID.EQ(new FieldLiteral('User:id')))` | ||
/// | ||
/// Note: | ||
/// You can execute a `NOT EXISTS` operation simply by prefixing `EXISTS` with a `.NOT` | ||
/// operation, for example `query.NOT.EXISTS(...)`. | ||
/// | ||
/// Arguments: | ||
/// query: <see>QueryEngine</see> | ||
/// The sub-query to execute to check for existence. Use a <see>FieldLiteral</see> | ||
/// to pair it with the primary query. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
EXISTS(_query) { | ||
@@ -296,2 +655,24 @@ let query = _query; | ||
/// Replace, add to, or subtract from the projection of the query. | ||
/// | ||
/// See <see>ModelScope.margeFields</see> to better understand how this method works. | ||
/// | ||
/// Note: | ||
/// This method will flatten all provided arguments into a one dimensional array, | ||
/// so you can provide arrays or deeply nested arrays for the fields specified. | ||
/// | ||
/// Note: | ||
/// Mythix ORM collects and returns models (or partial models) based on the projection. | ||
/// By default only the "root model" of a query will be projected and converted into | ||
/// model instances. If you want to fetch more than just the root model while querying, | ||
/// make sure to project the models (or fields from other models) that you want to | ||
/// collect into model instances during load. | ||
/// | ||
/// Arguments: | ||
/// ...args: Array<<see>Field</see> | <see>LiteralBase</see> | string | '+' | '-' | '*'> | ||
/// New "fields" to supply to the projection, replacing, adding, or subtracting the | ||
/// specified fields from the `PROJECT` operation. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
PROJECT(...args) { | ||
@@ -343,2 +724,58 @@ let entities = Nife.arrayFlatten(args); | ||
/// Apply a DISTINCT clause to the query. | ||
/// | ||
/// This method does not need to be called, but it can | ||
/// be called if desired. | ||
/// | ||
/// A field is required for this operation to keep the interface | ||
/// consistent across all database drivers... however, the field | ||
/// specified may not actually be used, depending on database support, | ||
/// or other operations being carried out in the query. | ||
/// | ||
/// If the underlying database supports it (i.e. PostgreSQL), then | ||
/// this will turn into a `DISTINCT ON(field)` operation. If the | ||
/// database (or operation being carried out), doesn't support `DISTINCT ON`, | ||
/// then it will fallback to just a `DISTINCT` across the entire projection. | ||
/// Any `DISTINCT` operation applied to the query will always be the very | ||
/// first part of the projection, regardless of whatever else is projected. | ||
/// | ||
/// This method is *optionally* callable. If not called, then the primary key | ||
/// of the model specified will be used (if available). If no primary key exists | ||
/// on the specified model, then it will fallback to a raw `DISTINCT` clause | ||
/// prefixing the projection. For example: `User.where.DISTINCT.lastName.EQ('Smith')` | ||
/// would be distinct on the primary key of `User` (which would be `id` in our example). | ||
/// If instead we call the operator, then we can supply our own field or literal: | ||
/// `User.where.DISTINCT('User:firstName').lastName.EQ('Smith')`. | ||
/// | ||
/// To turn off any previous `DISTINCT` applied, pass the argument `false` to | ||
/// `DISTINCT`: `User.where.DISTINCT.lastName.EQ('Smith').DISTINCT(false)`. | ||
/// In this example the resulting query would have no `DISTINCT` clause at all, | ||
/// since it was disabled when `DISTINCT(false)` was called. | ||
/// | ||
/// `DISTINCT` changes the nature of the query, and might change how it is carried out | ||
/// by the underlying database driver. For example, a distinct combined with a `count` | ||
/// call would modify the `count` query: `await User.where.DISTINCT('User:id').count()` would | ||
/// actually turn into the following SQL: `SELECT COUNT(DISTINCT "users"."id")`. This | ||
/// is just one example however... just know that the underlying database driver might | ||
/// alter the query, or take a completely different path to query if a `DISTINCT` operation | ||
/// is in-play. | ||
/// | ||
/// Note: | ||
/// The support for a `DISTINCT` clause changes wildly across databases. Some might support | ||
/// `ON` for a specific column, some may not. Some databases might force a certain `ORDER` | ||
/// when using `DISTINCT`, some may not... `DISTINCT` may be supported in sub-queries, or | ||
/// it may not... or might require the query be written differently. Mythix ORM does its best | ||
/// to make a "standard" out of this very non-standard situation (does any SQL actually follow a standard?), | ||
/// but just be aware that `DISTINCT` might bite you if you are changing database drivers to a | ||
/// database that behaves differently. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
/// | ||
/// Arguments: | ||
/// field: <see>Field</see> | <see>LiteralBase</see> | string | false = Model.getPrimaryKeyField() | ||
/// The field or literal to be `DISTINCT ON` (if the database supports it). If `false` | ||
/// is specified, then any previous `DISTINCT` operation is cleared. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
DISTINCT = ProxyClass.autoCall(function(_fullyQualifiedName) { | ||
@@ -379,2 +816,22 @@ let fullyQualifiedName = _fullyQualifiedName; | ||
/// Specify an `INNER JOIN` operation for joining tables. | ||
/// | ||
/// This method does not need to be called, but it can | ||
/// be called if desired. | ||
/// | ||
/// All Mythix ORM "join" operations should come immediately | ||
/// before a table join. For example: `User.where.INNER_JOIN.id.EQ(Role.where.userID)`. | ||
/// They are "toggles", and so will remain "on" once used. In short, if | ||
/// you specify an `INNER_JOIN` at the very beginning of your query, then | ||
/// **ALL** table joins in the query will be `INNER JOIN`, unless you specify | ||
/// other join types. | ||
/// | ||
/// Note: | ||
/// `INNER_JOIN` is the default table join type in Mythix ORM if no other table | ||
/// join type is specified in the query. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
INNER_JOIN = ProxyClass.autoCall(function() { | ||
@@ -385,2 +842,22 @@ this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'INNER_JOIN', value: 'inner', joinType: 'inner', joinOuter: false }); | ||
/// Specify a `LEFT JOIN` operation for joining tables. | ||
/// | ||
/// This method does not need to be called, but it can | ||
/// be called if desired. | ||
/// | ||
/// All Mythix ORM "join" operations should come immediately | ||
/// before a table join. For example: `User.where.LEFT_JOIN.id.EQ(Role.where.userID)`. | ||
/// They are "toggles", and so will remain "on" once used. In short, if | ||
/// you specify a `LEFT_JOIN` at the very beginning of your query, then | ||
/// **ALL** table joins in the query will be `LEFT JOIN`, unless you specify | ||
/// other join types, i.e. `User.where.LEFT_JOIN.id.EQ(Role.where.userID).AND.INNER_JOIN.id.EQ(Organization.where.userID)`. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
/// | ||
/// Arguments: | ||
/// outerJoin: boolean = false | ||
/// If `true`, then this will result in a `LEFT OUTER JOIN` if the database supports it. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
LEFT_JOIN = ProxyClass.autoCall(function(outerJoin) { | ||
@@ -391,2 +868,22 @@ this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'LEFT_JOIN', value: 'left', joinType: 'left', joinOuter: !!outerJoin }); | ||
/// Specify a `RIGHT JOIN` operation for joining tables. | ||
/// | ||
/// This method does not need to be called, but it can | ||
/// be called if desired. | ||
/// | ||
/// All Mythix ORM "join" operations should come immediately | ||
/// before a table join. For example: `User.where.RIGHT_JOIN.id.EQ(Role.where.userID)`. | ||
/// They are "toggles", and so will remain "on" once used. In short, if | ||
/// you specify a `RIGHT_JOIN` at the very beginning of your query, then | ||
/// **ALL** table joins in the query will be `RIGHT JOIN`, unless you specify | ||
/// other join types, i.e. `User.where.RIGHT_JOIN.id.EQ(Role.where.userID).AND.INNER_JOIN.id.EQ(Organization.where.userID)`. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
/// | ||
/// Arguments: | ||
/// outerJoin: boolean = false | ||
/// If `true`, then this will result in a `RIGHT OUTER JOIN` if the database supports it. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
RIGHT_JOIN = ProxyClass.autoCall(function(outerJoin) { | ||
@@ -397,2 +894,22 @@ this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'RIGHT_JOIN', value: 'right', joinType: 'right', joinOuter: !!outerJoin }); | ||
/// Specify a `FULL JOIN` operation for joining tables. | ||
/// | ||
/// This method does not need to be called, but it can | ||
/// be called if desired. | ||
/// | ||
/// All Mythix ORM "join" operations should come immediately | ||
/// before a table join. For example: `User.where.FULL_JOIN.id.EQ(Role.where.userID)`. | ||
/// They are "toggles", and so will remain "on" once used. In short, if | ||
/// you specify a `FULL_JOIN` at the very beginning of your query, then | ||
/// **ALL** table joins in the query will be `FULL JOIN`, unless you specify | ||
/// other join types, i.e. `User.where.FULL_JOIN.id.EQ(Role.where.userID).AND.INNER_JOIN.id.EQ(Organization.where.userID)`. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
/// | ||
/// Arguments: | ||
/// outerJoin: boolean = false | ||
/// If `true`, then this will result in a `FULL OUTER JOIN` if the database supports it. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
FULL_JOIN = ProxyClass.autoCall(function(outerJoin) { | ||
@@ -403,2 +920,17 @@ this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'FULL_JOIN', value: 'full', joinType: 'full', joinOuter: !!outerJoin }); | ||
/// Specify a `CROSS JOIN` operation for joining tables. | ||
/// | ||
/// This method does not need to be called. | ||
/// | ||
/// All Mythix ORM "join" operations should come immediately | ||
/// before a table join. For example: `User.where.CROSS_JOIN.id.EQ(Role.where.userID)`. | ||
/// They are "toggles", and so will remain "on" once used. In short, if | ||
/// you specify a `CROSS_JOIN` at the very beginning of your query, then | ||
/// **ALL** table joins in the query will be `CROSS JOIN`, unless you specify | ||
/// other join types, i.e. `User.where.CROSS_JOIN.id.EQ(Role.where.userID).AND.INNER_JOIN.id.EQ(Organization.where.userID)`. | ||
/// | ||
/// SyntaxType: FunctionDeclaration | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
CROSS_JOIN = ProxyClass.autoCall(function() { | ||
@@ -409,2 +941,25 @@ this._pushOperationOntoStack({ join: true, operator: 'JOIN', queryProp: 'CROSS_JOIN', value: 'cross', joinType: 'cross', joinOuter: false }); | ||
/// Specify a custom join operation for the underlying database. | ||
/// It is recommended that you use a literal, though that isn't required. | ||
/// | ||
/// All Mythix ORM "join" operations should come immediately | ||
/// before a table join. For example: `User.where.JOIN('RIGHT INNER JOIN').id.EQ(Role.where.userID)`. | ||
/// They are "toggles", and so will remain "on" once used. | ||
/// | ||
/// Note: | ||
/// This method should be avoided if at all possible, since it will | ||
/// likely be database specific, making your code not as portable to | ||
/// another database driver. | ||
/// | ||
/// Note: | ||
/// Is there a standard join type that Mythix ORM missed, that should be | ||
/// supported across most or all databases? If so, let us know by opening | ||
/// an Issue or PR on our GitHub page. Thank you! | ||
/// | ||
/// Arguments: | ||
/// type: <see>Literal</see> | string | ||
/// The join type to use in the underlying database (a literal value). | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// Return a <see>ModelScope</see> to allow the user to continue chaining operations on the query. | ||
JOIN(type) { | ||
@@ -417,4 +972,8 @@ if (!(Nife.instanceOf(type, 'string') || LiteralBase.isLiteral(type))) | ||
} | ||
toString(...args) { | ||
return this.getOperationContext().queryEngineScope.toString(...args); | ||
} | ||
} | ||
module.exports = ModelScope; |
@@ -11,3 +11,19 @@ 'use strict'; | ||
/// `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. | ||
/// | ||
/// Note: | ||
/// `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 <see>ModelScope</see>, | ||
/// and <see>FieldScope</see>, which also make up the `QueryEngine` as sub-parts, | ||
/// and so are also often referred to simply as "the query engine". | ||
class QueryEngineBase extends ProxyClass { | ||
/// Used to generate unique "operation ids" | ||
/// for the query operation stack. | ||
/// | ||
/// Return: number | ||
/// A unique id. | ||
static generateID() { | ||
@@ -17,2 +33,25 @@ return uuid++; | ||
/// 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: boolean | ||
/// Return `true` if the provided `value` is a query engine "operation context", | ||
/// or `false` otherwise. | ||
static isQueryOperationContext(value) { | ||
@@ -22,2 +61,15 @@ return !!(value && value.isQueryOperationContext); | ||
/// Check to see if the provided value is | ||
/// an *instance* of a Mythix ORM <see>QueryEngineBase</see>. | ||
/// It will return `true` if the provided value is an `instanceof` | ||
/// <see>QueryEngineBase</see>, if the value's `constructor` | ||
/// property has a truthy `_isMythixQueryEngine` property | ||
/// (`value.constructor._isMythixQueryEngine`), or if the | ||
/// provided value has a `getOperationContext` method. | ||
/// | ||
/// Return: boolean | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// Value to check | ||
static isQuery(value) { | ||
@@ -30,3 +82,3 @@ if (!value) | ||
if (value._isQueryEngine) | ||
if (value._isMythixQueryEngine) | ||
return true; | ||
@@ -40,12 +92,60 @@ | ||
static queryOperationInfo(queryContext) { | ||
/// 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: | ||
/// query: <see>QueryEngine</see> | ||
/// The query to get information on. | ||
/// | ||
/// Return: QueryInfo | ||
/// Return information relating to the query. | ||
static getQueryOperationInfo(query) { | ||
let contextParams = { | ||
hasCondition: false, | ||
hasField: false, | ||
hasModel: false, | ||
hasDistinct: false, | ||
hasOrder: false, | ||
hasProjection: false, | ||
hasGroupBy: false, | ||
hasHaving: false, | ||
hasCondition: false, | ||
hasTableJoins: false, | ||
hasField: false, | ||
hasModel: false, | ||
}; | ||
if (!queryContext) | ||
if (!query) | ||
return contextParams; | ||
let queryContext = query.getOperationContext(); | ||
if (queryContext.distinct) | ||
contextParams.hasDistinct = true; | ||
if (queryContext.order) | ||
contextParams.hasOrder = true; | ||
if (queryContext.projection) | ||
contextParams.hasProjection = true; | ||
if (queryContext.groupBy) | ||
contextParams.hasGroupBy = true; | ||
if (queryContext.having) | ||
contextParams.hasHaving = true; | ||
if (queryContext.condition) | ||
@@ -60,5 +160,31 @@ contextParams.hasCondition = true; | ||
contextParams.hasTableJoins = query.queryHasJoins(); | ||
return contextParams; | ||
} | ||
/// Get the `ModelScope` class that should be used | ||
/// for the query. By default this will be <see>ModelScope</see>. | ||
/// | ||
/// This is provided so that the end-user can overload the | ||
/// query engine, adding custom functionality. You could for | ||
/// example inherit from the default <see>ModelScope</see> 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: class inherits <see>ModelScope</see> | ||
/// The class to use for "model scopes". | ||
getModelScopeClass() { | ||
@@ -68,2 +194,25 @@ return this.getQueryEngineScope().getModelScopeClass(); | ||
/// Get the `FieldScope` class that should be used | ||
/// for the query. By default this will be <see>FieldScope</see>. | ||
/// | ||
/// This is provided so that the end-user can overload the | ||
/// query engine, adding custom functionality. You could for | ||
/// example inherit from the default <see>FieldScope</see> 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: class inherits <see>FieldScope</see> | ||
/// The class to use for "field scopes". | ||
getFieldScopeClass() { | ||
@@ -73,2 +222,29 @@ return this.getQueryEngineScope().getFieldScopeClass(); | ||
/// Get the `QueryEngine` class that should be used | ||
/// for the query. By default this will be <see>QueryEngine</see>. | ||
/// | ||
/// This is provided so that the end-user can overload the | ||
/// query engine, adding custom functionality. You could for | ||
/// example inherit from the default <see>QueryEngine</see> 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: class inherits <see>QueryEngine</see> | ||
/// 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). | ||
getQueryEngineScopeClass() { | ||
@@ -78,2 +254,31 @@ return this.getQueryEngineScope().constructor; | ||
/// 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. | ||
/// | ||
/// Note: | ||
/// You probably should not use this method directly unless you know | ||
/// exactly what you are doing. Instead you should use the <see>QueryEngineBase._pushOperationOntoStack</see>, | ||
/// <see>QueryEngineBase._newQueryEngineScope</see>, <see>QueryEngineBase._newModelScope</see>, or | ||
/// <see>QueryEngineBase._newFieldScope</see> to push operations onto the stack. | ||
/// | ||
/// Arguments: | ||
/// context: object | ||
/// The previous (top-most) "operation context" on top the "operation stack". | ||
/// name?: string | ||
/// The name for this operation context. This is only ever used when the "scope" | ||
/// changes by using one of <see>QueryEngineBase._newQueryEngineScope</see>, | ||
/// <see>QueryEngineBase._newModelScope</see>, or <see>QueryEngineBase._newFieldScope</see>. | ||
/// These methods will set a "scope name" so that it can later be fetched using | ||
/// <see>QueryEngineBase._fetchScope</see>. Scope names will only ever be one of | ||
/// `'queryEngine'`, `'model'`, or `'field'`. | ||
/// ...args?: Array<object> | ||
/// Other objects to merge into the context. | ||
/// | ||
/// Return: 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: QueryEngineBase._fetchScope | ||
_inheritContext(context, name, ...args) { | ||
@@ -97,2 +302,31 @@ let newContext = Object.assign( | ||
/// 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 <see>FieldScope</see>, | ||
/// 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: <see>QueryEngine</see> | <see>ModelScope</see> | <see>FieldScope</see> | ||
/// Return the specified scope, the first found in the list of provided scopes. If | ||
/// no scope specified is found, then return `this` instead. | ||
/// | ||
/// See: QueryEngineBase._newQueryEngineScope | ||
/// | ||
/// See: QueryEngineBase._newModelScope | ||
/// | ||
/// See: QueryEngineBase._newFieldScope | ||
_fetchScope(...scopeNames) { | ||
@@ -123,5 +357,15 @@ let context = this.getOperationContext(); | ||
/// Create a new "root scope" (`QueryEngine` scope), push it onto the | ||
/// "operation stack", and return the newly created scope. | ||
/// | ||
/// Return: <see>QueryEngine</see> | ||
/// 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: QueryEngineBase._fetchScope | ||
_newQueryEngineScope() { | ||
const QueryEngineClass = this.getQueryEngineClass(); | ||
let context = this.currentContext; | ||
let context = this.getOperationContext(); | ||
let newContext = this._inheritContext(context, 'queryEngine'); | ||
@@ -133,6 +377,20 @@ let newScope = new QueryEngineClass(newContext); | ||
/// Create a new "model scope" (`ModelScope` scope), push it onto the | ||
/// "operation stack", and return the newly created scope. | ||
/// | ||
/// Arguments: | ||
/// Model: class <see>Model</see> | ||
/// The model class for this "model scope". | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// 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: QueryEngineBase._fetchScope | ||
_newModelScope(Model) { | ||
let ModelScopeClass = this.getModelScopeClass(); | ||
let modelName = Model.getModelName(); | ||
let context = this.currentContext; | ||
let context = this.getOperationContext(); | ||
let extra = {}; | ||
@@ -177,5 +435,19 @@ let isFirst = !context.rootModel; | ||
/// Create a new "field scope" (`FieldScope` scope), push it onto the | ||
/// "operation stack", and return the newly created scope. | ||
/// | ||
/// Arguments: | ||
/// Field: <see>Field</see> | ||
/// The field for this "field scope". | ||
/// | ||
/// Return: <see>FieldScope</see> | ||
/// 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: QueryEngineBase._fetchScope | ||
_newFieldScope(Field) { | ||
let fieldName = Field.fieldName; | ||
let context = this.currentContext; | ||
let context = this.getOperationContext(); | ||
let FieldScopeClass = this.getFieldScopeClass(); | ||
@@ -202,7 +474,23 @@ let extra = {}; | ||
constructor(context) { | ||
/// 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 <see>Connection</see> instance as a value. Besides that, you can apply | ||
/// any properties you want here, and they will be available on all "operation contexts". | ||
constructor(_context) { | ||
super(); | ||
this._isQueryEngine = true; | ||
let context = Object.create(_context || null); | ||
Object.defineProperties(this, { | ||
'_isMythixQueryEngine': { | ||
writable: true, | ||
enumerable: false, | ||
configurable: true, | ||
value: true, | ||
}, | ||
}); | ||
if (!context) | ||
@@ -212,6 +500,18 @@ throw new TypeError('QueryEngineBase::constructor: "context" required.'); | ||
if (!context.rootContext) { | ||
context._isQueryEngine = true; | ||
context.rootContext = context; | ||
context._isMythixQueryEngine = true; | ||
context.rootContext = Object.assign( | ||
{}, | ||
context, | ||
{ | ||
contextID: QueryEngineBase.generateID(), | ||
}, | ||
); | ||
context = Object.create(context); | ||
} | ||
context.isQueryOperationContext = true; | ||
if (!context.contextID) | ||
context.contextID = QueryEngineBase.generateID(); | ||
if (!context.operationStack) | ||
@@ -248,6 +548,44 @@ context.operationStack = []; | ||
/// 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: 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". | ||
getQueryID() { | ||
return this.currentContext.contextID; | ||
return this.getOperationContext().contextID; | ||
} | ||
/// 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: object | ||
/// The top-most "operation context" on the "operation stack". | ||
getOperationContext() { | ||
@@ -257,6 +595,49 @@ return this.currentContext; | ||
/// 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 } ]`. | ||
/// | ||
/// Note: | ||
/// You can dynamically alter the operation stack of a query by using one of | ||
/// <see>QueryEngineBase.filter</see>, or <see>QueryEngineBase.map</see>. Since | ||
/// queries are essentially just arrays of operations, they can be treated much | ||
/// like arrays. | ||
/// | ||
/// Return: 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. | ||
getOperationStack() { | ||
return this.currentContext.operationStack; | ||
return this.getOperationContext().operationStack; | ||
} | ||
/// 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: boolean | ||
/// Return `true` if the very last operation on the "operation stack" | ||
/// is categorized as a `control` level operation. | ||
/// | ||
/// See: ModelScope.LIMIT | ||
/// | ||
/// See: ModelScope.OFFSET | ||
/// | ||
/// See: ModelScope.ORDER | ||
/// | ||
/// See: ModelScope.GROUP_BY | ||
/// | ||
/// See: ModelScope.HAVING | ||
/// | ||
/// See: ModelScope.PROJECT | ||
isLastOperationControl() { | ||
@@ -272,2 +653,9 @@ let queryParts = this.getOperationStack(); | ||
/// 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: boolean | ||
/// Return `true` if the very last operation on the "operation stack" | ||
/// is categorized as a `condition` level operation. | ||
isLastOperationCondition() { | ||
@@ -283,2 +671,9 @@ let queryParts = this.getOperationStack(); | ||
/// Check if the query has any conditions. | ||
/// | ||
/// A query might not have conditions... if for example | ||
/// it is defining a table-join. | ||
/// | ||
/// Return: boolean | ||
/// Return `true` if the query has any conditions, i.e. `EQ`, or `GT`. | ||
queryHasConditions() { | ||
@@ -289,2 +684,6 @@ let context = this.getOperationContext(); | ||
/// Check if the query has any table joins. | ||
/// | ||
/// Return: boolean | ||
/// Return `true` if the query is joining on tables, or `false` otherwise. | ||
queryHasJoins() { | ||
@@ -301,2 +700,7 @@ let queryParts = this.getOperationStack(); | ||
/// 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. | ||
logQueryOperations() { | ||
@@ -317,2 +721,16 @@ let query = this.getOperationStack(); | ||
/// 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". | ||
_pushOperationOntoStack(queryPart, _context) { | ||
@@ -331,6 +749,18 @@ let context = _context || this.getOperationContext(); | ||
/// Get the `connection` supplied to the query | ||
/// engine when it was first created. | ||
/// | ||
/// Return: <see>Connection</see> | ||
/// The `connection` that was supplied to the query engine when it was created. | ||
getConnection() { | ||
return this.currentContext.connection; | ||
return this.getOperationContext().connection; | ||
} | ||
/// Get a model class by name. | ||
/// | ||
/// This will fetch the `connection` using <see>QueryEngineBase.getConnection</see>, | ||
/// and then will request the model by name from the connection itself. | ||
/// | ||
/// Return: class <see>Model</see> | ||
/// Return the named model class. | ||
getModel(modelName) { | ||
@@ -341,3 +771,3 @@ let connection = this.getConnection(); | ||
if (!Model) { | ||
let models = this.currentContext.models; | ||
let models = this.getOperationContext().models; | ||
if (models) | ||
@@ -350,10 +780,29 @@ Model = models[modelName]; | ||
/// Fetch the top-most "root scope" or "queryEngine scope". | ||
/// | ||
/// Return: <see>QueryEngine</see> | ||
/// The top-most "query engine" scope. | ||
getQueryEngineScope() { | ||
return this.currentContext.queryEngineScope; | ||
return this.getOperationContext().queryEngineScope; | ||
} | ||
/// Get the `QueryEngine` class that is being used for the query. | ||
/// | ||
/// This works by calling <see>QueryEngineBase.getQueryEngineScope</see> | ||
/// and returning the `constructor` property used by this scope. The | ||
/// `constructor` property will be the `QueryEngine` class itself. | ||
/// | ||
/// Return: class <see>QueryEngine</see> | ||
/// The custom `QueryEngine` class being used for the query, or | ||
/// the `QueryEngine` class Mythix ORM uses (the default). | ||
getQueryEngineClass() { | ||
return this.currentContext.queryEngineScope.constructor; | ||
return this.getOperationContext().queryEngineScope.constructor; | ||
} | ||
/// Clone this query. | ||
/// | ||
/// Return: <see>ModelScope</see> | <see>QueryEngine</see> | ||
/// The query, cloned. By default this will return the | ||
/// most recent "model scope" from the cloned query... if one is found. | ||
clone() { | ||
@@ -363,2 +812,22 @@ return this.map((part) => part)._fetchScope('model'); | ||
/// 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: <see>FieldScope</see> | <see>ModelScope</see> | <see>QueryEngine</see> | ||
/// 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. | ||
filter(callback) { | ||
@@ -388,2 +857,24 @@ const Klass = this.getQueryEngineScopeClass(); | ||
/// 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: <see>FieldScope</see> | <see>ModelScope</see> | <see>QueryEngine</see> | ||
/// 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. | ||
map(callback) { | ||
@@ -414,2 +905,29 @@ const Klass = this.getQueryEngineScopeClass(); | ||
/// 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). | ||
/// | ||
/// Note: | ||
/// Though you wouldn't generally modify the query while walking it | ||
/// (for that you should instead use <see>Connection.finalizeQuery</see>) | ||
/// 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> = [ '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: undefined | ||
/// Nothing is returned from this method. | ||
walk(callback, _checkContextKeys) { | ||
@@ -442,3 +960,20 @@ const walkQueries = (query, parent, contextKey, depth) => { | ||
getAllModelsUsedInQuery() { | ||
/// 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: Array<class <see>Model</see>> | ||
/// An array of all model classes used in the query. | ||
getAllModelsUsedInQuery(_options) { | ||
let options = _options || {}; | ||
let Models = new Set(); | ||
@@ -455,6 +990,9 @@ let query = this.getOperationStack(); | ||
let operatorValue = queryPart.value; | ||
if (!QueryEngineBase.isQuery(operatorValue) || operatorValue.queryHasConditions()) | ||
if (!QueryEngineBase.isQuery(operatorValue)) | ||
continue; | ||
let SubModels = operatorValue.getAllModelsUsedInQuery(); | ||
if (options.subQueries !== true && operatorValue.queryHasConditions()) | ||
continue; | ||
let SubModels = operatorValue.getAllModelsUsedInQuery(options); | ||
for (let j = 0, jl = SubModels.length; j < jl; j++) { | ||
@@ -471,4 +1009,26 @@ let Model = SubModels[j]; | ||
isModelUsedInQuery(Model) { | ||
let allModels = this.getAllModelsUsedInQuery(); | ||
/// 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. | ||
/// | ||
/// Note: | ||
/// This internally calls <see>QueryEngineBase.getAllModelsUsedInQuery</see> | ||
/// an then checks for the existence of the provided `Model` in the result. | ||
/// | ||
/// Arguments: | ||
/// Model: class <see>Model</see> | ||
/// 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: boolean | ||
/// Return `true` if the specified `Model` is used in the query, or | ||
/// any table-joins... `false` otherwise. | ||
isModelUsedInQuery(Model, options) { | ||
let allModels = this.getAllModelsUsedInQuery(options); | ||
return (allModels.indexOf(Model) >= 0); | ||
@@ -475,0 +1035,0 @@ } |
@@ -19,2 +19,14 @@ import ConnectionBase from '../connection/connection-base'; | ||
export declare interface QueryInfo { | ||
hasDistinct: boolean; | ||
hasOrder: boolean; | ||
hasProjection: boolean; | ||
hasGroupBy: boolean; | ||
hasHaving: boolean; | ||
hasCondition: boolean; | ||
hasTableJoins: boolean; | ||
hasField: boolean; | ||
hasModel: boolean; | ||
} | ||
export declare class QueryEngine<T = ConnectionBase> { | ||
@@ -25,3 +37,3 @@ // QueryEngineBase | ||
static isQuery(value: any): boolean; | ||
static queryOperationInfo(queryContext: GenericObject): { hasCondition: boolean; hasField: boolean; hasModel: boolean; }; | ||
static getQueryOperationInfo(query: QueryEngine): QueryInfo; | ||
@@ -80,3 +92,2 @@ public getModelScopeClass(): QueryEngine; | ||
public _getField(fieldName: string): Field | undefined; | ||
public _getQueryEngineClass(): QueryEngineClass; | ||
public Field(fieldName: string): QueryEngine; | ||
@@ -83,0 +94,0 @@ public LIMIT(value: number): QueryEngine; |
@@ -10,3 +10,54 @@ 'use strict'; | ||
/// QueryEngine is the "root level" of a query. | ||
/// It manages things like `MERGE`, `unscoped`, | ||
/// and all the database interfaces, such as `all`, | ||
/// `first`, `pluck`, etc... | ||
/// | ||
/// Being a Proxy, it will "listen" for key access, | ||
/// and lookup models if there is a key access where | ||
/// the key name starts with an upper-case letter. In this | ||
/// case, it will check the `connection` it owns to see | ||
/// if such a model (by name) is registered with the | ||
/// connection. If there is, then it will use the model | ||
/// found to push a <see>ModelScope</see> onto the "operation stack", | ||
/// and then return that to the user. Take the following example: | ||
/// ```javascript | ||
/// let queryRoot = new QueryEngine({ connection }); | ||
/// let userModelScope = queryRoot.User; | ||
/// ``` | ||
/// When we attempt to access the key `'User'` on | ||
/// the QueryEngine scope, we will find that no such key | ||
/// exists on the `QueryEngine` class. Now that no such property is found | ||
/// on the `QueryEngine` class, the <see>ProxyClass</see> will call the method | ||
/// `MISSING` on the `QueryEngine`, and this `MISSING` method | ||
/// will check the `connection` to see if there is a model | ||
/// named `'User'`. The `connection` finds this model, and | ||
/// returns it. The `QueryEngine` then takes this model class, | ||
/// and uses it to create and return a <see>ModelScope</see> | ||
/// using <see>QueryEngineBase._newModelScope</see>. Now | ||
/// that we have a <see>ModelScope</see>, we can continue | ||
/// chaining by looking up a field `queryRoot.User.id`, and | ||
/// the process repeats on `ModelScope`, but this time looking | ||
/// for a field from the `connection` instead of a model. The | ||
/// model owning the field is already known, because we already | ||
/// have a `Model` class on our "operation stack" for the query. | ||
/// So the field `id` is looked up on the `User` model, and if | ||
/// found, returned as a <see>FieldScope</see> using <see>QueryEngineBase._newFieldScope</see>. | ||
/// | ||
/// See: QueryEngineBase | ||
/// | ||
/// See: ModelScope | ||
/// | ||
/// See: FieldScope | ||
/// | ||
/// Note: | ||
/// `QueryEngine` is the primary part "the query engine", and so is generally referred to | ||
/// simply as the `QueryEngine` as a whole. This is also the case for <see>ModelScope</see>, | ||
/// and <see>FieldScope</see>, and <see>QueryEngineBase</see> which also make up the `QueryEngine` | ||
/// as sub-parts, and so are also often referred to simply as "the query engine". | ||
class QueryEngine extends QueryEngineBase { | ||
/// Get the `ModelScope` class for the | ||
/// query engine. | ||
/// | ||
/// See: QueryEngineBase.getModelScopeClass | ||
getModelScopeClass() { | ||
@@ -16,2 +67,6 @@ return ModelScope; | ||
/// Get the `FieldScope` class for the | ||
/// query engine. | ||
/// | ||
/// See: QueryEngineBase.getFieldScopeClass | ||
getFieldScopeClass() { | ||
@@ -21,17 +76,30 @@ return FieldScope; | ||
constructor(_context) { | ||
let context = Object.assign( | ||
Object.create(_context || {}), | ||
{ | ||
currentScopeName: 'queryEngine', | ||
isQueryOperationContext: true, | ||
contextID: QueryEngineBase.generateID(), | ||
}, | ||
); | ||
/// 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 <see>Connection</see> instance as a value. Besides that, you can apply | ||
/// any properties you want here, and they will be available on all "operation contexts". | ||
constructor(context) { | ||
super(context); | ||
context.queryEngineScope = this; | ||
let operationContext = this.getOperationContext(); | ||
operationContext.currentScopeName = 'queryEngine'; | ||
operationContext.queryEngineScope = this; | ||
} | ||
/// This method will directly fetch a model by | ||
/// name and return a <see>ModelScope</see>. | ||
/// | ||
/// This can be useful when you have a name collision, | ||
/// or when you just want to go the direct route to get | ||
/// a model operation in place. | ||
/// | ||
/// Arguments: | ||
/// modelName: string | ||
/// The model name to fetch and use for the `ModelScope`. | ||
/// | ||
/// Return: <see>ModelScope</see> | ||
/// A new `ModelScope` using the model found by the name provided. | ||
Model(modelName) { | ||
@@ -45,11 +113,29 @@ let model = this.getModel(modelName); | ||
/// This method will reset the query "operation stack" | ||
/// back to the root context--and, if possible, to the root | ||
/// model scope and root model projection. | ||
/// All other operations will be wiped, including | ||
/// the any <see>Model.static defaultScope</see> that has been | ||
/// applied. One would generally call this as the very | ||
/// first thing on an query to ensure it has been reset | ||
/// back to its primary parts *before* you would | ||
/// apply any other operations. For example: `let users = await User.where.unscoped().lastName.EQ('Smith').all();`, | ||
/// would "ignore" any <see>Model.static defaultScope</see> applied to | ||
/// the query, including any default `ORDER` that `defaultScope` | ||
/// applied. `User.where.lastName.EQ('Smith').unscoped()` would | ||
/// be equivalent to `User.where` (assuming no <see>Model.static defaultScope</see> | ||
/// is in-play). | ||
/// | ||
/// Return: <see>QueryEngine</see> | ||
/// A new query, reset back to only its root context, or the root model | ||
/// scope (if there is a root model available on the query). If there | ||
/// is a root model available, then its projection will also be applied | ||
/// as usual. | ||
unscoped() { | ||
let context = this.getOperationContext(); | ||
let QueryEngineClass = this.constructor; | ||
let queryEngine = new QueryEngineClass({ | ||
connection: this.getConnection(), | ||
}); | ||
let QueryEngineClass = this.getQueryEngineScopeClass(); | ||
let queryEngine = new QueryEngineClass(context.rootContext); | ||
if (context.rootModelName) | ||
queryEngine = queryEngine[context.rootModelName]; | ||
if (context.rootModel) | ||
queryEngine = queryEngine._newModelScope(context.rootModel); | ||
@@ -59,2 +145,20 @@ return queryEngine; | ||
/// Stringify this query, using the underlying database | ||
/// connection. This is similar to a `toSQL` method in other ORMs. | ||
/// It isn't exactly that though... because for non-SQL | ||
/// database drivers, it might stringify the query into | ||
/// something that is non-SQL. By default the query will | ||
/// be turned into a string that represents a `SELECT` | ||
/// statement (or other type of fetch statement) for the | ||
/// underlying database driver. | ||
/// | ||
/// Arguments: | ||
/// ...args?: Array<any> | ||
/// Any arguments you want to pass off to the underlying | ||
/// <see>QueryGenerator.toConnectionString</see> method that | ||
/// is called to stringify the query. | ||
/// | ||
/// Return: string | ||
/// The query, turned into a "fetch" representation for the underlying | ||
/// database driver. For SQL-based databases this would be a `SELECT` statement. | ||
toString(...args) { | ||
@@ -67,2 +171,58 @@ let connection = this.getConnection(); | ||
/// Merge one query onto another one. | ||
/// | ||
/// This method will merge two queries together | ||
/// by "replaying" the provided one onto `this` | ||
/// one. For example: | ||
/// ```javascript | ||
/// let userQuery = User.where.id.EQ(1); | ||
/// let roleQuery = Role.where.userID.EQ(User.where.id).name.EQ('admin'); | ||
/// let finalQuery = userQuery.AND.MERGE(roleQuery); | ||
/// // finalQuery == User.where.id.EQ(1).AND.Role.where.userID.EQ(User.where.id).name.EQ('admin'); | ||
/// ``` | ||
/// | ||
/// This is almost like a "concatenate" operation, but there are a few notable differences: | ||
/// 1) The first "logical" operator encountered is always skipped | ||
/// 2) `PROJECT` operations are skipped by default (but can be merged with the use of `options`) | ||
/// 3) Any `GROUP_BY`, `ORDER`, or `PROJECT` are *merged* together, instead of replacing the previous operation. | ||
/// | ||
/// The first logical operator is skipped for a good reason. It is to allow one to `OR` merge | ||
/// the query, or `AND` merge it instead. If we were to simply "concatenate" the stacks together, | ||
/// then we might have contexts that look like (pseudo/concept code): | ||
/// `[ User, AND, id, EQ(1) ].OR.MERGE([ Role, AND, userID, EQ(...), ... ])`. | ||
/// The problem here should be obvious: The `OR` is (nearly) immediately followed by an `AND` in | ||
/// the query we are merging with. If we didn't skip the first logical operator, then this would | ||
/// nearly always be the case, especially since the `QueryEngine` turns on the `AND` operator | ||
/// by default for all new queries. This is why we skip the first logical operator, so that we | ||
/// are able to `.AND.MERGE(...)`, or `.OR.MERGE(...)` a query together... a distinction which | ||
/// sometimes can be vitally important. | ||
/// | ||
/// `PROJECT` operations are skipped by default unless you specify the `options` of `{ projections: true }`. | ||
/// This is simply because it often isn't desired to have projections merge over from sub-queries that | ||
/// are being merged in... causing extra data to be pulled from the database. Projections in Mythix ORM | ||
/// should **always** be deliberate and direct. | ||
/// On the other hand, `ORDER` and `GROUP_BY` operations are automatically merged for you by default. The | ||
/// reason should be fairly obvious, if two parts of the query need a specific order, or group-by fields, | ||
/// then they probably shouldn't be skipped. They can always be skipped if desired by passing the `options` | ||
/// of `{ orders: false }`, or `{ groupBys: false }` (or both). | ||
/// | ||
/// Note: | ||
/// Because merges (and other operations) can get quite complex, it is recommend that you always | ||
/// apply a projection, order, limit, and offset immediately before you use your query to interact | ||
/// with the database. | ||
/// | ||
/// Arguments: | ||
/// incomingQuery: <see>QueryEngine</see> | ||
/// The query to merge/replay on-top a clone of `this` query. | ||
/// options?: object | ||
/// Options for the operation. See the table below for a list of possible options: | ||
/// | Option | Type | Default Value | Description | | ||
/// | ------------- | ---- | ------------- | ----------- | | ||
/// | `projections` | `boolean` | `false` | If `true`, then also merge projections together. | | ||
/// | `orders` | `boolean` | `true` | If `true`, then also merge order clauses together. | | ||
/// | `groupBys` | `boolean` | `true` | If `true`, then also merge group-by clauses together. | | ||
/// | `connection` | `Connection` | `this.getConnection()` | The connection to supply to the newly created `QueryEngine`. If none is supplied, then `this.getConnection()` is used to retrieve the connection. | | ||
/// | ||
/// Return: <see>QueryEngine</see> | ||
/// Return the newly merged query. | ||
MERGE(_incomingQueryEngine, _options) { | ||
@@ -128,2 +288,29 @@ let incomingQueryEngine = _incomingQueryEngine; | ||
/// Fetch all rows matching the query from the database. | ||
/// | ||
/// By default all rows fetched will be converted into | ||
/// <see>Model</see> instances. | ||
/// | ||
/// This method will fetch in batches. By default the batch | ||
/// size is `500`, but can be modified with the `{ batchSize: number; }` | ||
/// `options`. Even though it fetches from the database in batches, | ||
/// it won't return until all data has been fetched from the database. | ||
/// | ||
/// Example: | ||
/// let smithFamilyUsers = await User.where.lastName.EQ('Smith').all(); | ||
/// | ||
/// Note: | ||
/// To stream from the database instead, use the | ||
/// <see>QueryEngine.cursor</see> method. | ||
/// | ||
/// Arguments: | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. This specific operation also has a `batchSize: number;` | ||
/// option that can be used to specify the batch size for fetching. | ||
/// | ||
/// Return: Array<Model> | ||
/// An array of all matching root model instances found by the query. | ||
async all(options) { | ||
@@ -138,2 +325,32 @@ let connection = this.getConnection(options && options.connection); | ||
/// Fetch all rows matching the query from the database, | ||
/// by streaming them, using an async generator. | ||
/// | ||
/// By default all rows fetched will be converted into | ||
/// <see>Model</see> instances. | ||
/// | ||
/// This method will fetch in batches. By default the batch | ||
/// size is `500`, but can be modified with the `{ batchSize: number; }` | ||
/// `options`. All data being fetched will be "streamed" to the caller | ||
/// via an async generator return value. | ||
/// | ||
/// Example: | ||
/// for await (let smithFamilyUser of User.where.lastName.EQ('Smith').cursor()) { | ||
/// console.log(smithFamilyUser); | ||
/// } | ||
/// | ||
/// Note: | ||
/// To collect all results at once instead of streaming, | ||
/// use the <see>QueryEngine.all</see> method instead. | ||
/// | ||
/// Arguments: | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. This specific operation also has a `batchSize: number;` | ||
/// option that can be used to specify the batch size for fetching. | ||
/// | ||
/// Return: Array<Model> | ||
/// An array of all matching root model instances found by the query. | ||
async *cursor(_options) { | ||
@@ -145,2 +362,30 @@ let options = _options || {}; | ||
/// Fetch the first matching row(s) from the database. | ||
/// | ||
/// By default all rows fetched will be converted into | ||
/// <see>Model</see> instances. | ||
/// | ||
/// This method will always apply a `LIMIT` of `limit`, but it | ||
/// won't modify the `OFFSET`... so it is possible to get | ||
/// the nth item(s) by supplying your own `OFFSET` on the query. | ||
/// | ||
/// Example: | ||
/// let firstUser = await User.where.lastName.EQ('Smith').ORDER.ASC('User:lastName').first(); | ||
/// | ||
/// Note: | ||
/// An `ORDER` should be applied on the query before | ||
/// calling this method... or you might get funky results. | ||
/// | ||
/// Arguments: | ||
/// limit: number = 1 | ||
/// The number of rows to fetch from the database. The default is `1`. | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. | ||
/// | ||
/// Return: Array<Model> | Model | ||
/// Return just one `Model` if the `limit` is set to `1`, otherwise | ||
/// return an `Array` of `Model`. | ||
async first(_limit, options) { | ||
@@ -155,2 +400,35 @@ let limit = (_limit == null) ? 1 : _limit; | ||
/// Fetch the last matching row(s) from the database. | ||
/// | ||
/// By default all rows fetched will be converted into | ||
/// <see>Model</see> instances. | ||
/// | ||
/// This method will always apply a `LIMIT` of `limit`, but it | ||
/// won't modify the `OFFSET`... so it is possible to get | ||
/// the nth item(s) by supplying your own `OFFSET` on the query. | ||
/// | ||
/// This method works by forcing the query `ORDER` to invert itself, | ||
/// and then it selects the first `limit` rows from the top. | ||
/// | ||
/// Example: | ||
/// // The `ORDER` operation here will be forcefully inverted | ||
/// // to select the "last" row from the top. | ||
/// let firstUser = await User.where.lastName.EQ('Smith').ORDER.ASC('User:lastName').last(); | ||
/// | ||
/// Note: | ||
/// An `ORDER` should be applied on the query before | ||
/// calling this method... or you might get funky results. | ||
/// | ||
/// Arguments: | ||
/// limit: number = 1 | ||
/// The number of rows to fetch from the database. The default is `1`. | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. | ||
/// | ||
/// Return: Array<Model> | Model | ||
/// Return just one `Model` if the `limit` is set to `1`, otherwise | ||
/// return an `Array` of `Model`. | ||
async last(_limit, options) { | ||
@@ -165,2 +443,39 @@ let limit = (_limit == null) ? 1 : _limit; | ||
/// Update all matching rows in the database with | ||
/// the attributes provided. | ||
/// | ||
/// This method will bulk-write to the rows matching | ||
/// the query, using the attributes provided to update | ||
/// matching rows. The attributes provided can be a model instance, | ||
/// but will generally just be an object of attributes | ||
/// (where the key names must match model `fieldName`s). | ||
/// | ||
/// Example: | ||
/// // Update all users who are 'inactive' to now be 'active' | ||
/// await User.where.status.EQ('inactive').updateAll({ status: 'active' }); | ||
/// | ||
/// Note: | ||
/// **This will not call onBefore or onAfter update hooks | ||
/// on the model instances**. It is a direct bulk-write | ||
/// operation to the underlying database. | ||
/// | ||
/// Note: | ||
/// This operation will only ever update the "root model" | ||
/// table. The "root model" of a query is the first model | ||
/// used in the query. For example, `User` is the root model | ||
/// in the query `User.where`. | ||
/// | ||
/// Arguments: | ||
/// attributes: object | <see>Model</see> | ||
/// The attributes to update across all rows. The key names of | ||
/// this object must be `fieldName`s from the root model of the | ||
/// query (**NOT** column names). | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. | ||
/// | ||
/// Return: number | ||
/// Return the number of rows that were updated. | ||
async updateAll(attributes, options) { | ||
@@ -171,2 +486,25 @@ let connection = this.getConnection(options && options.connection); | ||
/// Delete matching rows in the database. | ||
/// | ||
/// This method will delete all rows matching the query | ||
/// from the database (for only the root model). | ||
/// | ||
/// Example: | ||
/// await QueueEntry.where.status.EQ('completed').destroy(); | ||
/// | ||
/// Note: | ||
/// This operation will only ever update the "root model" | ||
/// table. The "root model" of a query is the first model | ||
/// used in the query. For example, `User` is the root model | ||
/// in the query `User.where`. | ||
/// | ||
/// Arguments: | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. | ||
/// | ||
/// Return: number | ||
/// Return the number of rows that were deleted. | ||
async destroy(options) { | ||
@@ -177,2 +515,19 @@ let connection = this.getConnection(options && options.connection); | ||
/// Calculate the "average" of all matching rows for | ||
/// a single column in the database. | ||
/// | ||
/// Example: | ||
/// let averageUserAge = await User.where.average('User:age'); | ||
/// | ||
/// Arguments: | ||
/// field: <see>Field</see> | string | <see>Literal</see> | ||
/// A field, fully qualified field name, or literal to average across. | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. | ||
/// | ||
/// Return: number | ||
/// The "average" of all matching rows for a single column. | ||
async average(field, options) { | ||
@@ -183,2 +538,34 @@ let connection = this.getConnection(options && options.connection); | ||
/// Count the number of matching rows. | ||
/// | ||
/// A `field` can be provided, which is used as the column to count on. | ||
/// If a `DISTINCT` operation is used in the query, then the `DISTINCT` | ||
/// field will be used for counting, regardless of what `field` | ||
/// you specify. If no `field` is provided, then all fields (`*`) | ||
/// is assumed. | ||
/// | ||
/// Example: | ||
/// let smithFamilySize = await User.where.lastName.EQ('Smith').count(); | ||
/// | ||
/// Note: | ||
/// Due to database limitations, it can sometimes be difficult or | ||
/// impossible in the underlying database to use an `ORDER`, `LIMIT`, or `OFFSET`, | ||
/// with this operation. Because of this, any `ORDER` on the query | ||
/// is simply ignored for now (help solving this problem would be | ||
/// greatly appreciated!). | ||
/// | ||
/// Arguments: | ||
/// field?: <see>Field</see> | string | literal | ||
/// A field, fully qualified field name, or literal to count on. If | ||
/// a `DISTINCT` operation is used in the query, then this argument | ||
/// will be ignored, and the field defined on the `DISTINCT` operation | ||
/// will be used instead. | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. | ||
/// | ||
/// Return: number | ||
/// The number of rows that match the query. | ||
async count(field, options) { | ||
@@ -189,2 +576,19 @@ let connection = this.getConnection(options && options.connection); | ||
/// Calculate the "minimum" of all matching rows for | ||
/// a single column in the database. | ||
/// | ||
/// Example: | ||
/// let youngestUserAge = await User.where.min('User:age'); | ||
/// | ||
/// Arguments: | ||
/// field: <see>Field</see> | string | <see>Literal</see> | ||
/// A field, fully qualified field name, or literal to calculate on. | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. | ||
/// | ||
/// Return: number | ||
/// The "minimum" value of all matching rows for a single column. | ||
async min(field, options) { | ||
@@ -195,2 +599,19 @@ let connection = this.getConnection(options && options.connection); | ||
/// Calculate the "maximum" of all matching rows for | ||
/// a single column in the database. | ||
/// | ||
/// Example: | ||
/// let oldestUserAge = await User.where.max('User:age'); | ||
/// | ||
/// Arguments: | ||
/// field: <see>Field</see> | string | <see>Literal</see> | ||
/// A field, fully qualified field name, or literal to calculate on. | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. | ||
/// | ||
/// Return: number | ||
/// The "maximum" value of all matching rows for a single column. | ||
async max(field, options) { | ||
@@ -201,2 +622,19 @@ let connection = this.getConnection(options && options.connection); | ||
/// Calculate the "sum" of all matching rows for | ||
/// a single column in the database. | ||
/// | ||
/// Example: | ||
/// let totalInventoryPrice = await Product.where.sum('Product:price'); | ||
/// | ||
/// Arguments: | ||
/// field: <see>Field</see> | string | <see>Literal</see> | ||
/// A field, fully qualified field name, or literal to average across. | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. | ||
/// | ||
/// Return: number | ||
/// The "sum" of all matching rows for a single column. | ||
async sum(field, options) { | ||
@@ -207,2 +645,33 @@ let connection = this.getConnection(options && options.connection); | ||
/// Pluck specific fields directly from the database. | ||
/// These plucked fields will not be converted into | ||
/// model instances, but instead will be returned raw | ||
/// to the caller. | ||
/// | ||
/// If only a single field is provided for `fields` then | ||
/// an array of column values is returned, i.e. `[ ... ]`. If | ||
/// however more than one field is specified for `fields`, | ||
/// then an array of arrays containing column values is | ||
/// returned, i.e. `[ [ ... ], [ ... ], ... ]`. | ||
/// | ||
/// Example: | ||
/// let allSmithFamilyHobbies = await User.where.lastName.EQ('Smith').pluck('User:hobby'); | ||
/// | ||
/// Arguments: | ||
/// fields: <see>Field</see> | string | Array<<see>Field</see>> | Array<string> | ||
/// The field(s) to pluck from the database. A single field may be specified. | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. The option `{ mapToObjects: true }` can be specified to | ||
/// map the results to objects, instead of returning arrays of raw values. | ||
/// | ||
/// Return: Array<any> | Array<Array<any>> | ||
/// If a single field is specified for `fields`, then an array of column values | ||
/// will be returned. If more than one field is specified, then an array of arrays | ||
/// of column values will be returned. It matters not if you specify the single field | ||
/// directly, or as an array, i.e. `.pluck('User:firstName')` is the same as `.pluck([ 'User:firstName' ])`. | ||
/// The engine only cares if "one field" is used... it doesn't care how it receives | ||
/// that single field. | ||
async pluck(fields, options) { | ||
@@ -213,2 +682,16 @@ let connection = this.getConnection(options && options.connection); | ||
/// Check if any rows match the query. | ||
/// | ||
/// Example: | ||
/// let smithFamilyHasMembers = await User.where.lastName.EQ('Smith').exists(); | ||
/// | ||
/// Arguments: | ||
/// options?: object | ||
/// Options to pass into the operation. These are generally connection | ||
/// specific. However, one option that is always available is the `connection` | ||
/// option, which can define the <see>Connection</see> to use for the | ||
/// operation. | ||
/// | ||
/// Return: boolean | ||
/// Return `true` if at least one row matches the query, or `false` otherwise. | ||
async exists(options) { | ||
@@ -219,2 +702,26 @@ let connection = this.getConnection(options && options.connection); | ||
/// Finalize the query before a database operation. | ||
/// | ||
/// This method simply proxies the request to <see>Connection.finalizeQuery</see>. | ||
/// If no connection can be found (for whatever reason), then `this` | ||
/// (the current query) is simply returned. | ||
/// | ||
/// Note: | ||
/// This system was designed for "row level permissions" systems, | ||
/// or simply to allow models themselves to control how queries are | ||
/// carried out against them. | ||
/// | ||
/// Arguments: | ||
/// type: string | ||
/// The CRUD operation type being performed. Can be one of `'create'`, `'read'`, | ||
/// `'update'`, or `'delete'`. The `'create'` operation is currently never used | ||
/// by Mythix ORM, since no query is ever involved in an `INSERT` operation; however | ||
/// it has been reserved for future use. | ||
/// options?: object | ||
/// The options that were provided by the user to the operation being executed. | ||
/// These are the operation options themselves, but can contain properties | ||
/// specifically targeted for custom down-stream code. | ||
/// | ||
/// Return: <see>QueryEngine</see> | ||
/// Return `this` query, possibly altered by down-stream code. | ||
async finalizeQuery(crudOperation, options) { | ||
@@ -230,3 +737,3 @@ let connection = this.getConnection(); | ||
if (prop === 'debug') { | ||
this.currentContext.rootContext.debug = true; | ||
this.getOperationContext().rootContext.debug = true; | ||
return this._fetchScope('model', 'queryEngine'); | ||
@@ -233,0 +740,0 @@ } |
@@ -0,1 +1,58 @@ | ||
///! Import `const { Types: { Helpers } } = require('mythix-orm');` | ||
///! | ||
///! Helpers are a set of utilities used to assist with | ||
///! `defaultValue` attributes on model fields. | ||
///! | ||
///! Default values for fields in Mythix ORM are generally used in | ||
///! two different places: As column values directly, or as `DEFAULT VALUE` | ||
///! for columns when defining tables in the database. When creating tables | ||
///! the `defaultValue` of a field will generally only ever be used if it | ||
///! has the `remote` flag set. Otherwise, Mythix ORM simply provides the | ||
///! `defaultValue` to the database directly during `INSERT` and `UPDATE` | ||
///! operations. | ||
///! | ||
///! ```javascript | ||
///! const { Model, Types } = require('mythix-orm'); | ||
///! | ||
///! class TestModel extends Model { | ||
///! static fields = { | ||
///! myCustomDate: { | ||
///! type: Types.STRING(32), | ||
///! // Inform Mythix ORM that our `defaultValue` should be | ||
///! // applied on every `UPDATE` operation... even if the | ||
///! // field already has a value. | ||
///! defaultValue: Types.Helpers.defaultValueFlags(() => { | ||
///! return (new Date()).toISOString(); | ||
///! }, { onUpdate: true }), | ||
///! allowNull: false, | ||
///! index: true, | ||
///! } | ||
///! }; | ||
///! } | ||
///! ``` | ||
///! | ||
///! Properties: | ||
///! FLAG_ON_INITIALIZE: number = 0x01 | ||
///! This flag informs Mythix ORM that the `defaultValue` should | ||
///! be fetched and used as soon as a model instance is first | ||
///! initialized. | ||
///! FLAG_ON_INSERT: number = 0x02 | ||
///! This flag informs Mythix ORM that the `defaultValue` should | ||
///! only be used on `INSERT` operations. | ||
///! FLAG_ON_UPDATE: number = 0x04 | ||
///! This flag informs Mythix ORM that the `defaultValue` should | ||
///! only be used on `UPDATE` operations. This is used for example | ||
///! by `Types.DATE.Defaults.NOW.UPDATE`. | ||
///! FLAG_ON_STORE: number = 0x06 | ||
///! This flag informs Mythix ORM that the `defaultValue` should | ||
///! only be used on `INSERT` and `UPDATE` operations. | ||
///! FLAG_LITERAL: number = 0x08 | ||
///! This flag informs Mythix ORM that the `defaultValue` is intended | ||
///! to be a literal value, and should not be escaped. | ||
///! FLAG_REMOTE: number = 0x10 | ||
///! This flag informs Mythix ORM that the `defaultValue` is provided | ||
///! by the database itself. For example the `Types.BIGINT.Defaults.AUTO_INCREMENT` | ||
///! type uses this flag. | ||
///! | ||
///! DocScope: Helpers | ||
'use strict'; | ||
@@ -10,2 +67,42 @@ | ||
/// Give a `defaultValue` method certain flags to | ||
/// assist Mythix ORM in working with a field's | ||
/// `defaultValue`. | ||
/// | ||
/// `defaultValue` flags change the way a `defaultValue` | ||
/// for a field behaves. For example, if the `FLAG_LITERAL` | ||
/// is set, then the database will not escape the default value. | ||
/// | ||
/// By default, all `defaultValue` attributes on fields (that | ||
/// are methods) have the flags `FLAG_ON_INITIALIZE`, which | ||
/// simply tells Mythix ORM to call the `defaultValue` method | ||
/// and assign the result to the field as soon as the model is | ||
/// instantiated. This is the default behavior of all `defaultValue` | ||
/// attributes... unless the defined flags change that default | ||
/// behavior. As soon as any flag is set on a `defaultValue` method, | ||
/// the `FLAG_ON_INITIALIZE` value will be cleared. | ||
/// | ||
/// This adds an attribute named `mythixFlags` to | ||
/// the provided method. This attribute is then | ||
/// used by Mythix ORM to know what to do with the | ||
/// `defaultValue` method provided on a field. | ||
/// | ||
/// Arguments: | ||
/// func: Function | ||
/// The `defaultValue` function to apply flags to. | ||
/// flags: object | ||
/// An object, specifying which flags to enable | ||
/// or disable. The allowed properties are listed in | ||
/// the table below. | ||
/// | Option | Type | Default Value | Description | | ||
/// | ------------- | ---- | ------------- | ----------- | | ||
/// | `onInitialize` | `boolean` | `true` | If `true`, then assign the `defaultValue` as soon as a model is instantiated. | | ||
/// | `onInsert` | `boolean` | `false` | If `true`, then only assign the `defaultValue` when an `INSERT` operation is being executed. | | ||
/// | `onUpdate` | `boolean` | `false` | If `true`, then only assign the `defaultValue` when an `UPDATE` operation is being executed. | | ||
/// | `onStore` | `boolean` | `false` | If `true`, then only assign the `defaultValue` when an `INSERT` or `UPDATE` operation is being executed. | | ||
/// | `literal` | `boolean` | `false` | If `true`, then inform Mythix ORM that the value is a literal, and not to escape it. | | ||
/// | `remote` | `boolean` | `false` | If `true`, then inform Mythix ORM that the value is provided by the database itself. | | ||
/// | ||
/// Return: Function | ||
/// The method provided, with a new `mythixFlags` assigned to it. | ||
function defaultValueFlags(func, _flagsObj) { | ||
@@ -40,2 +137,11 @@ let flags = FLAG_ON_INITIALIZE; | ||
/// Fetch the `mythixFlags` attribute on the provided | ||
/// function. If none is found, or no method is provided, | ||
/// then the value `0` will be returned instead. | ||
/// | ||
/// Return: number | ||
/// The `defaultValue` flags as a bitmask. If none is found, or no | ||
/// method is provided, then `0` will be returned instead. | ||
/// | ||
/// See: Helpers.defaultValueFlags | ||
function getDefaultValueFlags(func) { | ||
@@ -48,2 +154,20 @@ if (!func) | ||
/// Check if the provided `defaultValue` method has | ||
/// specific flags set. The flags are provided via | ||
/// their name. | ||
/// | ||
/// Arguments: | ||
/// func: Function | ||
/// The `defaultValue` function to check. | ||
/// checkFlags: Array<string> | ||
/// The flags to check. Acceptable values are: | ||
/// `[ 'onInitialize', 'onInsert', 'onUpdate', 'onStore', 'literal', 'remote' ]` | ||
/// | ||
/// Return: boolean | ||
/// Return `true` if all provided `checkFlags` are `true`. If any | ||
/// provided flag results in `false`, then the method will return | ||
/// `false`. In short, all provided flags must be `true`, otherwise | ||
/// `false` is returned. | ||
/// | ||
/// See: Helpers.defaultValueFlags | ||
function checkDefaultValueFlags(func, checkFlags) { | ||
@@ -50,0 +174,0 @@ if (!checkFlags || !checkFlags.length || !func) |
@@ -11,2 +11,16 @@ 'use strict'; | ||
/// Get the "display name" for this type instance. | ||
/// | ||
/// This will return a "human friendly" name for the type, | ||
/// generally used for logging or debugging. It will change | ||
/// based on the type it is called upon. For example, a | ||
/// <see>BooleanType</see> will return the string value `'BOOLEAN'`. | ||
/// | ||
/// Note: | ||
/// This method is also a static method on the type class. | ||
/// | ||
/// Return: string | ||
/// A string representing the type. This is not the same as | ||
/// the database equivalent type. Rather, it is simply used | ||
/// to represent the type for logging or debugging. | ||
getDisplayName() { | ||
@@ -22,2 +36,14 @@ return this.constructor.getDisplayName(); | ||
/// Use this method to check if a class | ||
/// is a Mythix ORM field type. It will return | ||
/// `true` if the provided value is a class | ||
/// that inherits from <see>Type</see>, or | ||
/// if the provided value has an attribute | ||
/// named `_isMythixFieldType` that is truthy. | ||
/// | ||
/// Return: boolean | ||
/// | ||
/// Arguments: | ||
/// value: Function | ||
/// Value to check. | ||
static isTypeClass(value) { | ||
@@ -36,2 +62,18 @@ if (!value) | ||
/// Check to see if the provided value is | ||
/// an *instance* of a Mythix ORM <see>Type</see>. | ||
/// Unlike <see>Type.static isTypeClass</see>, which | ||
/// checks if a *class* is a <see>Type</see>, this will check | ||
/// to see if an *instance* is an instance of a | ||
/// Mythix ORM <see>Type</see>. It will return | ||
/// `true` if the provided value is an `instanceof` | ||
/// <see>Type</see>, or if the value's `constructor` | ||
/// property has a truthy `_isMythixFieldType` property | ||
/// (`value.constructor._isMythixFieldType`) | ||
/// | ||
/// Return: boolean | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// Value to check | ||
static isType(value) { | ||
@@ -50,2 +92,16 @@ if (!value) | ||
/// Check to see if the provided type is the same as this type. | ||
/// | ||
/// This checks if the two types are the same type by comparing | ||
/// the type names. For example, a `Types.BooleanType.isSameType(new BooleanType())` | ||
/// would return `true`. | ||
/// | ||
/// Arguments: | ||
/// value: <see>Type</see> | ||
/// A type instance to check. This must be a type instance, not a type class. | ||
/// | ||
/// Return: boolean | ||
/// Return `true` if the provided `value` is the same type instance as this type class. | ||
/// Return `false` otherwise. Similarity is based on the name of the types. If the class | ||
/// names between the two match, then the types are considered the same type. | ||
static isSameType(value) { | ||
@@ -55,2 +111,20 @@ return (this.isType(value) && value.constructor && value.constructor.name === this.name); | ||
/// Instantiate this field type. | ||
/// | ||
/// This method will instantiate the type class it | ||
/// is called from. It won't instantiate the type if | ||
/// it is already instantiated, and it will call the | ||
/// type wrapper if one was provided (see <see>Type.static wrapConstructor</see>). | ||
/// It may not need to instantiate the type if the user already | ||
/// did so (for example `type: new Types.StringType()`, | ||
/// or `type: Types.STRING()`). It may need to instantiate | ||
/// however if the user didn't do so, for example `type: Types.StringType`, | ||
/// or `type: Types.STRING`. | ||
/// | ||
/// Note: | ||
/// If the type is already instantiated, then it will be | ||
/// cloned instead using <see>Type.clone</see>. | ||
/// | ||
/// Return: <see>Type</see> | ||
/// The instantiated type instance. | ||
static instantiateType(_type) { | ||
@@ -85,2 +159,24 @@ let type = _type; | ||
/// This method is used to create the "shortcut" types | ||
/// for Mythix ORM. For example, a "string" type can be | ||
/// created either via `new Types.StringType()`, or via `Types.STRING()`. | ||
/// Both are equivalent. `Types.STRING` was created by calling | ||
/// `Types.STRING = Type.wrapConstructor(Types.StringType)`. | ||
/// | ||
/// This method "wraps" a type constructor, providing a more | ||
/// convenient way to create and use field types. It works | ||
/// by wrapping the provided class's constructor in a callable | ||
/// method. If the method **is not** called (i.e. `type: Types.STRING`) | ||
/// then the type system still knows what to do, because the wrapping | ||
/// method will be called by Mythix ORM to instantiate the type | ||
/// when the model's fields are first fetched. | ||
/// | ||
/// Arguments: | ||
/// TypeKlass: class <see>Type</see> | ||
/// A `Type` class to wrap. | ||
/// | ||
/// Return: Function | ||
/// A callable function to instantiate the type class. If not called | ||
/// by the user, then Mythix ORM will call the method when the type | ||
/// is being created to fetch the type instance. | ||
static wrapConstructor(TypeKlass) { | ||
@@ -125,2 +221,11 @@ let TypeWrapper = function(...args) { | ||
/// Instantiate a new field Type. | ||
/// | ||
/// Arguments: | ||
/// ...args: Array<any> | ||
/// All field types "record" their constructor arguments, always. | ||
/// Mythix ORM requires that all field types, when calling `super` in | ||
/// their constructor, provide the base `Type` class all arguments | ||
/// provided to their constructor. | ||
/// This is so that the field type can later be cloned or serialized. | ||
constructor(...args) { | ||
@@ -149,2 +254,6 @@ Object.defineProperties(this, { | ||
/// Clone this field type. | ||
/// | ||
/// Return: Type | ||
/// This field type, cloned to a new instance. | ||
clone() { | ||
@@ -160,2 +269,14 @@ let Type = this.constructor; | ||
/// Check if this field type is a virtual field type. | ||
/// | ||
/// Note: | ||
/// This method is also a static method on the type class. | ||
/// | ||
/// Return: boolean | ||
/// Return `true` if this field type is a virtual field | ||
/// type. Virtual fields are fields that don't *directly* | ||
/// store a concrete value themselves. They often pull | ||
/// and combine values from other fields, or other models. | ||
/// Currently the only virtual fields in Mythix ORM are | ||
/// <see>ModelType</see> and <see>ModelsType</see>. | ||
isVirtual() { | ||
@@ -165,2 +286,16 @@ return this.constructor.isVirtual.call(this.constructor); | ||
/// Check if this field type is a relational field type. | ||
/// | ||
/// Note: | ||
/// This method is also a static method on the type class. | ||
/// | ||
/// Return: boolean | ||
/// Return `true` if this field type defines a relation, | ||
/// instead of a direct value. Field types such as | ||
/// <see>ModelType</see> and <see>ModelsType</see> are | ||
/// examples of relational field types. <see>ForeignKeyType</see> | ||
/// **is not** a relational type, but is instead considered | ||
/// a "concrete" type. Even though it does technically define | ||
/// a relationship, it is also a direct field, that actually | ||
/// has a concrete value, backed by database storage. | ||
isRelational() { | ||
@@ -170,2 +305,10 @@ return this.constructor.isRelational.call(this.constructor); | ||
/// Check if this field type is a foreign key type. | ||
/// | ||
/// Note: | ||
/// This method is also a static method on the type class. | ||
/// | ||
/// Return: boolean | ||
/// Return `true` if this field type defines a foreign key, | ||
/// or `false` otherwise. | ||
isForeignKey() { | ||
@@ -175,2 +318,21 @@ return this.constructor.isForeignKey.call(this.constructor); | ||
/// Used to decide if a field should be exposed on the model | ||
/// or not. | ||
/// | ||
/// Some fields, such as types like <see>ModelType</see> and | ||
/// <see>ModelsType</see> do not expose themselves on the model | ||
/// as model fields. Instead they only expose themselves via the | ||
/// relationship methods they inject onto the model instance. | ||
/// Most fields **do** expose themselves on the model, but any | ||
/// field that overrides this method and returns `false` will | ||
/// not be exposed on the model instance. | ||
/// | ||
/// Note: | ||
/// This method is also a static method on the type class. | ||
/// | ||
/// Return: boolean | ||
/// If `true` is returned, then the field owning this type | ||
/// will be added to the model instance, and accessible by | ||
/// the user. If `false` is returned, then the field will | ||
/// be "hidden", and not exposed to the user on model instances. | ||
exposeToModel() { | ||
@@ -180,2 +342,13 @@ return this.constructor.exposeToModel.call(this.constructor); | ||
/// Check if this field's value is driven by the database. | ||
/// | ||
/// For example, given an AUTOINCREMENTING id field, this | ||
/// would return `true`, because the value of the field is | ||
/// provided by the database itself. This can be true for | ||
/// nearly any field type, but is generally only true for | ||
/// auto-incrementing ids, and date/time types. | ||
/// | ||
/// Return: boolean | ||
/// Return `true` for any field type that has a value provided | ||
/// directly by the underlying database, or `false` otherwise. | ||
isRemote() { | ||
@@ -192,2 +365,17 @@ let field = this.getField(); | ||
/// Check if the value provided is a valid value for this field type. | ||
/// | ||
/// This is used to check if any value *would* be a valid value for | ||
/// this type. For example, if this type happens to be a <see>BooleanType</see>, | ||
/// then `isValidValue` would only return `true` if the provided `value` | ||
/// was either `true` or `false`. All field types have their own custom | ||
/// `isValidValue` implementation, to verify values against their type. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// Any value to check. | ||
/// | ||
/// Return: boolean | ||
/// Return `true` if the provided `value` is a valid value for the field | ||
/// type this was called upon, or `false` otherwise. | ||
// eslint-disable-next-line no-unused-vars | ||
@@ -198,2 +386,3 @@ isValidValue(value) { | ||
/// Get the field that owns this type. | ||
getField() { | ||
@@ -203,2 +392,7 @@ return this._field; | ||
/// Set the field that owns this type. | ||
/// | ||
/// Arguments: | ||
/// field: <see>Field</see> | ||
/// The field that owns this type. | ||
setField(field) { | ||
@@ -208,2 +402,3 @@ this._field = field; | ||
/// Get the model class that owns this field type. | ||
getModel() { | ||
@@ -226,2 +421,7 @@ let Model = this._Model; | ||
/// Set the model class that this type belongs to. | ||
/// | ||
/// Arguments: | ||
/// Model: class <see>Model</see> | ||
/// The model class that owns this field type. | ||
setModel(Model) { | ||
@@ -279,13 +479,107 @@ this._Model = Model; | ||
isDirty() { | ||
/// Check if the field value is dirty. | ||
/// | ||
/// This is only used by some field types, such as <see>SerializedType</see>. | ||
/// It will respond with `true` if the field is dirty, or `false` if it isn't. | ||
/// | ||
/// Interface: | ||
/// interface CheckDirtyContext { | ||
/// value: any; // The field's current value | ||
/// field: Field; // The field that is being checked | ||
/// fieldName: string; // The fieldName of the field that is being checked | ||
/// self: Model; // The model instance the field is from | ||
/// connection: ConnectionBase; // The current connection | ||
/// } | ||
/// | ||
/// Arguments: | ||
/// context: CheckDirtyContext | ||
/// The context provided to the call. | ||
/// | ||
/// Return: boolean | ||
/// Returns `true` if the field is dirty, or `false` otherwise. | ||
// eslint-disable-next-line no-unused-vars | ||
isDirty(context) { | ||
} | ||
/// This method is called any time a field's value is set on a model. | ||
/// | ||
/// It is only used by the <see>SerializedType</see> to update its internal | ||
/// "dirty" cache. It can be used by any field type however to detect when | ||
/// a model's field's value changes. | ||
/// | ||
/// Interface: | ||
/// interface SetFieldValueContext { | ||
/// value: any; | ||
/// field: Field; | ||
/// fieldName: string; | ||
/// self: Model; | ||
/// } | ||
/// | ||
/// Arguments: | ||
/// context: SetFieldValueContext | ||
/// The context provided to the call. | ||
/// | ||
/// Return: undefined | ||
/// This method returns nothing. | ||
onSetFieldValue() { | ||
} | ||
serialize(value) { | ||
/// Serialize the field's value. This is the opposite of | ||
/// <see>Type.deserialize</see>. | ||
/// | ||
/// This is used mostly by the <see>Model.toJSON</see> method | ||
/// to serialize values for JSON format. However, it is also used | ||
/// in some other cases, such as formatting for <see>DateType</see> | ||
/// and <see>DateTimeType</see>, and for checking dirtiness in the | ||
/// <see>SerializedType</see>. | ||
/// | ||
/// The job of this method is to serialize the field's value for any serialize operation, | ||
/// generally to JSON. If a `connection` is provided as the second argument, | ||
/// then it is assumed by the field type that it is being serialized for the | ||
/// database. In this case the connection generally assists with the serializing | ||
/// of the value. This is used for example with <see>DateType</see> | ||
/// and <see>DateTimeType</see> types, when they need to be serialized to or from | ||
/// a database operation. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The field's value to serialize. | ||
/// connection?: <see>Connection</see> | ||
/// An optional connection, which if provided will likely change | ||
/// how the value is serialized. | ||
/// | ||
/// Return: any | string | ||
/// Though the return value will generally be a string, it isn't required | ||
/// to be. | ||
/// | ||
/// See: Type.deserialize | ||
// eslint-disable-next-line no-unused-vars | ||
serialize(value, connection) { | ||
return value; | ||
} | ||
deserialize(value) { | ||
/// Deserialize the field's value. This is the opposite of | ||
/// <see>Type.serialize</see>. | ||
/// | ||
/// The job of this method to serialize the field's value for any serialize operation, | ||
/// generally to JSON. If a `connection` is provided as the second argument, | ||
/// then it is assumed by the field type that it is being serialized for the | ||
/// database. In this case the connection generally assists with the serializing | ||
/// of the value. This is used for example with <see>DateType</see> | ||
/// and <see>DateTimeType</see> types, when they need to be serialized to or from | ||
/// a database operation. | ||
/// | ||
/// Arguments: | ||
/// value: any | ||
/// The field's value to deserialize. | ||
/// connection?: <see>Connection</see> | ||
/// An optional connection, which if provided will likely change | ||
/// how the value is deserialized. | ||
/// | ||
/// Return: any | ||
/// The deserialized value for the field. | ||
/// | ||
/// See: Type.serialize | ||
// eslint-disable-next-line no-unused-vars | ||
deserialize(value, connection) { | ||
return value; | ||
@@ -292,0 +586,0 @@ } |
{ | ||
"name": "mythix-orm", | ||
"version": "1.11.6", | ||
"version": "1.11.7", | ||
"description": "ORM for Mythix framework", | ||
@@ -5,0 +5,0 @@ "main": "lib/index", |
109
README.md
# mythix-orm | ||
ORM for Mythix framework | ||
Mythix ORM aims to replace Sequelize and the few other terrible solutions that the poor destitute Node community has to work with. Mythix ORM has been designed to replace all current ORMs for Node, with a focus on what is lacking in the community, namely good engineering, good documentation, and ease of use. | ||
Mythix ORM aims to replace Sequelize and the few other terrible solutions that the poor destitute Node community has to work with. Mythix ORM is not yet quite ready for prime time however, so please check back soon! | ||
What to expect while you are waiting: | ||
1. Advanced, seamless, and powerful (yet simple) query engine that is easy to use, and works across database drivers, even for No-SQL databases. Here is a simple example to fetch users and their roles: `let users = await User.where.id.EQ(Role.where.userID).firstName.EQ('Mythix').lastName.EQ('ORM').Roles.name.EQ('superuser').PROJECT('User', 'Role').all();` | ||
Mythix ORMs feature set includes: | ||
1. An advanced, seamless, and powerful (yet simple) query engine that is easy to use, and works across database drivers, even for No-SQL databases. | ||
2. Powerful model classes and helpers that don't violate good design patterns, stay out of your face, and have no undocumented auto-magic built in. The model system is also designed to work seamlessly across different databases, including No-SQL databases. | ||
3. Simple, clean, and slim... Mythix ORM isn't intended to be a sledge hammer, nor a 'batteries included' framework. Instead, it is designed to be a useful tool, and was designed to be easily extended. It can be used for large enterprise applications, or it can be used as a simple slim layer to interact with different databases in a human-friendly way. | ||
4. Mythix ORM is modular by design. Instead of being a large bloated library that attempts to handle every database and every type of operation, it instead only provides exactly what you need. Mythix ORM is itself just a base connection, a query engine, and a model and type system. That is all. To interact with databases you can choose between any number of drivers for Mythix ORM, and can use community built plugins for adding features (or simply write your own!). | ||
5. Mythix ORM is designed from the ground-up to be extended/modified. Want to change the nature of the Query Engine? Just extend from it and away you go! Want to change the way models behave? No problem! Want to make your own connection? Go for it! Want to add your own custom data types for models? Super easy. Every part of Mythix ORM is designed to be swapped out in a non-global way so that its feature set can be extended and added onto. | ||
6. Has complete feature parity (and soon greater functionality) then all existing ORMs for Node. Model validation, hooks, model attributes and data types, model relations, support for multiple databases, an advanced query engine, transactions, transactions inside transactions, useful utility methods, an extensible type system, virtual types, an extensible query generator, support for older Node versions, support for multiple connections and multiplex connections (at the same time), and more! | ||
4. A modular design. Instead of being a large bloated library that attempts to handle every database and every type of operation, it instead only provides exactly what you need. Mythix ORM is itself just a base connection, a query engine, and a model and type system. That is all. To interact with databases you can choose between any number of drivers for Mythix ORM (coming soon!), and can use community-built plugins for adding features (or simply write your own!). | ||
5. A deliberate design to be extended and added onto. Easily modify the Query Engine to add more features, create your own database driver, modify how models behave, or add your own custom data types. The sky is the limit! | ||
6. Complete feature parity (and soon greater functionality) then all existing ORMs for Node. Model validation, hooks, model attributes and data types, model relations, support for multiple databases, an advanced query engine, transactions, transactions inside transactions, useful utility methods, an extensible type system, virtual types, an extensible query generator, support for older Node versions, support for multiple connections and multiplex connections (at the same time), and more! | ||
Stay tuned! Mythix ORM should be released and fully documented by Q1 of 2023! | ||
Mythix ORM is still in its very early stages, and is looking for users! It is stable, and currently has native support for SQLite and PostgreSQL. A mongo driver will be added next, and after that MySQL. If you want to help then drop me a line! All help is welcome. | ||
## Install | ||
```bash | ||
npm i --save mythix-orm mythix-orm-sqlite | ||
``` | ||
## Documentation | ||
Check out the [WIKI](https://github.com/th317erd/mythix-orm/wiki) for documentation. | ||
## Getting started | ||
Just start creating models! | ||
```javascript | ||
const { Model, Types } = require('mythix-orm'); | ||
const SQLiteConnection = require('mythix-orm-sqlite'); | ||
class User extends Model { | ||
static fields = { | ||
id: { | ||
type: Types.XID(), | ||
defaultValue: Types.XID.Defaults.XID, | ||
allowNull: false, | ||
primaryKey: true, | ||
}, | ||
email: { | ||
type: Types.STRING(128), | ||
allowNull: false, | ||
index: true, | ||
unique: true, | ||
}, | ||
firstName: { | ||
type: Types.STRING(64), | ||
allowNull: false, | ||
index: true, | ||
}, | ||
lastName: { | ||
type: Types.STRING(64), | ||
allowNull: false, | ||
index: true, | ||
}, | ||
}; | ||
} | ||
// Entry point | ||
(async function() { | ||
// Define a connection, and "bind" our models to it | ||
let connection = new SQLiteConnection({ | ||
models: [ | ||
User, | ||
], | ||
}); | ||
// Fire up our connection | ||
await connection.start(); | ||
// Create our tables needed for our User | ||
// model in SQLite | ||
await connection.createTables([ User ]); | ||
// Now we can store and load a user | ||
let user = new User({ | ||
email: 'test@example.com', | ||
firstName: 'Test', | ||
lastName: 'User', | ||
}); | ||
// Store user | ||
await user.save(); | ||
// Reload user by querying on the | ||
// user's email address | ||
user = await User.where.email.EQ('test@example.com').first(); | ||
// Serialize to JSON | ||
console.log('My user: ', JSON.stringify(user, undefined, 2)); | ||
// Shutdown our connection | ||
await connection.stop(); | ||
})(); | ||
``` | ||
## Notes | ||
1. The [WIKI](https://github.com/th317erd/mythix-orm/wiki) is still being worked on. Most of the documentation is complete, but there is still a lot more to write. Documentation is the main focus right now. There is a lot more to write. If you have any questions, feel free to drop a line, or open an issue! We will be happy to answer any questions. We aren't "done" until our documentation is pristine. | ||
2. Right now there are only database drivers for [SQLite](https://www.npmjs.com/package/mythix-orm-sqlite) and [PostgreSQL](https://www.npmjs.com/package/mythix-orm-postgresql). More are planned, with a Mongo driver likely to land next, followed by MySQL. Help wanted! | ||
3. Check out the [Mythix](https://www.npmjs.com/package/mythix) web-app framework. It is also still in active development, and the documentation is poor (to say the least), but it is up and coming, and will soon have fantastic documentation, and even though still in active development is fully functional. To get started try `npx mythix-cli create 'Test App'` | ||
## Goals | ||
The `Mythix` suite of technologies are being developed to give a rock-solid full-stack to build web-apps on Node. I got tired of the piecemeal garbage that currently exist in the Node ecosystem for building apps. My end goal is to have `Mythix` technologies take the Node community by storm, providing top-notch technologies for developers to create amazing things. Get involved with me, and let's change the world for the better! |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
814716
18848
107