This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+11
View File
@@ -0,0 +1,11 @@
import { QueryClientContract } from '@ioc:Adonis/Lucid/Database';
export declare class BaseSeeder {
client: QueryClientContract;
/**
* @deprecated
*/
static developmentOnly: boolean;
static environment: string[];
constructor(client: QueryClientContract);
run(): Promise<void>;
}
+23
View File
@@ -0,0 +1,23 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseSeeder = void 0;
class BaseSeeder {
constructor(client) {
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
}
async run() { }
}
exports.BaseSeeder = BaseSeeder;
+7
View File
@@ -0,0 +1,7 @@
/// <reference types="@adonisjs/application/build/adonis-typings" />
import { ReplContract } from '@ioc:Adonis/Addons/Repl';
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
/**
* Define REPL bindings
*/
export declare function defineReplBindings(app: ApplicationContract, Repl: ReplContract): void;
+59
View File
@@ -0,0 +1,59 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineReplBindings = void 0;
const helpers_1 = require("@poppinss/utils/build/helpers");
/**
* Helper to define REPL state
*/
function setupReplState(repl, key, value) {
repl.server.context[key] = value;
repl.notify(`Loaded ${key} module. You can access it using the "${repl.colors.underline(key)}" variable`);
}
/**
* Define REPL bindings
*/
function defineReplBindings(app, Repl) {
/**
* Load all models to the models property
*/
Repl.addMethod('loadModels', (repl) => {
const modelsPath = app.resolveNamespaceDirectory('models') || 'app/Models';
console.log(repl.colors.dim(`recursively reading models from "${modelsPath}"`));
const modelsAbsPath = app.makePath(modelsPath);
setupReplState(repl, 'models', (0, helpers_1.requireAll)(modelsAbsPath));
}, {
description: 'Recursively load Lucid models to the "models" property',
});
/**
* Load database provider to the Db provider
*/
Repl.addMethod('loadDb', (repl) => {
setupReplState(repl, 'Db', app.container.use('Adonis/Lucid/Database'));
}, {
description: 'Load database provider to the "Db" property',
});
/**
* Load all factories to the factories property
*/
Repl.addMethod('loadFactories', (repl) => {
const factoriesPath = app.resolveNamespaceDirectory('factories') || 'database/factories';
console.log(repl.colors.dim(`recursively reading factories from "${factoriesPath}"`));
const factoriesAbsPath = app.makePath(factoriesPath);
const loadedFactories = (0, helpers_1.requireAll)(factoriesAbsPath);
if (!loadedFactories) {
return;
}
setupReplState(repl, 'factories', Object.values(loadedFactories).reduce((acc, items) => ({ ...acc, ...items }), {}));
}, {
description: 'Recursively load factories to the "factories" property',
});
}
exports.defineReplBindings = defineReplBindings;
+6
View File
@@ -0,0 +1,6 @@
import type { TestUtilsContract } from '@ioc:Adonis/Core/TestUtils';
import type Ace from '@ioc:Adonis/Core/Ace';
/**
* Define database testing utilities
*/
export declare function defineTestUtils(testUtils: TestUtilsContract, ace: typeof Ace): void;
+33
View File
@@ -0,0 +1,33 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineTestUtils = void 0;
const Seeder_1 = require("../TestUtils/Seeder");
const Migration_1 = require("../TestUtils/Migration");
const Truncator_1 = require("../TestUtils/Truncator");
/**
* Define database testing utilities
*/
function defineTestUtils(testUtils, ace) {
testUtils.constructor.macro('db', (connectionName) => {
return {
migrate() {
return new Migration_1.TestsMigrator(ace, connectionName).run();
},
seed() {
return new Seeder_1.TestsSeeder(ace, connectionName).run();
},
truncate() {
return new Truncator_1.TestsTruncator(ace, connectionName).run();
},
};
});
}
exports.defineTestUtils = defineTestUtils;
+9
View File
@@ -0,0 +1,9 @@
/// <reference types="@adonisjs/validator" />
/// <reference types="@adonisjs/logger/build/adonis-typings/logger" />
import { LoggerContract } from '@ioc:Adonis/Core/Logger';
import { DatabaseContract } from '@ioc:Adonis/Lucid/Database';
import { validator as validatorStatic } from '@ioc:Adonis/Core/Validator';
/**
* Extends the validator by adding `unique` and `exists`
*/
export declare function extendValidator(validator: typeof validatorStatic, database: DatabaseContract, logger: LoggerContract): void;
+205
View File
@@ -0,0 +1,205 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.extendValidator = void 0;
const luxon_1 = require("luxon");
const utils_1 = require("@poppinss/utils");
/**
* Checks for database rows for `exists` and `unique` rule.
*/
class DbRowCheck {
constructor(ruleName, database, helpers) {
Object.defineProperty(this, "ruleName", {
enumerable: true,
configurable: true,
writable: true,
value: ruleName
});
Object.defineProperty(this, "database", {
enumerable: true,
configurable: true,
writable: true,
value: database
});
Object.defineProperty(this, "helpers", {
enumerable: true,
configurable: true,
writable: true,
value: helpers
});
}
/**
* Applies user defined where constraints on the query builder
*/
applyWhere(query, constraints, refs) {
if (!constraints.length) {
return;
}
constraints.forEach(({ key, operator, value, ref }) => {
const val = ref ? refs[ref].value : value;
if (operator === 'in') {
query.whereIn(key, val);
}
else {
query.where(key, val);
}
});
}
/**
* Applies user defined where not constraints on the query builder
*/
applyWhereNot(query, constraints, refs) {
if (!constraints.length) {
return;
}
constraints.forEach(({ key, operator, value, ref }) => {
const val = ref ? refs[ref].value : value;
if (operator === 'in') {
query.whereNotIn(key, val);
}
else {
query.whereNot(key, val);
}
});
}
/**
* Normalizes constraints
*/
normalizeConstraints(constraints) {
const normalized = [];
if (!constraints) {
return normalized;
}
/**
* Normalize object into an array of objects
*/
return Object.keys(constraints).reduce((result, key) => {
const value = constraints[key];
if (this.helpers.isRef(value)) {
result.push({ key, ref: value.key, operator: Array.isArray(value.value) ? 'in' : 'eq' });
}
else {
result.push({ key, value, operator: Array.isArray(value) ? 'in' : 'eq' });
}
return result;
}, normalized);
}
/**
* Compile validation options
*/
compile(options) {
/**
* Ensure options are defined with table and column name
*/
if (!options || !options.table || !options.column) {
throw new utils_1.Exception(`"${this.ruleName}" rule expects a "table" and a "column" name`);
}
/**
* Emit warning
*/
if (options.constraints) {
process.emitWarning('DeprecationWarning', '"options.constraints" have been depreciated. Use "options.where" instead.');
}
return {
table: options.table,
column: options.column,
caseInsensitive: !!options.caseInsensitive,
connection: options.connection,
dateFormat: options.dateFormat,
where: this.normalizeConstraints(options.where || options.constraints),
whereNot: this.normalizeConstraints(options.whereNot),
};
}
/**
* Validate value
*/
async validate(value, { table, column, where, whereNot, connection, caseInsensitive, dateFormat }, { pointer, errorReporter, arrayExpressionPointer, refs }) {
const client = this.database.connection(connection);
const query = client.from(table).select(1);
/**
* Convert datetime to a string
*/
if (luxon_1.DateTime.isDateTime(value)) {
const format = dateFormat || client.dialect.dateTimeFormat;
value = value.toFormat(format);
}
/**
* https://www.sqlite.org/lang_corefunc.html#lower
* https://docs.aws.amazon.com/redshift/latest/dg/r_LOWER.html
* https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_lower
* https://www.postgresql.org/docs/9.1/functions-string.html
* https://docs.microsoft.com/en-us/sql/t-sql/functions/lower-transact-sql?view=sql-server-ver15
* https://coderwall.com/p/6yhsuq/improve-case-insensitive-queries-in-postgres-using-smarter-indexes
*/
if (caseInsensitive) {
query.whereRaw(`lower(${column}) = ?`, [this.database.raw(`lower(?)`, [value])]);
}
else {
query.where(column, value);
}
this.applyWhere(query, where, refs);
this.applyWhereNot(query, whereNot, refs);
const row = await query.first();
if (this.ruleName === 'exists') {
if (!row) {
errorReporter.report(pointer, this.ruleName, `${this.ruleName} validation failure`, arrayExpressionPointer);
}
return;
}
if (this.ruleName === 'unique') {
if (row) {
errorReporter.report(pointer, this.ruleName, `${this.ruleName} validation failure`, arrayExpressionPointer);
}
return;
}
}
}
/**
* Extends the validator by adding `unique` and `exists`
*/
function extendValidator(validator, database, logger) {
/**
* Exists rule to ensure the value exists in the database
*/
const existsChecker = new DbRowCheck('exists', database, validator.helpers);
validator.rule('exists', async (value, compiledOptions, options) => {
try {
await existsChecker.validate(value, compiledOptions, options);
}
catch (error) {
logger.fatal({ err: error }, '"exists" validation rule failed');
options.errorReporter.report(options.pointer, 'exists', 'exists validation failure', options.arrayExpressionPointer);
}
}, (options) => {
return {
compiledOptions: existsChecker.compile(options[0]),
async: true,
};
});
/**
* Unique rule to check if value is unique or not
*/
const uniqueChecker = new DbRowCheck('unique', database, validator.helpers);
validator.rule('unique', async (value, compiledOptions, options) => {
try {
await uniqueChecker.validate(value, compiledOptions, options);
}
catch (error) {
logger.fatal({ err: error }, '"unique" validation rule failed');
options.errorReporter.report(options.pointer, 'unique', 'unique validation failure', options.arrayExpressionPointer);
}
}, (options) => {
return {
compiledOptions: uniqueChecker.compile(options[0]),
async: true,
};
});
}
exports.extendValidator = extendValidator;
+15
View File
@@ -0,0 +1,15 @@
/// <reference types="@adonisjs/logger/build/adonis-typings/logger" />
import { LoggerContract } from '@ioc:Adonis/Core/Logger';
/**
* Custom knex logger that uses adonisjs logger under the
* hood.
*/
export declare class Logger {
name: string;
adonisLogger: LoggerContract;
warn: any;
error: any;
deprecate: any;
debug: any;
constructor(name: string, adonisLogger: LoggerContract);
}
+65
View File
@@ -0,0 +1,65 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Logger = void 0;
/**
* Custom knex logger that uses adonisjs logger under the
* hood.
*/
class Logger {
constructor(name, adonisLogger) {
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: name
});
Object.defineProperty(this, "adonisLogger", {
enumerable: true,
configurable: true,
writable: true,
value: adonisLogger
});
Object.defineProperty(this, "warn", {
enumerable: true,
configurable: true,
writable: true,
value: function (message) {
this.adonisLogger.warn(message);
}.bind(this)
});
Object.defineProperty(this, "error", {
enumerable: true,
configurable: true,
writable: true,
value: function (message) {
this.adonisLogger.error(message);
}.bind(this)
});
Object.defineProperty(this, "deprecate", {
enumerable: true,
configurable: true,
writable: true,
value: function (message) {
this.adonisLogger.info(message);
}.bind(this)
});
Object.defineProperty(this, "debug", {
enumerable: true,
configurable: true,
writable: true,
value: function (message) {
this.warn('"debug" property inside config is depreciated. We recommend using "db:query" event for enrich logging');
this.adonisLogger.debug(message);
}.bind(this)
});
}
}
exports.Logger = Logger;
+86
View File
@@ -0,0 +1,86 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="@adonisjs/logger/build/adonis-typings/logger" />
/// <reference types="@adonisjs/events/build/adonis-typings" />
import { EmitterContract } from '@ioc:Adonis/Core/Event';
import { LoggerContract } from '@ioc:Adonis/Core/Logger';
import { HealthReportEntry } from '@ioc:Adonis/Core/HealthCheck';
import { ReportNode, ConnectionNode, ConnectionConfig, ConnectionManagerContract } from '@ioc:Adonis/Lucid/Database';
/**
* Connection manager job is to manage multiple named connections. You can add any number
* or connections by registering their config only once and then make use of `connect`
* and `close` methods to create and destroy db connections.
*/
export declare class ConnectionManager implements ConnectionManagerContract {
private logger;
private emitter;
/**
* List of managed connections
*/
connections: ConnectionManagerContract['connections'];
/**
* Connections for which the config was patched. They must get removed
* overtime, unless application is behaving unstable.
*/
private orphanConnections;
constructor(logger: LoggerContract, emitter: EmitterContract);
/**
* Handles disconnection of a connection
*/
private handleDisconnect;
/**
* Handles event when a new connection is added
*/
private handleConnect;
/**
* Monitors a given connection by listening for lifecycle events
*/
private monitorConnection;
/**
* Add a named connection with it's configuration. Make sure to call `connect`
* before using the connection to make database queries.
*/
add(connectionName: string, config: ConnectionConfig): void;
/**
* Connect to the database using config for a given named connection
*/
connect(connectionName: string): void;
/**
* Patching the config
*/
patch(connectionName: string, config: ConnectionConfig): void;
/**
* Returns the connection node for a given named connection
*/
get(connectionName: string): ConnectionNode | undefined;
/**
* Returns a boolean telling if we have connection details for
* a given named connection. This method doesn't tell if
* connection is connected or not.
*/
has(connectionName: string): boolean;
/**
* Returns a boolean telling if connection has been established
* with the database or not
*/
isConnected(connectionName: string): boolean;
/**
* Closes a given connection and can optionally release it from the
* tracking list
*/
close(connectionName: string, release?: boolean): Promise<void>;
/**
* Close all tracked connections
*/
closeAll(release?: boolean): Promise<void>;
/**
* Release a connection. This will disconnect the connection
* and will delete it from internal list
*/
release(connectionName: string): Promise<void>;
/**
* Returns the report for all the connections marked for healthChecks.
*/
report(): Promise<HealthReportEntry & {
meta: ReportNode[];
}>;
}
+254
View File
@@ -0,0 +1,254 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConnectionManager = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const utils_1 = require("@poppinss/utils");
const index_1 = require("./index");
/**
* Connection manager job is to manage multiple named connections. You can add any number
* or connections by registering their config only once and then make use of `connect`
* and `close` methods to create and destroy db connections.
*/
class ConnectionManager {
constructor(logger, emitter) {
Object.defineProperty(this, "logger", {
enumerable: true,
configurable: true,
writable: true,
value: logger
});
Object.defineProperty(this, "emitter", {
enumerable: true,
configurable: true,
writable: true,
value: emitter
});
/**
* List of managed connections
*/
Object.defineProperty(this, "connections", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
/**
* Connections for which the config was patched. They must get removed
* overtime, unless application is behaving unstable.
*/
Object.defineProperty(this, "orphanConnections", {
enumerable: true,
configurable: true,
writable: true,
value: new Set()
});
}
/**
* Handles disconnection of a connection
*/
handleDisconnect(connection) {
/**
* We received the close event on the orphan connection and not the connection
* that is in use
*/
if (this.orphanConnections.has(connection)) {
this.orphanConnections.delete(connection);
this.emitter.emit('db:connection:disconnect', connection);
this.logger.trace({ connection: connection.name }, 'disconnecting connection inside manager');
return;
}
const internalConnection = this.get(connection.name);
/**
* This will be false, when connection was released at the
* time of closing
*/
if (!internalConnection) {
return;
}
this.emitter.emit('db:connection:disconnect', connection);
this.logger.trace({ connection: connection.name }, 'disconnecting connection inside manager');
delete internalConnection.connection;
internalConnection.state = 'closed';
}
/**
* Handles event when a new connection is added
*/
handleConnect(connection) {
const internalConnection = this.get(connection.name);
if (!internalConnection) {
return;
}
this.emitter.emit('db:connection:connect', connection);
internalConnection.state = 'open';
}
/**
* Monitors a given connection by listening for lifecycle events
*/
monitorConnection(connection) {
connection.on('disconnect', ($connection) => this.handleDisconnect($connection));
connection.on('connect', ($connection) => this.handleConnect($connection));
connection.on('error', (error, $connection) => {
this.emitter.emit('db:connection:error', [error, $connection]);
});
}
/**
* Add a named connection with it's configuration. Make sure to call `connect`
* before using the connection to make database queries.
*/
add(connectionName, config) {
/**
* Noop when connection already exists. If one wants to change the config, they
* must release the old connection and add a new one
*/
if (this.has(connectionName)) {
return;
}
this.logger.trace({ connection: connectionName }, 'adding new connection to the manager');
this.connections.set(connectionName, {
name: connectionName,
config: config,
state: 'registered',
});
}
/**
* Connect to the database using config for a given named connection
*/
connect(connectionName) {
const connection = this.connections.get(connectionName);
if (!connection) {
throw new utils_1.Exception(`Cannot connect to unregistered connection ${connectionName}`, 500, 'E_UNMANAGED_DB_CONNECTION');
}
/**
* Ignore when the there is already a connection.
*/
if (this.isConnected(connection.name)) {
return;
}
/**
* Create a new connection and monitor it's state
*/
connection.connection = new index_1.Connection(connection.name, connection.config, this.logger);
this.monitorConnection(connection.connection);
connection.connection.connect();
}
/**
* Patching the config
*/
patch(connectionName, config) {
const connection = this.get(connectionName);
/**
* If connection is missing, then simply add it
*/
if (!connection) {
return this.add(connectionName, config);
}
/**
* Move the current connection to the orphan connections. We need
* to keep a seperate track of old connections to make sure
* they cleanup after some time
*/
if (connection.connection) {
this.orphanConnections.add(connection.connection);
connection.connection.disconnect();
}
/**
* Updating config and state. Next call to connect will use the
* new config
*/
connection.state = 'migrating';
connection.config = config;
/**
* Removing the connection right away, so that the next call to `connect`
* creates a new one with new config
*/
delete connection.connection;
}
/**
* Returns the connection node for a given named connection
*/
get(connectionName) {
return this.connections.get(connectionName);
}
/**
* Returns a boolean telling if we have connection details for
* a given named connection. This method doesn't tell if
* connection is connected or not.
*/
has(connectionName) {
return this.connections.has(connectionName);
}
/**
* Returns a boolean telling if connection has been established
* with the database or not
*/
isConnected(connectionName) {
if (!this.has(connectionName)) {
return false;
}
const connection = this.get(connectionName);
return !!connection.connection && connection.state === 'open';
}
/**
* Closes a given connection and can optionally release it from the
* tracking list
*/
async close(connectionName, release = false) {
if (this.isConnected(connectionName)) {
const connection = this.get(connectionName);
await connection.connection.disconnect();
connection.state = 'closing';
}
if (release) {
await this.release(connectionName);
}
}
/**
* Close all tracked connections
*/
async closeAll(release = false) {
await Promise.all(Array.from(this.connections.keys()).map((name) => this.close(name, release)));
}
/**
* Release a connection. This will disconnect the connection
* and will delete it from internal list
*/
async release(connectionName) {
if (this.isConnected(connectionName)) {
await this.close(connectionName, true);
}
else {
this.connections.delete(connectionName);
}
}
/**
* Returns the report for all the connections marked for healthChecks.
*/
async report() {
const reports = await Promise.all(Array.from(this.connections.keys())
.filter((one) => this.get(one).config.healthCheck)
.map((one) => {
this.connect(one);
return this.get(one).connection.getReport();
}));
const healthy = !reports.find((report) => !!report.error);
return {
displayName: 'Database',
health: {
healthy,
message: healthy
? 'All connections are healthy'
: 'One or more connections are not healthy',
},
meta: reports,
};
}
}
exports.ConnectionManager = ConnectionManager;
+127
View File
@@ -0,0 +1,127 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="@adonisjs/logger/build/adonis-typings/logger" />
/// <reference types="node" />
import { Pool } from 'tarn';
import { Knex } from 'knex';
import { EventEmitter } from 'events';
import { LoggerContract } from '@ioc:Adonis/Core/Logger';
import { ConnectionConfig, ConnectionContract, ReportNode } from '@ioc:Adonis/Lucid/Database';
/**
* Connection class manages a given database connection. Internally it uses
* knex to build the database connection with appropriate database
* driver.
*/
export declare class Connection extends EventEmitter implements ConnectionContract {
readonly name: string;
config: ConnectionConfig;
private logger;
/**
* Reference to knex. The instance is created once the `open`
* method is invoked
*/
client?: Knex;
/**
* Read client when read/write replicas are defined in the config, otherwise
* it is a reference to the `client`.
*/
readClient?: Knex;
/**
* Connection dialect name
*/
dialectName: ConnectionContract['dialectName'];
/**
* A boolean to know if connection operates on read/write
* replicas
*/
hasReadWriteReplicas: boolean;
/**
* Config for one or more read replicas. Only exists, when replicas are
* defined
*/
private readReplicas;
/**
* The round robin counter for reading config
*/
private roundRobinCounter;
constructor(name: string, config: ConnectionConfig, logger: LoggerContract);
/**
* Validates the config to ensure that read/write replicas are defined
* properly.
*/
private validateConfig;
/**
* Cleanup references
*/
private cleanup;
/**
* Does cleanup by removing knex reference and removing all listeners.
* For the same of simplicity, we get rid of both read and write
* clients, when anyone of them disconnects.
*/
private monitorPoolResources;
/**
* Returns normalized config object for write replica to be
* used by knex
*/
private getWriteConfig;
/**
* Returns the config for read replicas.
*/
private getReadConfig;
/**
* Resolves connection config for the writer connection
*/
private writeConfigResolver;
/**
* Resolves connection config for the reader connection
*/
private readConfigResolver;
/**
* Creates the write connection.
*/
private setupWriteConnection;
/**
* Creates the read connection. If there aren't any replicas in use, then
* it will use the write client instead.
*/
private setupReadConnection;
/**
* Checks all the read hosts by running a query on them. Stops
* after first error.
*/
private checkReadHosts;
/**
* Checks for the write host
*/
private checkWriteHost;
/**
* Returns the pool instance for the given connection
*/
get pool(): null | Pool<any>;
/**
* Returns the pool instance for the read connection. When replicas are
* not in use, then read/write pools are same.
*/
get readPool(): null | Pool<any>;
/**
* Returns a boolean indicating if the connection is ready for making
* database queries. If not, one must call `connect`.
*/
get ready(): boolean;
/**
* Opens the connection by creating knex instance
*/
connect(): void;
/**
* Closes DB connection by destroying knex instance. The `connection`
* object must be free for garbage collection.
*
* In case of error this method will emit `close:error` event followed
* by the `close` event.
*/
disconnect(): Promise<void>;
/**
* Returns the healthcheck report for the connection
*/
getReport(): Promise<ReportNode>;
}
+405
View File
@@ -0,0 +1,405 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Connection = void 0;
const knex_1 = __importDefault(require("knex"));
const events_1 = require("events");
const utils_1 = require("@poppinss/utils");
const knex_dynamic_connection_1 = require("knex-dynamic-connection");
const helpers_1 = require("knex/lib/util/helpers");
const Logger_1 = require("./Logger");
/**
* Connection class manages a given database connection. Internally it uses
* knex to build the database connection with appropriate database
* driver.
*/
class Connection extends events_1.EventEmitter {
constructor(name, config, logger) {
super();
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: name
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: config
});
Object.defineProperty(this, "logger", {
enumerable: true,
configurable: true,
writable: true,
value: logger
});
/**
* Reference to knex. The instance is created once the `open`
* method is invoked
*/
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Read client when read/write replicas are defined in the config, otherwise
* it is a reference to the `client`.
*/
Object.defineProperty(this, "readClient", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Connection dialect name
*/
Object.defineProperty(this, "dialectName", {
enumerable: true,
configurable: true,
writable: true,
value: (0, helpers_1.resolveClientNameWithAliases)(this.config.client)
});
/**
* A boolean to know if connection operates on read/write
* replicas
*/
Object.defineProperty(this, "hasReadWriteReplicas", {
enumerable: true,
configurable: true,
writable: true,
value: !!(this.config.replicas &&
this.config.replicas.read &&
this.config.replicas.write)
});
/**
* Config for one or more read replicas. Only exists, when replicas are
* defined
*/
Object.defineProperty(this, "readReplicas", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
/**
* The round robin counter for reading config
*/
Object.defineProperty(this, "roundRobinCounter", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
this.validateConfig();
}
/**
* Validates the config to ensure that read/write replicas are defined
* properly.
*/
validateConfig() {
if (this.config.replicas) {
if (!this.config.replicas.read || !this.config.replicas.write) {
throw new utils_1.Exception('Make sure to define read/write replicas or use connection property', 500, 'E_INCOMPLETE_REPLICAS_CONFIG');
}
if (!this.config.replicas.read.connection || !this.config.replicas.read.connection) {
throw new utils_1.Exception('Make sure to define connection property inside read/write replicas', 500, 'E_INVALID_REPLICAS_CONFIG');
}
}
}
/**
* Cleanup references
*/
cleanup() {
this.client = undefined;
this.readClient = undefined;
this.readReplicas = [];
this.roundRobinCounter = 0;
}
/**
* Does cleanup by removing knex reference and removing all listeners.
* For the same of simplicity, we get rid of both read and write
* clients, when anyone of them disconnects.
*/
monitorPoolResources() {
/**
* Pool has destroyed and hence we must cleanup resources
* as well.
*/
this.pool.on('poolDestroySuccess', () => {
this.logger.trace({ connection: this.name }, 'pool destroyed, cleaning up resource');
this.cleanup();
this.emit('disconnect', this);
this.removeAllListeners();
});
if (this.readPool !== this.pool) {
this.readPool.on('poolDestroySuccess', () => {
this.logger.trace({ connection: this.name }, 'pool destroyed, cleaning up resource');
this.cleanup();
this.emit('disconnect', this);
this.removeAllListeners();
});
}
}
/**
* Returns normalized config object for write replica to be
* used by knex
*/
getWriteConfig() {
if (!this.config.replicas) {
return this.config;
}
const { replicas, ...config } = this.config;
/**
* Give preference to the replica write connection when and merge values from
* the main connection object when defined.
*/
if (typeof replicas.write.connection === 'string' || typeof config.connection === 'string') {
config.connection = replicas.write.connection;
}
else {
config.connection = Object.assign({}, config.connection, replicas.write.connection);
}
/**
* Add pool to the config when pool config defined on main connection
* or the write replica
*/
if (config.pool || replicas.write.pool) {
config.pool = Object.assign({}, config.pool, replicas.write.pool);
}
return config;
}
/**
* Returns the config for read replicas.
*/
getReadConfig() {
if (!this.config.replicas) {
return this.config;
}
const { replicas, ...config } = this.config;
/**
* Reading replicas and storing them as a reference, so that we
* can pick a config from replicas as round robin.
*/
this.readReplicas = replicas.read.connection.map((one) => {
if (typeof one === 'string' || typeof config.connection === 'string') {
return one;
}
else {
return Object.assign({}, config.connection, one);
}
});
/**
* Add database property on the main connection, since knexjs needs it
* internally
*/
config.connection = {
database: this.readReplicas[0].database,
};
/**
* Add pool to the config when pool config defined on main connection
* or the read replica
*/
if (config.pool || replicas.read.pool) {
config.pool = Object.assign({}, config.pool, replicas.read.pool);
}
return config;
}
/**
* Resolves connection config for the writer connection
*/
writeConfigResolver(originalConfig) {
return originalConfig.connection;
}
/**
* Resolves connection config for the reader connection
*/
readConfigResolver(originalConfig) {
if (!this.readReplicas.length) {
return originalConfig.connection;
}
const index = this.roundRobinCounter++ % this.readReplicas.length;
this.logger.trace({ connection: this.name }, `round robin using host at ${index} index`);
return this.readReplicas[index];
}
/**
* Creates the write connection.
*/
setupWriteConnection() {
this.client = (0, knex_1.default)(Object.assign({ log: new Logger_1.Logger(this.name, this.logger) }, this.getWriteConfig(), {
debug: false,
}));
(0, knex_dynamic_connection_1.patchKnex)(this.client, this.writeConfigResolver.bind(this));
}
/**
* Creates the read connection. If there aren't any replicas in use, then
* it will use the write client instead.
*/
setupReadConnection() {
if (!this.hasReadWriteReplicas) {
this.readClient = this.client;
return;
}
this.logger.trace({ connection: this.name }, 'setting up read/write replicas');
this.readClient = (0, knex_1.default)(Object.assign({ log: new Logger_1.Logger(this.name, this.logger) }, this.getReadConfig(), {
debug: false,
}));
(0, knex_dynamic_connection_1.patchKnex)(this.readClient, this.readConfigResolver.bind(this));
}
/**
* Checks all the read hosts by running a query on them. Stops
* after first error.
*/
async checkReadHosts() {
const configCopy = Object.assign({ log: new Logger_1.Logger(this.name, this.logger) }, this.config, {
debug: false,
});
let error = null;
// eslint-disable-next-line @typescript-eslint/naming-convention
for (let _ of this.readReplicas) {
configCopy.connection = this.readConfigResolver(this.config);
this.logger.trace({ connection: this.name }, 'spawing health check read connection');
const client = (0, knex_1.default)(configCopy);
try {
if (this.dialectName === 'oracledb') {
await client.raw('SELECT 1 + 1 AS result FROM dual');
}
else {
await client.raw('SELECT 1 + 1 AS result');
}
}
catch (err) {
error = err;
}
/**
* Cleanup client connection
*/
await client.destroy();
this.logger.trace({ connection: this.name }, 'destroying health check read connection');
/**
* Return early when there is an error
*/
if (error) {
break;
}
}
return error;
}
/**
* Checks for the write host
*/
async checkWriteHost() {
try {
if (this.dialectName === 'oracledb') {
await this.client.raw('SELECT 1 + 1 AS result FROM dual');
}
else {
await this.client.raw('SELECT 1 + 1 AS result');
}
}
catch (error) {
return error;
}
}
/**
* Returns the pool instance for the given connection
*/
get pool() {
return this.client ? this.client.client.pool : null;
}
/**
* Returns the pool instance for the read connection. When replicas are
* not in use, then read/write pools are same.
*/
get readPool() {
return this.readClient ? this.readClient.client.pool : null;
}
/**
* Returns a boolean indicating if the connection is ready for making
* database queries. If not, one must call `connect`.
*/
get ready() {
return !!(this.client || this.readClient);
}
/**
* Opens the connection by creating knex instance
*/
connect() {
try {
this.setupWriteConnection();
this.setupReadConnection();
this.monitorPoolResources();
this.emit('connect', this);
}
catch (error) {
this.emit('error', error, this);
throw error;
}
}
/**
* Closes DB connection by destroying knex instance. The `connection`
* object must be free for garbage collection.
*
* In case of error this method will emit `close:error` event followed
* by the `close` event.
*/
async disconnect() {
this.logger.trace({ connection: this.name }, 'destroying connection');
/**
* Disconnect write client
*/
if (this.client) {
try {
await this.client.destroy();
}
catch (error) {
this.emit('disconnect:error', error, this);
}
}
/**
* Disconnect read client when it exists and both clients
* aren't same
*/
if (this.readClient && this.readClient !== this.client) {
try {
await this.readClient.destroy();
}
catch (error) {
this.emit('disconnect:error', error, this);
}
}
}
/**
* Returns the healthcheck report for the connection
*/
async getReport() {
const error = await this.checkWriteHost();
let readError;
if (!error && this.hasReadWriteReplicas) {
readError = await this.checkReadHosts();
}
return {
connection: this.name,
message: readError
? 'Unable to reach one of the read hosts'
: error
? 'Unable to reach the database server'
: 'Connection is healthy',
error: error || readError || null,
};
}
}
exports.Connection = Connection;
@@ -0,0 +1,106 @@
import { SimplePaginatorContract, SimplePaginatorMetaKeys } from '@ioc:Adonis/Lucid/Database';
/**
* Simple paginator works with the data set provided by the standard
* `offset` and `limit` based pagination.
*/
export declare class SimplePaginator extends Array implements SimplePaginatorContract<any> {
private totalNumber;
readonly perPage: number;
readonly currentPage: number;
private qs;
private url;
private rows;
/**
* Naming strategy for the pagination meta keys
*/
static namingStrategy: {
paginationMetaKeys(): SimplePaginatorMetaKeys;
};
/**
* Can be defined at per instance level as well
*/
namingStrategy: {
paginationMetaKeys(): SimplePaginatorMetaKeys;
};
/**
* The first page is always 1
*/
readonly firstPage: number;
/**
* Find if results set is empty or not
*/
readonly isEmpty: boolean;
/**
* Casting `total` to a number. Later, we can think of situations
* to cast it to a bigint
*/
readonly total: number;
/**
* Find if there are total records or not. This is not same as
* `isEmpty`.
*
* The `isEmpty` reports about the current set of results. However `hasTotal`
* reports about the total number of records, regardless of the current.
*/
readonly hasTotal: boolean;
/**
* The Last page number
*/
readonly lastPage: number;
/**
* Find if there are more pages to come
*/
readonly hasMorePages: boolean;
/**
* Find if there are enough results to be paginated or not
*/
readonly hasPages: boolean;
constructor(totalNumber: number, perPage: number, currentPage: number, ...rows: any[]);
/**
* A reference to the result rows
*/
all(): any[];
/**
* Returns JSON meta data
*/
getMeta(): any;
/**
* Returns JSON representation of the paginated
* data
*/
toJSON(): {
meta: any;
data: any[];
};
/**
* Define query string to be appended to the pagination links
*/
queryString(values: {
[key: string]: any;
}): this;
/**
* Define base url for making the pagination links
*/
baseUrl(url: string): this;
/**
* Returns url for a given page. Doesn't validates the integrity of the
* page
*/
getUrl(page: number): string;
/**
* Returns url for the next page
*/
getNextPageUrl(): string | null;
/**
* Returns URL for the previous page
*/
getPreviousPageUrl(): string | null;
/**
* Returns an array of urls under a given range
*/
getUrlsForRange(start: number, end: number): {
url: string;
page: number;
isActive: boolean;
}[];
}
@@ -0,0 +1,230 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SimplePaginator = void 0;
const qs_1 = require("qs");
const SnakeCase_1 = require("../../Orm/NamingStrategies/SnakeCase");
/**
* Simple paginator works with the data set provided by the standard
* `offset` and `limit` based pagination.
*/
class SimplePaginator extends Array {
constructor(totalNumber, perPage, currentPage, ...rows) {
super(...rows);
Object.defineProperty(this, "totalNumber", {
enumerable: true,
configurable: true,
writable: true,
value: totalNumber
});
Object.defineProperty(this, "perPage", {
enumerable: true,
configurable: true,
writable: true,
value: perPage
});
Object.defineProperty(this, "currentPage", {
enumerable: true,
configurable: true,
writable: true,
value: currentPage
});
Object.defineProperty(this, "qs", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "url", {
enumerable: true,
configurable: true,
writable: true,
value: '/'
});
Object.defineProperty(this, "rows", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Can be defined at per instance level as well
*/
Object.defineProperty(this, "namingStrategy", {
enumerable: true,
configurable: true,
writable: true,
value: SimplePaginator.namingStrategy
});
/**
* The first page is always 1
*/
Object.defineProperty(this, "firstPage", {
enumerable: true,
configurable: true,
writable: true,
value: 1
});
/**
* Find if results set is empty or not
*/
Object.defineProperty(this, "isEmpty", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Casting `total` to a number. Later, we can think of situations
* to cast it to a bigint
*/
Object.defineProperty(this, "total", {
enumerable: true,
configurable: true,
writable: true,
value: Number(this.totalNumber)
});
/**
* Find if there are total records or not. This is not same as
* `isEmpty`.
*
* The `isEmpty` reports about the current set of results. However `hasTotal`
* reports about the total number of records, regardless of the current.
*/
Object.defineProperty(this, "hasTotal", {
enumerable: true,
configurable: true,
writable: true,
value: this.total > 0
});
/**
* The Last page number
*/
Object.defineProperty(this, "lastPage", {
enumerable: true,
configurable: true,
writable: true,
value: Math.max(Math.ceil(this.total / this.perPage), 1)
});
/**
* Find if there are more pages to come
*/
Object.defineProperty(this, "hasMorePages", {
enumerable: true,
configurable: true,
writable: true,
value: this.lastPage > this.currentPage
});
/**
* Find if there are enough results to be paginated or not
*/
Object.defineProperty(this, "hasPages", {
enumerable: true,
configurable: true,
writable: true,
value: this.lastPage !== 1
});
this.rows = rows;
this.isEmpty = this.rows.length === 0;
}
/**
* A reference to the result rows
*/
all() {
return this.rows;
}
/**
* Returns JSON meta data
*/
getMeta() {
const metaKeys = this.namingStrategy.paginationMetaKeys();
return {
[metaKeys.total]: this.total,
[metaKeys.perPage]: this.perPage,
[metaKeys.currentPage]: this.currentPage,
[metaKeys.lastPage]: this.lastPage,
[metaKeys.firstPage]: this.firstPage,
[metaKeys.firstPageUrl]: this.getUrl(1),
[metaKeys.lastPageUrl]: this.getUrl(this.lastPage),
[metaKeys.nextPageUrl]: this.getNextPageUrl(),
[metaKeys.previousPageUrl]: this.getPreviousPageUrl(),
};
}
/**
* Returns JSON representation of the paginated
* data
*/
toJSON() {
return {
meta: this.getMeta(),
data: this.all(),
};
}
/**
* Define query string to be appended to the pagination links
*/
queryString(values) {
this.qs = values;
return this;
}
/**
* Define base url for making the pagination links
*/
baseUrl(url) {
this.url = url;
return this;
}
/**
* Returns url for a given page. Doesn't validates the integrity of the
* page
*/
getUrl(page) {
const qs = (0, qs_1.stringify)(Object.assign({}, this.qs, { page: page < 1 ? 1 : page }));
return `${this.url}?${qs}`;
}
/**
* Returns url for the next page
*/
getNextPageUrl() {
if (this.hasMorePages) {
return this.getUrl(this.currentPage + 1);
}
return null;
}
/**
* Returns URL for the previous page
*/
getPreviousPageUrl() {
if (this.currentPage > 1) {
return this.getUrl(this.currentPage - 1);
}
return null;
}
/**
* Returns an array of urls under a given range
*/
getUrlsForRange(start, end) {
let urls = [];
for (let i = start; i <= end; i++) {
urls.push({ url: this.getUrl(i), page: i, isActive: i === this.currentPage });
}
return urls;
}
}
exports.SimplePaginator = SimplePaginator;
/**
* Naming strategy for the pagination meta keys
*/
Object.defineProperty(SimplePaginator, "namingStrategy", {
enumerable: true,
configurable: true,
writable: true,
value: new SnakeCase_1.SnakeCaseNamingStrategy()
});
@@ -0,0 +1,721 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import { Knex } from 'knex';
import { Macroable } from 'macroable';
import { ChainableContract, DBQueryCallback } from '@ioc:Adonis/Lucid/Database';
/**
* The chainable query builder to consturct SQL queries for selecting, updating and
* deleting records.
*
* The API internally uses the knex query builder. However, many of methods may have
* different API.
*/
export declare abstract class Chainable extends Macroable implements ChainableContract {
knexQuery: Knex.QueryBuilder;
private queryCallback;
keysResolver?: ((columnName: string) => string) | undefined;
hasAggregates: boolean;
hasGroupBy: boolean;
hasUnion: boolean;
/**
* Collection where clauses in a 2nd array. Calling `wrapExisting`
* adds a new stack item
*/
private whereStack;
/**
* Returns the recent most array from the where stack
*/
private getRecentStackItem;
/**
* Returns the wrapping method for a given where method
*/
private getWrappingMethod;
/**
* Applies the where clauses
*/
protected applyWhere(): void;
/**
* An array of selected columns
*/
get columns(): ChainableContract['columns'];
/**
* Custom alias for the query results. Ignored if it not a
* subquery
*/
subQueryAlias?: string;
constructor(knexQuery: Knex.QueryBuilder, queryCallback: DBQueryCallback, keysResolver?: ((columnName: string) => string) | undefined);
/**
* Raises exception when only one argument is passed to a where
* clause and it is a string. It means the value is undefined
*/
private validateWhereSingleArgument;
/**
* Returns the value pair for the `whereBetween` clause
*/
private getBetweenPair;
/**
* Normalizes the columns aggregates functions to something
* knex can process.
*/
private normalizeAggregateColumns;
/**
* Resolves the column name considering raw queries as well.
*/
private resolveColumn;
/**
* Resolves column names
*/
protected resolveKey(columns: any, checkForObject?: boolean, returnValue?: any): any;
/**
* Apply existing query flags to a new query builder. This is
* done during clone operation
*/
protected applyQueryFlags(query: Chainable): void;
/**
* Transforms the value to something that knex can internally understand and
* handle. It includes.
*
* 1. Returning the `knexBuilder` for sub queries.
* 2. Returning the `knex.refBuilder` for reference builder.
* 2. Returning the `knexBuilder` for raw queries.
* 3. Wrapping callbacks, so that the end user receives an instance Lucid query
* builder and not knex query builder.
*/
protected transformValue(value: any): any;
/**
* Transforms the user callback to something that knex
* can internally process
*/
protected transformCallback(value: any): any;
/**
* Returns the underlying knex raw query builder for Lucid raw
* query builder
*/
protected transformRaw(value: any): any;
/**
* Define columns for selection
*/
select(...args: any[]): this;
/**
* Select table for the query. Re-calling this method multiple times will
* use the last selected table
*/
from(table: any): this;
/**
* Wrap existing where clauses to its own group
*/
wrapExisting(): this;
/**
* Add a `where` clause
*/
where(key: any, operator?: any, value?: any): this;
/**
* Add a `or where` clause
*/
orWhere(key: any, operator?: any, value?: any): this;
/**
* Alias for `where`
*/
andWhere(key: any, operator?: any, value?: any): this;
/**
* Adding `where not` clause
*/
whereNot(key: any, operator?: any, value?: any): this;
/**
* Adding `or where not` clause
*/
orWhereNot(key: any, operator?: any, value?: any): this;
/**
* Alias for [[whereNot]]
*/
andWhereNot(key: any, operator?: any, value?: any): this;
/**
* Add a where clause on a given column
*/
whereColumn(column: any, operator: any, comparisonColumn?: any): this;
/**
* Add a orWhere clause on a given column
*/
orWhereColumn(column: any, operator: any, comparisonColumn?: any): this;
/**
* Alias for whereColumn
*/
andWhereColumn(column: any, operator: any, comparisonColumn?: any): this;
/**
* Add a whereNot clause on a given column
*/
whereNotColumn(column: any, operator: any, comparisonColumn?: any): this;
/**
* Add a orWhereNotColumn clause on a given column
*/
orWhereNotColumn(column: any, operator: any, comparisonColumn?: any): this;
/**
* Alias for whereNotColumn
*/
andWhereNotColumn(column: any, operator: any, comparisonColumn?: any): this;
/**
* Adding a `where in` clause
*/
whereIn(columns: any, value: any): this;
/**
* Adding a `or where in` clause
*/
orWhereIn(columns: any, value: any): this;
/**
* Alias for [[whereIn]]
*/
andWhereIn(key: any, value: any): this;
/**
* Adding a `where not in` clause
*/
whereNotIn(columns: any, value: any): this;
/**
* Adding a `or where not in` clause
*/
orWhereNotIn(columns: any, value: any): this;
/**
* Alias for [[whereNotIn]]
*/
andWhereNotIn(key: any, value: any): this;
/**
* Adding `where not null` clause
*/
whereNull(key: any): this;
/**
* Adding `or where not null` clause
*/
orWhereNull(key: any): this;
/**
* Alias for [[whereNull]]
*/
andWhereNull(key: any): this;
/**
* Adding `where not null` clause
*/
whereNotNull(key: any): this;
/**
* Adding `or where not null` clause
*/
orWhereNotNull(key: any): this;
/**
* Alias for [[whereNotNull]]
*/
andWhereNotNull(key: any): this;
/**
* Add a `where exists` clause
*/
whereExists(value: any): this;
/**
* Add a `or where exists` clause
*/
orWhereExists(value: any): this;
/**
* Alias for [[whereExists]]
*/
andWhereExists(value: any): this;
/**
* Add a `where not exists` clause
*/
whereNotExists(value: any): this;
/**
* Add a `or where not exists` clause
*/
orWhereNotExists(value: any): this;
/**
* Alias for [[whereNotExists]]
*/
andWhereNotExists(value: any): this;
/**
* Add where between clause
*/
whereBetween(key: any, value: [any, any]): this;
/**
* Add where between clause
*/
orWhereBetween(key: any, value: any): this;
/**
* Alias for [[whereBetween]]
*/
andWhereBetween(key: any, value: any): this;
/**
* Add where between clause
*/
whereNotBetween(key: any, value: any): this;
/**
* Add where between clause
*/
orWhereNotBetween(key: any, value: any): this;
/**
* Alias for [[whereNotBetween]]
*/
andWhereNotBetween(key: any, value: any): this;
/**
* Adding a where clause using raw sql
*/
whereRaw(sql: any, bindings?: any): this;
/**
* Adding a or where clause using raw sql
*/
orWhereRaw(sql: any, bindings?: any): this;
/**
* Alias for [[whereRaw]]
*/
andWhereRaw(sql: any, bindings?: any): this;
/**
* Add a `where like` clause
*/
whereLike(key: any, value: any): this;
/**
* Add a `where like` clause
*/
orWhereLike(key: any, value?: any): this;
/**
* Add a `where like` clause
*/
andWhereLike(key: any, value?: any): this;
/**
* Add a `where like` clause
*/
whereILike(key: any, value?: any): this;
/**
* Add a `where like` clause
*/
orWhereILike(key: any, value?: any): this;
/**
* Add a `where like` clause
*/
andWhereILike(key: any, value?: any): this;
/**
* Define a where clause with value that matches for JSON
*/
whereJson(column: string, value: any): this;
/**
* Define a or where clause with value that matches for JSON
*/
orWhereJson(column: string, value: any): this;
/**
* Define a where clause with value that matches for JSON
*
* @alias whereJson
*/
andWhereJson(column: string, value: any): this;
/**
* Define a where clause with value that matches for JSON
*/
whereNotJson(column: string, value: any): this;
/**
* Define a or where clause with value that matches for JSON
*/
orWhereNotJson(column: string, value: any): this;
/**
* Define a where clause with value that matches for JSON
*
* @alias whereNotJson
*/
andWhereNotJson(column: string, value: any): this;
/**
* Define a where clause with value that matches for a superset of
* JSON
*/
whereJsonSuperset(column: string, value: any): this;
/**
* Define a or where clause with value that matches for a superset of
* JSON
*/
orWhereJsonSuperset(column: string, value: any): this;
/**
* Define or where clause with value that matches for a superset of
* JSON
*
* @alias whereJsonSuperset
*/
andWhereJsonSuperset(column: string, value: any): this;
/**
* Define a where clause with value that matches for a superset of
* JSON
*/
whereNotJsonSuperset(column: string, value: any): this;
/**
* Define a or where clause with value that matches for a superset of
* JSON
*/
orWhereNotJsonSuperset(column: string, value: any): this;
/**
* Define or where clause with value that matches for a superset of
* JSON
*
* @alias whereNotJsonSuperset
*/
andWhereNotJsonSuperset(column: string, value: any): this;
/**
* Define a where clause with value that matches for a subset of
* JSON
*/
whereJsonSubset(column: string, value: any): this;
/**
* Define a or where clause with value that matches for a subset of
* JSON
*/
orWhereJsonSubset(column: string, value: any): this;
/**
* Define or where clause with value that matches for a subset of
* JSON
*
* @alias whereJsonSubset
*/
andWhereJsonSubset(column: string, value: any): this;
/**
* Define a where clause with value that matches for a subset of
* JSON
*/
whereNotJsonSubset(column: string, value: any): this;
/**
* Define a or where clause with value that matches for a subset of
* JSON
*/
orWhereNotJsonSubset(column: string, value: any): this;
/**
* Define or where clause with value that matches for a subset of
* JSON
*
* @alias whereNotJsonSubset
*/
andWhereNotJsonSubset(column: string, value: any): this;
/**
* Adds a where clause with comparison of a value returned
* by a JsonPath given an operator and a value.
*/
whereJsonPath(column: string, jsonPath: string, operator: any, value?: any): this;
/**
* Adds a or where clause with comparison of a value returned
* by a JsonPath given an operator and a value.
*/
orWhereJsonPath(column: string, jsonPath: string, operator: any, value?: any): this;
/**
* Adds a where clause with comparison of a value returned
* by a JsonPath given an operator and a value.
*
* @alias whereJsonPath
*/
andWhereJsonPath(column: string, jsonPath: string, operator: any, value?: any): this;
/**
* Add a join clause
*/
join(table: any, first: any, operator?: any, second?: any): this;
/**
* Add an inner join clause
*/
innerJoin(table: any, first: any, operator?: any, second?: any): this;
/**
* Add a left join clause
*/
leftJoin(table: any, first: any, operator?: any, second?: any): this;
/**
* Add a left outer join clause
*/
leftOuterJoin(table: any, first: any, operator?: any, second?: any): this;
/**
* Add a right join clause
*/
rightJoin(table: any, first: any, operator?: any, second?: any): this;
/**
* Add a right outer join clause
*/
rightOuterJoin(table: any, first: any, operator?: any, second?: any): this;
/**
* Add a full outer join clause
*/
fullOuterJoin(table: any, first: any, operator?: any, second?: any): this;
/**
* Add a cross join clause
*/
crossJoin(table: any, first: any, operator?: any, second?: any): this;
/**
* Add join clause as a raw query
*/
joinRaw(sql: any, bindings?: any): this;
/**
* Adds a having clause. The having clause breaks for `postgreSQL` when
* referencing alias columns, since PG doesn't support alias columns
* being referred within `having` clause. The end user has to
* use raw queries in this case.
*/
having(key: any, operator?: any, value?: any): this;
/**
* Adds or having clause. The having clause breaks for `postgreSQL` when
* referencing alias columns, since PG doesn't support alias columns
* being referred within `having` clause. The end user has to
* use raw queries in this case.
*/
orHaving(key: any, operator?: any, value?: any): this;
/**
* Alias for [[having]]
*/
andHaving(key: any, operator?: any, value?: any): this;
/**
* Adding having in clause to the query
*/
havingIn(key: any, value: any): this;
/**
* Adding or having in clause to the query
*/
orHavingIn(key: any, value: any): this;
/**
* Alias for [[havingIn]]
*/
andHavingIn(key: any, value: any): this;
/**
* Adding having not in clause to the query
*/
havingNotIn(key: any, value: any): this;
/**
* Adding or having not in clause to the query
*/
orHavingNotIn(key: any, value: any): this;
/**
* Alias for [[havingNotIn]]
*/
andHavingNotIn(key: any, value: any): this;
/**
* Adding having null clause
*/
havingNull(key: any): this;
/**
* Adding or having null clause
*/
orHavingNull(key: any): this;
/**
* Alias for [[havingNull]] clause
*/
andHavingNull(key: any): this;
/**
* Adding having not null clause
*/
havingNotNull(key: any): this;
/**
* Adding or having not null clause
*/
orHavingNotNull(key: any): this;
/**
* Alias for [[havingNotNull]] clause
*/
andHavingNotNull(key: any): this;
/**
* Adding `having exists` clause
*/
havingExists(value: any): this;
/**
* Adding `or having exists` clause
*/
orHavingExists(value: any): this;
/**
* Alias for [[havingExists]]
*/
andHavingExists(value: any): this;
/**
* Adding `having not exists` clause
*/
havingNotExists(value: any): this;
/**
* Adding `or having not exists` clause
*/
orHavingNotExists(value: any): this;
/**
* Alias for [[havingNotExists]]
*/
andHavingNotExists(value: any): this;
/**
* Adding `having between` clause
*/
havingBetween(key: any, value: any): this;
/**
* Adding `or having between` clause
*/
orHavingBetween(key: any, value: any): this;
/**
* Alias for [[havingBetween]]
*/
andHavingBetween(key: any, value: any): this;
/**
* Adding `having not between` clause
*/
havingNotBetween(key: any, value: any): this;
/**
* Adding `or having not between` clause
*/
orHavingNotBetween(key: any, value: any): this;
/**
* Alias for [[havingNotBetween]]
*/
andHavingNotBetween(key: any, value: any): this;
/**
* Adding a where clause using raw sql
*/
havingRaw(sql: any, bindings?: any): this;
/**
* Adding a where clause using raw sql
*/
orHavingRaw(sql: any, bindings?: any): this;
/**
* Alias for [[havingRaw]]
*/
andHavingRaw(sql: any, bindings?: any): this;
/**
* Add distinct clause
*/
distinct(...columns: any[]): this;
/**
* Add distinctOn clause
*/
distinctOn(...columns: any[]): this;
/**
* Add group by clause
*/
groupBy(...columns: any[]): this;
/**
* Add group by clause as a raw query
*/
groupByRaw(sql: any, bindings?: any): this;
/**
* Add order by clause
*/
orderBy(column: any, direction?: any): this;
/**
* Add order by clause as a raw query
*/
orderByRaw(sql: any, bindings?: any): this;
/**
* Define select offset
*/
offset(value: number): this;
/**
* Define results limit
*/
limit(value: number): this;
/**
* Define union queries
*/
union(queries: any, wrap?: boolean): this;
/**
* Define union all queries
*/
unionAll(queries: any, wrap?: boolean): this;
/**
* Define intersect queries
*/
intersect(queries: any, wrap?: boolean): this;
/**
* Clear select columns
*/
clearSelect(): this;
/**
* Clear where clauses
*/
clearWhere(): this;
/**
* Clear order by
*/
clearOrder(): this;
/**
* Clear having
*/
clearHaving(): this;
/**
* Clear limit
*/
clearLimit(): this;
/**
* Clear offset
*/
clearOffset(): this;
/**
* Specify `FOR UPDATE` lock mode for a given
* query
*/
forUpdate(...tableNames: string[]): this;
/**
* Specify `FOR SHARE` lock mode for a given
* query
*/
forShare(...tableNames: string[]): this;
/**
* Skip locked rows
*/
skipLocked(): this;
/**
* Fail when query wants a locked row
*/
noWait(): this;
/**
* Define `with` CTE
*/
with(alias: any, query: any, columns?: string[]): this;
/**
* Define `with` CTE with recursive keyword
*/
withRecursive(alias: any, query: any, columns?: string[]): this;
/**
* Define `with materialized` CTE
*/
withMaterialized(alias: any, query: any, columns?: string[]): this;
/**
* Define not `with materialized` CTE
*/
withNotMaterialized(alias: any, query: any, columns?: string[]): this;
/**
* Define schema for the table
*/
withSchema(schema: any): this;
/**
* Define table alias
*/
as(alias: any): this;
/**
* Count rows for the current query
*/
count(columns: any, alias?: any): this;
/**
* Count distinct rows for the current query
*/
countDistinct(columns: any, alias?: any): this;
/**
* Make use of `min` aggregate function
*/
min(columns: any, alias?: any): this;
/**
* Make use of `max` aggregate function
*/
max(columns: any, alias?: any): this;
/**
* Make use of `avg` aggregate function
*/
avg(columns: any, alias?: any): this;
/**
* Make use of distinct `avg` aggregate function
*/
avgDistinct(columns: any, alias?: any): this;
/**
* Make use of `sum` aggregate function
*/
sum(columns: any, alias?: any): this;
/**
* Make use of distinct `sum` aggregate function
*/
sumDistinct(columns: any, alias?: any): this;
/**
* A shorthand for applying offset and limit based upon
* the current page
*/
forPage(page: number, perPage: number): this;
/**
* Define a query to constraint to be defined when condition is truthy
*/
if(condition: any, matchCallback: (query: this) => any, noMatchCallback?: (query: this) => any): this;
/**
* Define a query to constraint to be defined when condition is falsy
*/
unless(condition: any, matchCallback: (query: this) => any, noMatchCallback?: (query: this) => any): this;
/**
* Define matching blocks just like `if/else if and else`.
*/
match(...blocks: ([condition: any, callback: (query: this) => any] | ((query: this) => any))[]): this;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,137 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import { Knex } from 'knex';
import { DialectContract, QueryClientContract, TransactionClientContract, DatabaseQueryBuilderContract } from '@ioc:Adonis/Lucid/Database';
import { Chainable } from './Chainable';
import { SimplePaginator } from '../Paginator/SimplePaginator';
/**
* Database query builder exposes the API to construct and run queries for selecting,
* updating and deleting records.
*/
export declare class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuilderContract {
client: QueryClientContract;
keysResolver?: ((columnName: string) => string) | undefined;
/**
* Custom data someone want to send to the profiler and the
* query event
*/
private customReporterData;
/**
* Control whether to debug the query or not. The initial
* value is inherited from the query client
*/
private debugQueries;
/**
* Required by macroable
*/
protected static macros: {};
protected static getters: {};
constructor(builder: Knex.QueryBuilder, client: QueryClientContract, keysResolver?: ((columnName: string) => string) | undefined);
/**
* Ensures that we are not executing `update` or `del` when using read only
* client
*/
private ensureCanPerformWrites;
/**
* Returns the log data
*/
private getQueryData;
/**
* Define custom reporter data. It will be merged with
* the existing data
*/
reporterData(data: any): this;
/**
* Delete rows under the current query
*/
del(returning?: string | string[]): this;
/**
* Alias for [[del]]
*/
delete(returning?: string | string[]): this;
/**
* Clone the current query builder
*/
clone(): DatabaseQueryBuilder;
/**
* Define returning columns
*/
returning(columns: any): this;
/**
* Perform update by incrementing value for a given column. Increments
* can be clubbed with `update` as well
*/
increment(column: any, counter?: any): this;
/**
* Perform update by decrementing value for a given column. Decrements
* can be clubbed with `update` as well
*/
decrement(column: any, counter?: any): this;
/**
* Perform update
*/
update(column: any, value?: any, returning?: string | string[]): this;
/**
* Fetch and return first results from the results set. This method
* will implicitly set a `limit` on the query
*/
first(): Promise<any>;
/**
* Fetch and return first results from the results set. This method
* will implicitly set a `limit` on the query
*/
firstOrFail(): Promise<any>;
/**
* Define a query to constraint to be defined when condition is truthy
*/
ifDialect(dialects: DialectContract['name'] | DialectContract['name'][], matchCallback: (query: this) => any, noMatchCallback?: (query: this) => any): this;
/**
* Define a query to constraint to be defined when condition is falsy
*/
unlessDialect(dialects: DialectContract['name'] | DialectContract['name'][], matchCallback: (query: this) => any, noMatchCallback?: (query: this) => any): this;
/**
* Turn on/off debugging for this query
*/
debug(debug: boolean): this;
/**
* Define query timeout
*/
timeout(time: number, options?: {
cancel: boolean;
}): this;
/**
* Returns SQL query as a string
*/
toQuery(): string;
/**
* Run query inside the given transaction
*/
useTransaction(transaction: TransactionClientContract): this;
/**
* Executes the query
*/
exec(): Promise<any>;
/**
* Paginate through rows inside a given table
*/
paginate(page: number, perPage?: number): Promise<SimplePaginator>;
/**
* Get sql representation of the query
*/
toSQL(): Knex.Sql;
/**
* Implementation of `then` for the promise API
*/
then(resolve: any, reject?: any): any;
/**
* Implementation of `catch` for the promise API
*/
catch(reject: any): any;
/**
* Implementation of `finally` for the promise API
*/
finally(fullfilled: any): Promise<any>;
/**
* Required when Promises are extended
*/
get [Symbol.toStringTag](): string;
}
@@ -0,0 +1,318 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DatabaseQueryBuilder = void 0;
const utils_1 = require("@poppinss/utils");
const Chainable_1 = require("./Chainable");
const QueryRunner_1 = require("../../QueryRunner");
const SimplePaginator_1 = require("../Paginator/SimplePaginator");
/**
* Wrapping the user function for a query callback and give them
* a new instance of the `DatabaseQueryBuilder` and not
* knex.QueryBuilder
*/
const queryCallback = (userFn, keysResolver) => {
return (builder) => {
/**
* Sub queries don't need the client, since client is used to execute the query
* and subqueries are not executed seperately. That's why we just pass
* an empty object.
*
* Other option is to have this method for each instance of the class, but this
* is waste of resources.
*/
const query = new DatabaseQueryBuilder(builder, {}, keysResolver);
userFn(query);
query['applyWhere']();
};
};
/**
* Database query builder exposes the API to construct and run queries for selecting,
* updating and deleting records.
*/
class DatabaseQueryBuilder extends Chainable_1.Chainable {
constructor(builder, client, keysResolver) {
super(builder, queryCallback, keysResolver);
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
Object.defineProperty(this, "keysResolver", {
enumerable: true,
configurable: true,
writable: true,
value: keysResolver
});
/**
* Custom data someone want to send to the profiler and the
* query event
*/
Object.defineProperty(this, "customReporterData", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Control whether to debug the query or not. The initial
* value is inherited from the query client
*/
Object.defineProperty(this, "debugQueries", {
enumerable: true,
configurable: true,
writable: true,
value: this.client.debug
});
}
/**
* Ensures that we are not executing `update` or `del` when using read only
* client
*/
ensureCanPerformWrites() {
if (this.client && this.client.mode === 'read') {
throw new utils_1.Exception('Updates and deletes cannot be performed in read mode');
}
}
/**
* Returns the log data
*/
getQueryData() {
return {
connection: this.client.connectionName,
inTransaction: this.client.isTransaction,
...this.customReporterData,
};
}
/**
* Define custom reporter data. It will be merged with
* the existing data
*/
reporterData(data) {
this.customReporterData = data;
return this;
}
/**
* Delete rows under the current query
*/
del(returning) {
this.ensureCanPerformWrites();
returning ? this.knexQuery.del(returning) : this.knexQuery.del();
return this;
}
/**
* Alias for [[del]]
*/
delete(returning) {
return this.del(returning);
}
/**
* Clone the current query builder
*/
clone() {
const clonedQuery = new DatabaseQueryBuilder(this.knexQuery.clone(), this.client);
this.applyQueryFlags(clonedQuery);
clonedQuery.debug(this.debugQueries);
this.customReporterData && clonedQuery.reporterData(this.customReporterData);
return clonedQuery;
}
/**
* Define returning columns
*/
returning(columns) {
if (this.client.dialect.supportsReturningStatement) {
columns = Array.isArray(columns)
? columns.map((column) => this.resolveKey(column))
: this.resolveKey(columns);
this.knexQuery.returning(columns);
}
return this;
}
/**
* Perform update by incrementing value for a given column. Increments
* can be clubbed with `update` as well
*/
increment(column, counter) {
this.ensureCanPerformWrites();
this.knexQuery.increment(this.resolveKey(column, true), counter);
return this;
}
/**
* Perform update by decrementing value for a given column. Decrements
* can be clubbed with `update` as well
*/
decrement(column, counter) {
this.ensureCanPerformWrites();
this.knexQuery.decrement(this.resolveKey(column, true), counter);
return this;
}
/**
* Perform update
*/
update(column, value, returning) {
this.ensureCanPerformWrites();
if (value === undefined && returning === undefined) {
this.knexQuery.update(this.resolveKey(column, true));
}
else if (returning === undefined) {
this.knexQuery.update(this.resolveKey(column), value);
}
else {
this.knexQuery.update(this.resolveKey(column), value, returning);
}
return this;
}
/**
* Fetch and return first results from the results set. This method
* will implicitly set a `limit` on the query
*/
async first() {
const result = await this.limit(1)['exec']();
return result[0] || null;
}
/**
* Fetch and return first results from the results set. This method
* will implicitly set a `limit` on the query
*/
async firstOrFail() {
const row = await this.first();
if (!row) {
throw new utils_1.Exception('Row not found', 404, 'E_ROW_NOT_FOUND');
}
return row;
}
/**
* Define a query to constraint to be defined when condition is truthy
*/
ifDialect(dialects, matchCallback, noMatchCallback) {
dialects = Array.isArray(dialects) ? dialects : [dialects];
if (dialects.includes(this.client.dialect.name)) {
matchCallback(this);
}
else if (noMatchCallback) {
noMatchCallback(this);
}
return this;
}
/**
* Define a query to constraint to be defined when condition is falsy
*/
unlessDialect(dialects, matchCallback, noMatchCallback) {
dialects = Array.isArray(dialects) ? dialects : [dialects];
if (!dialects.includes(this.client.dialect.name)) {
matchCallback(this);
}
else if (noMatchCallback) {
noMatchCallback(this);
}
return this;
}
/**
* Turn on/off debugging for this query
*/
debug(debug) {
this.debugQueries = debug;
return this;
}
/**
* Define query timeout
*/
timeout(time, options) {
this.knexQuery['timeout'](time, options);
return this;
}
/**
* Returns SQL query as a string
*/
toQuery() {
this.applyWhere();
return this.knexQuery.toQuery();
}
/**
* Run query inside the given transaction
*/
useTransaction(transaction) {
this.knexQuery.transacting(transaction.knexClient);
return this;
}
/**
* Executes the query
*/
async exec() {
this.applyWhere();
return new QueryRunner_1.QueryRunner(this.client, this.debugQueries, this.getQueryData()).run(this.knexQuery);
}
/**
* Paginate through rows inside a given table
*/
async paginate(page, perPage = 20) {
/**
* Cast to number
*/
page = Number(page);
perPage = Number(perPage);
const countQuery = this.client
.query()
.from(this.clone().clearOrder().clearLimit().clearOffset().as('subQuery'))
.count('* as total');
const aggregates = await countQuery.exec();
const total = aggregates[0].total;
const results = total > 0 ? await this.forPage(page, perPage).exec() : [];
return new SimplePaginator_1.SimplePaginator(total, perPage, page, ...results);
}
/**
* Get sql representation of the query
*/
toSQL() {
this.applyWhere();
return this.knexQuery.toSQL();
}
/**
* Implementation of `then` for the promise API
*/
then(resolve, reject) {
return this.exec().then(resolve, reject);
}
/**
* Implementation of `catch` for the promise API
*/
catch(reject) {
return this.exec().catch(reject);
}
/**
* Implementation of `finally` for the promise API
*/
finally(fullfilled) {
return this.exec().finally(fullfilled);
}
/**
* Required when Promises are extended
*/
get [Symbol.toStringTag]() {
return this.constructor.name;
}
}
exports.DatabaseQueryBuilder = DatabaseQueryBuilder;
/**
* Required by macroable
*/
Object.defineProperty(DatabaseQueryBuilder, "macros", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(DatabaseQueryBuilder, "getters", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
@@ -0,0 +1,111 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import { Knex } from 'knex';
import { Macroable } from 'macroable';
import { QueryClientContract, TransactionClientContract, InsertQueryBuilderContract } from '@ioc:Adonis/Lucid/Database';
/**
* Exposes the API for performing SQL inserts
*/
export declare class InsertQueryBuilder extends Macroable implements InsertQueryBuilderContract {
knexQuery: Knex.QueryBuilder;
client: QueryClientContract;
/**
* Custom data someone want to send to the profiler and the
* query event
*/
private customReporterData;
/**
* Control whether to debug the query or not. The initial
* value is inherited from the query client
*/
private debugQueries;
/**
* Required by macroable
*/
protected static macros: {};
protected static getters: {};
constructor(knexQuery: Knex.QueryBuilder, client: QueryClientContract);
/**
* Returns the log data
*/
private getQueryData;
/**
* Transforms the value to something that knex can internally understand and
* handle. It includes.
*
* 1. Returning the `knexBuilder` for sub queries.
* 2. Returning the `knexBuilder` for raw queries.
*/
protected transformValue(value: any): any;
/**
* Returns the underlying knex raw query builder for Lucid raw
* query builder
*/
protected transformRaw(value: any): any;
/**
* Define custom reporter data. It will be merged with
* the existing data
*/
reporterData(data: any): this;
/**
* Define table for performing the insert query
*/
table(table: any): this;
/**
* Define schema for the table
*/
withSchema(schema: any): this;
/**
* Define returning columns for the insert query
*/
returning(column: any): any;
/**
* Perform insert query
*/
insert(columns: any): this;
/**
* Insert multiple rows in a single query
*/
multiInsert(columns: any): this;
/**
* Turn on/off debugging for this query
*/
debug(debug: boolean): this;
/**
* Define query timeout
*/
timeout(time: number, options?: {
cancel: boolean;
}): this;
/**
* Returns SQL query as a string
*/
toQuery(): string;
/**
* Run query inside the given transaction
*/
useTransaction(transaction: TransactionClientContract): this;
/**
* Executes the query
*/
exec(): Promise<any>;
/**
* Get sql representation of the query
*/
toSQL(): Knex.Sql;
/**
* Implementation of `then` for the promise API
*/
then(resolve: any, reject?: any): any;
/**
* Implementation of `catch` for the promise API
*/
catch(reject: any): any;
/**
* Implementation of `finally` for the promise API
*/
finally(fullfilled: any): Promise<any>;
/**
* Required when Promises are extended
*/
get [Symbol.toStringTag](): string;
}
+231
View File
@@ -0,0 +1,231 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.InsertQueryBuilder = void 0;
const macroable_1 = require("macroable");
const Raw_1 = require("./Raw");
const QueryRunner_1 = require("../../QueryRunner");
const Raw_2 = require("../StaticBuilder/Raw");
const Reference_1 = require("../StaticBuilder/Reference");
/**
* Exposes the API for performing SQL inserts
*/
class InsertQueryBuilder extends macroable_1.Macroable {
constructor(knexQuery, client) {
super();
Object.defineProperty(this, "knexQuery", {
enumerable: true,
configurable: true,
writable: true,
value: knexQuery
});
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
/**
* Custom data someone want to send to the profiler and the
* query event
*/
Object.defineProperty(this, "customReporterData", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Control whether to debug the query or not. The initial
* value is inherited from the query client
*/
Object.defineProperty(this, "debugQueries", {
enumerable: true,
configurable: true,
writable: true,
value: this.client.debug
});
}
/**
* Returns the log data
*/
getQueryData() {
return {
connection: this.client.connectionName,
inTransaction: this.client.isTransaction,
...this.customReporterData,
};
}
/**
* Transforms the value to something that knex can internally understand and
* handle. It includes.
*
* 1. Returning the `knexBuilder` for sub queries.
* 2. Returning the `knexBuilder` for raw queries.
*/
transformValue(value) {
if (value instanceof Reference_1.ReferenceBuilder) {
return value.toKnex(this.knexQuery.client);
}
return this.transformRaw(value);
}
/**
* Returns the underlying knex raw query builder for Lucid raw
* query builder
*/
transformRaw(value) {
if (value instanceof Raw_1.RawQueryBuilder) {
return value['knexQuery'];
}
if (value instanceof Raw_2.RawBuilder) {
return value.toKnex(this.knexQuery.client);
}
return value;
}
/**
* Define custom reporter data. It will be merged with
* the existing data
*/
reporterData(data) {
this.customReporterData = data;
return this;
}
/**
* Define table for performing the insert query
*/
table(table) {
this.knexQuery.table(table);
return this;
}
/**
* Define schema for the table
*/
withSchema(schema) {
this.knexQuery.withSchema(schema);
return this;
}
/**
* Define returning columns for the insert query
*/
returning(column) {
if (this.client.dialect.supportsReturningStatement) {
this.knexQuery.returning(column);
}
return this;
}
/**
* Perform insert query
*/
insert(columns) {
if (columns && Array.isArray(columns)) {
columns = columns.map((column) => {
return column && typeof column === 'object'
? Object.keys(column).reduce((result, key) => {
result[key] = this.transformValue(column[key]);
return result;
}, {})
: column;
});
}
else if (columns && typeof columns === 'object') {
columns = Object.keys(columns).reduce((result, key) => {
result[key] = this.transformValue(columns[key]);
return result;
}, {});
}
this.knexQuery.insert(columns);
return this;
}
/**
* Insert multiple rows in a single query
*/
multiInsert(columns) {
return this.insert(columns);
}
/**
* Turn on/off debugging for this query
*/
debug(debug) {
this.debugQueries = debug;
return this;
}
/**
* Define query timeout
*/
timeout(time, options) {
this.knexQuery['timeout'](time, options);
return this;
}
/**
* Returns SQL query as a string
*/
toQuery() {
return this.knexQuery.toQuery();
}
/**
* Run query inside the given transaction
*/
useTransaction(transaction) {
this.knexQuery.transacting(transaction.knexClient);
return this;
}
/**
* Executes the query
*/
async exec() {
return new QueryRunner_1.QueryRunner(this.client, this.debugQueries, this.getQueryData()).run(this.knexQuery);
}
/**
* Get sql representation of the query
*/
toSQL() {
return this.knexQuery.toSQL();
}
/**
* Implementation of `then` for the promise API
*/
then(resolve, reject) {
return this.exec().then(resolve, reject);
}
/**
* Implementation of `catch` for the promise API
*/
catch(reject) {
return this.exec().catch(reject);
}
/**
* Implementation of `finally` for the promise API
*/
finally(fullfilled) {
return this.exec().finally(fullfilled);
}
/**
* Required when Promises are extended
*/
get [Symbol.toStringTag]() {
return this.constructor.name;
}
}
exports.InsertQueryBuilder = InsertQueryBuilder;
/**
* Required by macroable
*/
Object.defineProperty(InsertQueryBuilder, "macros", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(InsertQueryBuilder, "getters", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
+76
View File
@@ -0,0 +1,76 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import { Knex } from 'knex';
import { QueryClientContract, RawQueryBuilderContract, TransactionClientContract } from '@ioc:Adonis/Lucid/Database';
/**
* Exposes the API to execute raw queries
*/
export declare class RawQueryBuilder implements RawQueryBuilderContract {
knexQuery: Knex.Raw;
client: QueryClientContract;
/**
* Custom data someone want to send to the profiler and the
* query event
*/
private customReporterData;
/**
* Control whether to debug the query or not. The initial
* value is inherited from the query client
*/
private debugQueries;
constructor(knexQuery: Knex.Raw, client: QueryClientContract);
/**
* Returns the log data
*/
private getQueryData;
/**
* Define custom reporter data. It will be merged with
* the existing data
*/
reporterData(data: any): this;
/**
* Wrap the query with before/after strings.
*/
wrap(before: string, after: string): this;
/**
* Turn on/off debugging for this query
*/
debug(debug: boolean): this;
/**
* Define query timeout
*/
timeout(time: number, options?: {
cancel: boolean;
}): this;
/**
* Returns SQL query as a string
*/
toQuery(): string;
/**
* Run query inside the given transaction
*/
useTransaction(transaction: TransactionClientContract): this;
/**
* Executes the query
*/
exec(): Promise<any>;
/**
* Get sql representation of the query
*/
toSQL(): Knex.Sql;
/**
* Implementation of `then` for the promise API
*/
then(resolve: any, reject?: any): any;
/**
* Implementation of `catch` for the promise API
*/
catch(reject: any): any;
/**
* Implementation of `finally` for the promise API
*/
finally(fullfilled: any): Promise<any>;
/**
* Required when Promises are extended
*/
get [Symbol.toStringTag](): string;
}
+140
View File
@@ -0,0 +1,140 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RawQueryBuilder = void 0;
const QueryRunner_1 = require("../../QueryRunner");
/**
* Exposes the API to execute raw queries
*/
class RawQueryBuilder {
constructor(knexQuery, client) {
Object.defineProperty(this, "knexQuery", {
enumerable: true,
configurable: true,
writable: true,
value: knexQuery
});
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
/**
* Custom data someone want to send to the profiler and the
* query event
*/
Object.defineProperty(this, "customReporterData", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Control whether to debug the query or not. The initial
* value is inherited from the query client
*/
Object.defineProperty(this, "debugQueries", {
enumerable: true,
configurable: true,
writable: true,
value: this.client.debug
});
}
/**
* Returns the log data
*/
getQueryData() {
return {
connection: this.client.connectionName,
inTransaction: this.client.isTransaction,
...this.customReporterData,
};
}
/**
* Define custom reporter data. It will be merged with
* the existing data
*/
reporterData(data) {
this.customReporterData = data;
return this;
}
/**
* Wrap the query with before/after strings.
*/
wrap(before, after) {
this.knexQuery.wrap(before, after);
return this;
}
/**
* Turn on/off debugging for this query
*/
debug(debug) {
this.debugQueries = debug;
return this;
}
/**
* Define query timeout
*/
timeout(time, options) {
this.knexQuery['timeout'](time, options);
return this;
}
/**
* Returns SQL query as a string
*/
toQuery() {
return this.knexQuery.toQuery();
}
/**
* Run query inside the given transaction
*/
useTransaction(transaction) {
this.knexQuery.transacting(transaction.knexClient);
return this;
}
/**
* Executes the query
*/
async exec() {
return new QueryRunner_1.QueryRunner(this.client, this.debugQueries, this.getQueryData()).run(this.knexQuery);
}
/**
* Get sql representation of the query
*/
toSQL() {
return this.knexQuery.toSQL();
}
/**
* Implementation of `then` for the promise API
*/
then(resolve, reject) {
return this.exec().then(resolve, reject);
}
/**
* Implementation of `catch` for the promise API
*/
catch(reject) {
return this.exec().catch(reject);
}
/**
* Implementation of `finally` for the promise API
*/
finally(fullfilled) {
return this.exec().finally(fullfilled);
}
/**
* Required when Promises are extended
*/
get [Symbol.toStringTag]() {
return this.constructor.name;
}
}
exports.RawQueryBuilder = RawQueryBuilder;
+22
View File
@@ -0,0 +1,22 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import { Knex } from 'knex';
import { RawBuilderContract } from '@ioc:Adonis/Lucid/Database';
/**
* Exposes the API to construct raw queries. If you want to execute
* raw queries, you can use the RawQueryBuilder
*/
export declare class RawBuilder implements RawBuilderContract {
private sql;
private bindings?;
private wrapBefore;
private wrapAfter;
constructor(sql: string, bindings?: any);
/**
* Wrap the query with before/after strings.
*/
wrap(before: string, after: string): this;
/**
* Converts the raw query to knex raw query instance
*/
toKnex(client: Knex.Client): Knex.Raw;
}
+62
View File
@@ -0,0 +1,62 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RawBuilder = void 0;
/**
* Exposes the API to construct raw queries. If you want to execute
* raw queries, you can use the RawQueryBuilder
*/
class RawBuilder {
constructor(sql, bindings) {
Object.defineProperty(this, "sql", {
enumerable: true,
configurable: true,
writable: true,
value: sql
});
Object.defineProperty(this, "bindings", {
enumerable: true,
configurable: true,
writable: true,
value: bindings
});
Object.defineProperty(this, "wrapBefore", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "wrapAfter", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
/**
* Wrap the query with before/after strings.
*/
wrap(before, after) {
this.wrapAfter = after;
this.wrapBefore = before;
return this;
}
/**
* Converts the raw query to knex raw query instance
*/
toKnex(client) {
const rawQuery = client.raw(this.sql, this.bindings);
if (this.wrapBefore && this.wrapAfter) {
rawQuery.wrap(this.wrapBefore, this.wrapAfter);
}
return rawQuery;
}
}
exports.RawBuilder = RawBuilder;
@@ -0,0 +1,25 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import { Knex } from 'knex';
import { ReferenceBuilderContract } from '@ioc:Adonis/Lucid/Database';
/**
* Reference builder to create SQL reference values
*/
export declare class ReferenceBuilder implements ReferenceBuilderContract {
private ref;
private client;
private schema;
private alias;
constructor(ref: string, client: Knex.Client);
/**
* Define schema
*/
withSchema(schema: string): this;
/**
* Define alias
*/
as(alias: string): this;
/**
* Converts reference to knex
*/
toKnex(client?: Knex.Client): Knex.Ref<any, any>;
}
@@ -0,0 +1,66 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReferenceBuilder = void 0;
/**
* Reference builder to create SQL reference values
*/
class ReferenceBuilder {
constructor(ref, client) {
Object.defineProperty(this, "ref", {
enumerable: true,
configurable: true,
writable: true,
value: ref
});
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
Object.defineProperty(this, "schema", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "alias", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
/**
* Define schema
*/
withSchema(schema) {
this.schema = schema;
return this;
}
/**
* Define alias
*/
as(alias) {
this.alias = alias;
return this;
}
/**
* Converts reference to knex
*/
toKnex(client) {
const ref = (client || this.client).ref(this.ref);
this.schema && ref.withSchema(this.schema);
this.alias && ref.as(this.alias);
return ref;
}
}
exports.ReferenceBuilder = ReferenceBuilder;
+163
View File
@@ -0,0 +1,163 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference path="../../adonis-typings/querybuilder.d.ts" />
/// <reference path="../../adonis-typings/database.d.ts" />
/// <reference path="../../adonis-typings/model.d.ts" />
/// <reference path="../../adonis-typings/orm.d.ts" />
/// <reference path="../../adonis-typings/relations.d.ts" />
/// <reference types="@adonisjs/logger/build/adonis-typings/logger" />
/// <reference types="@adonisjs/profiler/build/adonis-typings/profiler" />
/// <reference types="@adonisjs/events/build/adonis-typings" />
/// <reference types="@adonisjs/core/build/adonis-typings/health-check" />
import { Macroable } from 'macroable';
import { EmitterContract } from '@ioc:Adonis/Core/Event';
import { LoggerContract } from '@ioc:Adonis/Core/Logger';
import { ProfilerContract } from '@ioc:Adonis/Core/Profiler';
import { DatabaseConfig, IsolationLevels, DatabaseContract, QueryClientContract, DatabaseClientOptions, TransactionClientContract, ConnectionManagerContract } from '@ioc:Adonis/Lucid/Database';
import { RawBuilder } from './StaticBuilder/Raw';
import { prettyPrint } from '../Helpers/prettyPrint';
import { ModelQueryBuilder } from '../Orm/QueryBuilder';
import { InsertQueryBuilder } from './QueryBuilder/Insert';
import { ReferenceBuilder } from './StaticBuilder/Reference';
import { SimplePaginator } from './Paginator/SimplePaginator';
import { DatabaseQueryBuilder } from './QueryBuilder/Database';
/**
* Database class exposes the API to manage multiple connections and obtain an instance
* of query/transaction clients.
*/
export declare class Database extends Macroable implements DatabaseContract {
private config;
private logger;
private profiler;
private emitter;
/**
* Required by macroable
*/
protected static macros: {};
protected static getters: {};
/**
* Reference to self constructor. TypeScript sucks with "this.constructor"
* https://github.com/microsoft/TypeScript/issues/4586
*/
Database: typeof Database;
/**
* Reference to connections manager
*/
manager: ConnectionManagerContract;
/**
* Primary connection name
*/
primaryConnectionName: string;
/**
* Reference to query builders. We expose them, so that they can be
* extended from outside using macros.
*/
DatabaseQueryBuilder: typeof DatabaseQueryBuilder;
InsertQueryBuilder: typeof InsertQueryBuilder;
ModelQueryBuilder: typeof ModelQueryBuilder;
SimplePaginator: typeof SimplePaginator;
/**
* A store of global transactions
*/
connectionGlobalTransactions: Map<string, TransactionClientContract>;
hasHealthChecksEnabled: boolean;
prettyPrint: typeof prettyPrint;
constructor(config: DatabaseConfig, logger: LoggerContract, profiler: ProfilerContract, emitter: EmitterContract);
/**
* Validate config at runtime
*/
private validateConfig;
/**
* Compute whether health check is enabled or not after registering the connections.
* There are chances that all pre-registered connections are not using health
* checks but a dynamic connection is using it. We don't support that use case
* for now, since it complicates things a lot and forces us to register the
* health checker on demand.
*/
private findIfHealthChecksAreEnabled;
/**
* Registering all connections with the manager, so that we can fetch
* and connect with them whenver required.
*/
private registerConnections;
/**
* Returns the connection node from the connection manager
*/
getRawConnection(name: string): import("@ioc:Adonis/Lucid/Database").ConnectionNode | undefined;
/**
* Returns the query client for a given connection
*/
connection(connection?: string, options?: DatabaseClientOptions): QueryClientContract | TransactionClientContract;
/**
* Returns the knex query builder
*/
knexQuery(): import("knex").Knex.QueryBuilder<any, any>;
/**
* Returns the knex raw query builder
*/
knexRawQuery(sql: string, bindings?: any[]): import("knex").Knex.Raw<any>;
/**
* Returns query builder. Optionally one can define the mode as well
*/
query(options?: DatabaseClientOptions): import("@ioc:Adonis/Lucid/Database").DatabaseQueryBuilderContract<any>;
/**
* Returns insert query builder. Always has to be dual or write mode and
* hence it doesn't matter, since in both `dual` and `write` mode,
* the `write` connection is always used.
*/
insertQuery(options?: DatabaseClientOptions): import("@ioc:Adonis/Lucid/Database").InsertQueryBuilderContract<any[]>;
/**
* Returns a query builder instance for a given model.
*/
modelQuery(model: any, options?: DatabaseClientOptions): import("@ioc:Adonis/Lucid/Orm").ModelQueryBuilderContract<any, any>;
/**
* Returns an instance of raw query builder. Optionally one can
* defined the `read/write` mode in which to execute the
* query
*/
rawQuery(sql: string, bindings?: any, options?: DatabaseClientOptions): import("@ioc:Adonis/Lucid/Database").RawQueryBuilderContract<any>;
/**
* Returns an instance of raw builder. This raw builder queries
* cannot be executed. Use `rawQuery`, if you want to execute
* queries raw queries.
*/
raw(sql: string, bindings?: any): RawBuilder;
/**
* Returns reference builder.
*/
ref(reference: string): ReferenceBuilder;
/**
* Returns instance of a query builder and selects the table
*/
from(table: any): import("@ioc:Adonis/Lucid/Database").DatabaseQueryBuilderContract<any>;
/**
* Returns insert query builder and selects the table
*/
table(table: any): import("@ioc:Adonis/Lucid/Database").InsertQueryBuilderContract<any[]>;
/**
* Returns a transaction instance on the default
* connection
*/
transaction(callback?: {
isolationLevel?: IsolationLevels;
} | ((trx: TransactionClientContract) => Promise<any>), options?: {
isolationLevel?: IsolationLevels;
}): Promise<any>;
/**
* Invokes `manager.report`
*/
report(): Promise<import("@ioc:Adonis/Core/HealthCheck").HealthReportEntry & {
meta: import("@ioc:Adonis/Lucid/Database").ReportNode[];
}>;
/**
* Begin a new global transaction
*/
beginGlobalTransaction(connectionName?: string, options?: Omit<DatabaseClientOptions, 'mode'>): Promise<TransactionClientContract>;
/**
* Commit an existing global transaction
*/
commitGlobalTransaction(connectionName?: string): Promise<void>;
/**
* Rollback an existing global transaction
*/
rollbackGlobalTransaction(connectionName?: string): Promise<void>;
}
+378
View File
@@ -0,0 +1,378 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Database = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const macroable_1 = require("macroable");
const utils_1 = require("@poppinss/utils");
const QueryClient_1 = require("../QueryClient");
const Raw_1 = require("./StaticBuilder/Raw");
const prettyPrint_1 = require("../Helpers/prettyPrint");
const QueryBuilder_1 = require("../Orm/QueryBuilder");
const Manager_1 = require("../Connection/Manager");
const Insert_1 = require("./QueryBuilder/Insert");
const Reference_1 = require("./StaticBuilder/Reference");
const SimplePaginator_1 = require("./Paginator/SimplePaginator");
const Database_1 = require("./QueryBuilder/Database");
/**
* Database class exposes the API to manage multiple connections and obtain an instance
* of query/transaction clients.
*/
class Database extends macroable_1.Macroable {
constructor(config, logger, profiler, emitter) {
super();
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: config
});
Object.defineProperty(this, "logger", {
enumerable: true,
configurable: true,
writable: true,
value: logger
});
Object.defineProperty(this, "profiler", {
enumerable: true,
configurable: true,
writable: true,
value: profiler
});
Object.defineProperty(this, "emitter", {
enumerable: true,
configurable: true,
writable: true,
value: emitter
});
/**
* Reference to self constructor. TypeScript sucks with "this.constructor"
* https://github.com/microsoft/TypeScript/issues/4586
*/
Object.defineProperty(this, "Database", {
enumerable: true,
configurable: true,
writable: true,
value: Database
});
/**
* Reference to connections manager
*/
Object.defineProperty(this, "manager", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Primary connection name
*/
Object.defineProperty(this, "primaryConnectionName", {
enumerable: true,
configurable: true,
writable: true,
value: this.config.connection
});
/**
* Reference to query builders. We expose them, so that they can be
* extended from outside using macros.
*/
Object.defineProperty(this, "DatabaseQueryBuilder", {
enumerable: true,
configurable: true,
writable: true,
value: Database_1.DatabaseQueryBuilder
});
Object.defineProperty(this, "InsertQueryBuilder", {
enumerable: true,
configurable: true,
writable: true,
value: Insert_1.InsertQueryBuilder
});
Object.defineProperty(this, "ModelQueryBuilder", {
enumerable: true,
configurable: true,
writable: true,
value: QueryBuilder_1.ModelQueryBuilder
});
Object.defineProperty(this, "SimplePaginator", {
enumerable: true,
configurable: true,
writable: true,
value: SimplePaginator_1.SimplePaginator
});
/**
* A store of global transactions
*/
Object.defineProperty(this, "connectionGlobalTransactions", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
Object.defineProperty(this, "hasHealthChecksEnabled", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "prettyPrint", {
enumerable: true,
configurable: true,
writable: true,
value: prettyPrint_1.prettyPrint
});
this.validateConfig();
this.manager = new Manager_1.ConnectionManager(this.logger, this.emitter);
this.registerConnections();
this.findIfHealthChecksAreEnabled();
}
/**
* Validate config at runtime
*/
validateConfig() {
const validator = new utils_1.ManagerConfigValidator(this.config, 'database', 'config/database');
validator.validateDefault('connection');
validator.validateList('connections', 'connection');
}
/**
* Compute whether health check is enabled or not after registering the connections.
* There are chances that all pre-registered connections are not using health
* checks but a dynamic connection is using it. We don't support that use case
* for now, since it complicates things a lot and forces us to register the
* health checker on demand.
*/
findIfHealthChecksAreEnabled() {
for (let [, conn] of this.manager.connections) {
if (conn.config.healthCheck) {
this.hasHealthChecksEnabled = true;
break;
}
}
}
/**
* Registering all connections with the manager, so that we can fetch
* and connect with them whenver required.
*/
registerConnections() {
Object.keys(this.config.connections).forEach((name) => {
this.manager.add(name, this.config.connections[name]);
});
}
/**
* Returns the connection node from the connection manager
*/
getRawConnection(name) {
return this.manager.get(name);
}
/**
* Returns the query client for a given connection
*/
connection(connection = this.primaryConnectionName, options) {
options = options || {};
/**
* Use default profiler, when no profiler is defined when obtaining
* the query client for a given connection.
*/
if (!options.profiler) {
options.profiler = this.profiler;
}
/**
* Connect is noop when already connected
*/
this.manager.connect(connection);
/**
* Disallow modes other than `read` or `write`
*/
if (options.mode && !['read', 'write'].includes(options.mode)) {
throw new utils_1.Exception(`Invalid mode ${options.mode}. Must be read or write`);
}
/**
* Return the global transaction when it already exists.
*/
if (this.connectionGlobalTransactions.has(connection)) {
this.logger.trace({ connection }, 'using pre-existing global transaction connection');
const globalTransactionClient = this.connectionGlobalTransactions.get(connection);
return globalTransactionClient;
}
/**
* Fetching connection for the given name
*/
const rawConnection = this.getRawConnection(connection).connection;
/**
* Generating query client for a given connection and setting appropriate
* mode on it
*/
this.logger.trace({ connection }, 'creating query client in %s mode', [options.mode || 'dual']);
const queryClient = options.mode
? new QueryClient_1.QueryClient(options.mode, rawConnection, this.emitter)
: new QueryClient_1.QueryClient('dual', rawConnection, this.emitter);
/**
* Passing profiler to the query client for profiling queries
*/
queryClient.profiler = options.profiler;
return queryClient;
}
/**
* Returns the knex query builder
*/
knexQuery() {
return this.connection(this.primaryConnectionName).knexQuery();
}
/**
* Returns the knex raw query builder
*/
knexRawQuery(sql, bindings) {
return this.connection(this.primaryConnectionName).knexRawQuery(sql, bindings);
}
/**
* Returns query builder. Optionally one can define the mode as well
*/
query(options) {
return this.connection(this.primaryConnectionName, options).query();
}
/**
* Returns insert query builder. Always has to be dual or write mode and
* hence it doesn't matter, since in both `dual` and `write` mode,
* the `write` connection is always used.
*/
insertQuery(options) {
return this.connection(this.primaryConnectionName, options).insertQuery();
}
/**
* Returns a query builder instance for a given model.
*/
modelQuery(model, options) {
return this.connection(this.primaryConnectionName, options).modelQuery(model);
}
/**
* Returns an instance of raw query builder. Optionally one can
* defined the `read/write` mode in which to execute the
* query
*/
rawQuery(sql, bindings, options) {
return this.connection(this.primaryConnectionName, options).rawQuery(sql, bindings);
}
/**
* Returns an instance of raw builder. This raw builder queries
* cannot be executed. Use `rawQuery`, if you want to execute
* queries raw queries.
*/
raw(sql, bindings) {
return new Raw_1.RawBuilder(sql, bindings);
}
/**
* Returns reference builder.
*/
ref(reference) {
return new Reference_1.ReferenceBuilder(reference, this.connection().getReadClient().client);
}
/**
* Returns instance of a query builder and selects the table
*/
from(table) {
return this.connection().from(table);
}
/**
* Returns insert query builder and selects the table
*/
table(table) {
return this.connection().table(table);
}
/**
* Returns a transaction instance on the default
* connection
*/
transaction(callback, options) {
const client = this.connection();
return typeof callback === 'function'
? client.transaction(callback, options)
: client.transaction(callback);
}
/**
* Invokes `manager.report`
*/
report() {
return this.manager.report();
}
/**
* Begin a new global transaction
*/
async beginGlobalTransaction(connectionName, options) {
connectionName = connectionName || this.primaryConnectionName;
/**
* Return global transaction as it is
*/
const globalTrx = this.connectionGlobalTransactions.get(connectionName);
if (globalTrx) {
return globalTrx;
}
/**
* Create a new transaction and store a reference to it
*/
const trx = await this.connection(connectionName, options).transaction();
this.connectionGlobalTransactions.set(trx.connectionName, trx);
/**
* Listen for events to drop the reference when transaction
* is over
*/
trx.on('commit', ($trx) => {
this.connectionGlobalTransactions.delete($trx.connectionName);
});
trx.on('rollback', ($trx) => {
this.connectionGlobalTransactions.delete($trx.connectionName);
});
return trx;
}
/**
* Commit an existing global transaction
*/
async commitGlobalTransaction(connectionName) {
connectionName = connectionName || this.primaryConnectionName;
const trx = this.connectionGlobalTransactions.get(connectionName);
if (!trx) {
throw new utils_1.Exception([
'Cannot commit a non-existing global transaction.',
' Make sure you are not calling "commitGlobalTransaction" twice',
].join(''));
}
await trx.commit();
}
/**
* Rollback an existing global transaction
*/
async rollbackGlobalTransaction(connectionName) {
connectionName = connectionName || this.primaryConnectionName;
const trx = this.connectionGlobalTransactions.get(connectionName);
if (!trx) {
throw new utils_1.Exception([
'Cannot rollback a non-existing global transaction.',
' Make sure you are not calling "commitGlobalTransaction" twice',
].join(''));
}
await trx.rollback();
}
}
exports.Database = Database;
/**
* Required by macroable
*/
Object.defineProperty(Database, "macros", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(Database, "getters", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
+6
View File
@@ -0,0 +1,6 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { DialectContract } from '@ioc:Adonis/Lucid/Database';
import { BaseSqliteDialect } from './SqliteBase';
export declare class BetterSqliteDialect extends BaseSqliteDialect implements DialectContract {
readonly name = "better-sqlite3";
}
+24
View File
@@ -0,0 +1,24 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BetterSqliteDialect = void 0;
const SqliteBase_1 = require("./SqliteBase");
class BetterSqliteDialect extends SqliteBase_1.BaseSqliteDialect {
constructor() {
super(...arguments);
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: 'better-sqlite3'
});
}
}
exports.BetterSqliteDialect = BetterSqliteDialect;
+44
View File
@@ -0,0 +1,44 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { DialectContract, MssqlConfig, QueryClientContract } from '@ioc:Adonis/Lucid/Database';
export declare class MssqlDialect implements DialectContract {
private client;
private config;
readonly name = "mssql";
readonly supportsAdvisoryLocks = false;
readonly supportsViews = false;
readonly supportsTypes = false;
readonly supportsReturningStatement = true;
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
readonly version: any;
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
readonly dateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ";
constructor(client: QueryClientContract, config: MssqlConfig);
/**
* Returns an array of table names
*/
getAllTables(): Promise<any[]>;
/**
* Truncate mssql table. Disabling foreign key constriants alone is
* not enough for SQL server.
*
* One has to drop all FK constraints and then re-create them, and
* this all is too much work
*/
truncate(table: string, _: boolean): Promise<void>;
/**
* Drop all tables inside the database
*/
dropAllTables(): Promise<void>;
getAllViews(): Promise<string[]>;
getAllTypes(): Promise<string[]>;
dropAllViews(): Promise<void>;
dropAllTypes(): Promise<void>;
getAdvisoryLock(): Promise<boolean>;
releaseAdvisoryLock(): Promise<boolean>;
}
+143
View File
@@ -0,0 +1,143 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MssqlDialect = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const Raw_1 = require("../Database/StaticBuilder/Raw");
class MssqlDialect {
constructor(client, config) {
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: config
});
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: 'mssql'
});
Object.defineProperty(this, "supportsAdvisoryLocks", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "supportsViews", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "supportsTypes", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "supportsReturningStatement", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
Object.defineProperty(this, "version", {
enumerable: true,
configurable: true,
writable: true,
value: this.client.getReadClient()['context']['client'].version
});
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
Object.defineProperty(this, "dateTimeFormat", {
enumerable: true,
configurable: true,
writable: true,
value: "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"
});
}
/**
* Returns an array of table names
*/
async getAllTables() {
const tables = await this.client
.query()
.from('information_schema.tables')
.select('table_name as table_name')
.where('table_type', 'BASE TABLE')
.where('table_catalog', new Raw_1.RawBuilder('DB_NAME()'))
.whereNot('table_name', 'like', 'spt_%')
.andWhereNot('table_name', 'MSreplication_options')
.orderBy('table_name', 'asc');
return tables.map(({ table_name }) => table_name);
}
/**
* Truncate mssql table. Disabling foreign key constriants alone is
* not enough for SQL server.
*
* One has to drop all FK constraints and then re-create them, and
* this all is too much work
*/
async truncate(table, _) {
return this.client.knexQuery().table(table).truncate();
}
/**
* Drop all tables inside the database
*/
async dropAllTables() {
await this.client.rawQuery(`
DECLARE @sql NVARCHAR(MAX) = N'';
SELECT @sql += 'ALTER TABLE '
+ QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' + + QUOTENAME(OBJECT_NAME(parent_object_id))
+ ' DROP CONSTRAINT ' + QUOTENAME(name) + ';'
FROM sys.foreign_keys;
EXEC sp_executesql @sql;
`);
const ignoredTables = (this.config.wipe?.ignoreTables || [])
.map((table) => `"${table}"`)
.join(', ');
await this.client.rawQuery(`
EXEC sp_MSforeachtable 'DROP TABLE \\?',
@whereand='AND o.Name NOT IN (${ignoredTables || '""'})'
`);
}
async getAllViews() {
throw new Error('"getAllViews" method not implemented is not implemented for mssql. Create a PR to add the feature');
}
async getAllTypes() {
throw new Error('"getAllTypes" method not implemented is not implemented for mssql. Create a PR to add the feature');
}
async dropAllViews() {
throw new Error('"dropAllViews" method not implemented is not implemented for mssql. Create a PR to add the feature');
}
async dropAllTypes() {
throw new Error('"dropAllTypes" method not implemented is not implemented for mssql. Create a PR to add the feature');
}
getAdvisoryLock() {
throw new Error('Support for advisory locks is not implemented for mssql. Create a PR to add the feature');
}
releaseAdvisoryLock() {
throw new Error('Support for advisory locks is not implemented for mssql. Create a PR to add the feature');
}
}
exports.MssqlDialect = MssqlDialect;
+59
View File
@@ -0,0 +1,59 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { DialectContract, MysqlConfig, QueryClientContract } from '@ioc:Adonis/Lucid/Database';
export declare class MysqlDialect implements DialectContract {
private client;
private config;
readonly name = "mysql";
readonly supportsAdvisoryLocks = true;
readonly supportsViews = true;
readonly supportsTypes = false;
readonly supportsReturningStatement = false;
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
readonly version: any;
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
readonly dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
constructor(client: QueryClientContract, config: MysqlConfig);
/**
* Truncate mysql table with option to cascade
*/
truncate(table: string, cascade?: boolean): Promise<void>;
/**
* Returns an array of table names
*/
getAllTables(): Promise<string[]>;
/**
* Returns an array of all views names
*/
getAllViews(): Promise<string[]>;
/**
* Returns an array of all types names
*/
getAllTypes(): Promise<string[]>;
/**
* Drop all tables inside the database
*/
dropAllTables(): Promise<void>;
/**
* Drop all views inside the database
*/
dropAllViews(): Promise<void>;
/**
* Drop all custom types inside the database
*/
dropAllTypes(): Promise<void>;
/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
getAdvisoryLock(key: string, timeout?: number): Promise<boolean>;
/**
* Releases the advisory lock
*/
releaseAdvisoryLock(key: string): Promise<boolean>;
}
+194
View File
@@ -0,0 +1,194 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MysqlDialect = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const Raw_1 = require("../Database/StaticBuilder/Raw");
class MysqlDialect {
constructor(client, config) {
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: config
});
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: 'mysql'
});
Object.defineProperty(this, "supportsAdvisoryLocks", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "supportsViews", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "supportsTypes", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "supportsReturningStatement", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
Object.defineProperty(this, "version", {
enumerable: true,
configurable: true,
writable: true,
value: this.client.getReadClient()['context']['client'].version
});
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
Object.defineProperty(this, "dateTimeFormat", {
enumerable: true,
configurable: true,
writable: true,
value: 'yyyy-MM-dd HH:mm:ss'
});
}
/**
* Truncate mysql table with option to cascade
*/
async truncate(table, cascade = false) {
if (!cascade) {
return this.client.knexQuery().table(table).truncate();
}
/**
* Cascade and truncate
*/
const trx = await this.client.transaction();
try {
await trx.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
await trx.knexQuery().table(table).truncate();
await trx.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
await trx.commit();
}
catch (error) {
await trx.rollback();
throw error;
}
}
/**
* Returns an array of table names
*/
async getAllTables() {
const tables = await this.client
.query()
.from('information_schema.tables')
.select('table_name as table_name')
.where('TABLE_TYPE', 'BASE TABLE')
.where('table_schema', new Raw_1.RawBuilder('database()'))
.orderBy('table_name', 'asc');
return tables.map(({ table_name }) => table_name);
}
/**
* Returns an array of all views names
*/
async getAllViews() {
const tables = await this.client
.query()
.from('information_schema.tables')
.select('table_name as table_name')
.where('TABLE_TYPE', 'VIEW')
.where('table_schema', new Raw_1.RawBuilder('database()'))
.orderBy('table_name', 'asc');
return tables.map(({ table_name }) => table_name);
}
/**
* Returns an array of all types names
*/
async getAllTypes() {
throw new Error("MySQL doesn't support types");
}
/**
* Drop all tables inside the database
*/
async dropAllTables() {
let tables = await this.getAllTables();
/**
* Filter out tables that are not allowed to be dropped
*/
tables = tables.filter((table) => !(this.config.wipe?.ignoreTables || []).includes(table));
/**
* Add backquote around table names to avoid syntax errors
* in case of a table name with a reserved keyword
*/
tables = tables.map((table) => '`' + table + '`');
if (!tables.length) {
return;
}
/**
* Cascade and truncate
*/
const trx = await this.client.transaction();
try {
await trx.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
await trx.rawQuery(`DROP TABLE ${tables.join(',')};`);
await trx.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
await trx.commit();
}
catch (error) {
await trx.rollback();
throw error;
}
}
/**
* Drop all views inside the database
*/
async dropAllViews() {
const views = await this.getAllViews();
return this.client.rawQuery(`DROP VIEW ${views.join(',')};`);
}
/**
* Drop all custom types inside the database
*/
async dropAllTypes() {
throw new Error("MySQL doesn't support types");
}
/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
async getAdvisoryLock(key, timeout = 0) {
const response = await this.client.rawQuery(`SELECT GET_LOCK('${key}', ${timeout}) as lock_status;`);
return response[0] && response[0][0] && response[0][0].lock_status === 1;
}
/**
* Releases the advisory lock
*/
async releaseAdvisoryLock(key) {
const response = await this.client.rawQuery(`SELECT RELEASE_LOCK('${key}') as lock_status;`);
return response[0] && response[0][0] && response[0][0].lock_status === 1;
}
}
exports.MysqlDialect = MysqlDialect;
+39
View File
@@ -0,0 +1,39 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { DialectContract, QueryClientContract } from '@ioc:Adonis/Lucid/Database';
export declare class OracleDialect implements DialectContract {
private client;
readonly name = "oracledb";
readonly supportsAdvisoryLocks = false;
readonly supportsViews = false;
readonly supportsTypes = false;
readonly supportsReturningStatement = true;
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
readonly version: any;
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
readonly dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
constructor(client: QueryClientContract);
/**
* Not implemented yet
*/
getAllTables(): Promise<any>;
/**
* Truncate pg table with option to cascade and restart identity
*/
truncate(table: string, cascade?: boolean): Promise<any>;
/**
* Not implemented yet
*/
dropAllTables(): Promise<void>;
getAllViews(): Promise<string[]>;
getAllTypes(): Promise<string[]>;
dropAllViews(): Promise<void>;
dropAllTypes(): Promise<void>;
getAdvisoryLock(): Promise<boolean>;
releaseAdvisoryLock(): Promise<boolean>;
}
+110
View File
@@ -0,0 +1,110 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.OracleDialect = void 0;
class OracleDialect {
constructor(client) {
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: 'oracledb'
});
Object.defineProperty(this, "supportsAdvisoryLocks", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "supportsViews", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "supportsTypes", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "supportsReturningStatement", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
Object.defineProperty(this, "version", {
enumerable: true,
configurable: true,
writable: true,
value: this.client.getReadClient()['context']['client'].version
});
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
Object.defineProperty(this, "dateTimeFormat", {
enumerable: true,
configurable: true,
writable: true,
value: 'yyyy-MM-dd HH:mm:ss'
});
}
/**
* Not implemented yet
*/
async getAllTables() {
throw new Error('"getAllTables" method is not implemented for oracledb. Create a PR to add the feature');
}
/**
* Truncate pg table with option to cascade and restart identity
*/
async truncate(table, cascade = false) {
return cascade
? this.client.rawQuery(`TRUNCATE ${table} CASCADE;`)
: this.client.rawQuery(`TRUNCATE ${table};`);
}
/**
* Not implemented yet
*/
async dropAllTables() {
throw new Error('"dropAllTables" method is not implemented for oracledb. Create a PR to add the feature');
}
async getAllViews() {
throw new Error('"getAllViews" method is not implemented for oracledb. Create a PR to add the feature.');
}
async getAllTypes() {
throw new Error('"getAllTypes" method is not implemented for oracledb. Create a PR to add the feature.');
}
async dropAllViews() {
throw new Error('"dropAllViews" method is not implemented for oracledb. Create a PR to add the feature.');
}
async dropAllTypes() {
throw new Error('"dropAllTypes" method is not implemented for oracledb. Create a PR to add the feature.');
}
getAdvisoryLock() {
throw new Error('Support for advisory locks is not implemented for oracledb. Create a PR to add the feature');
}
releaseAdvisoryLock() {
throw new Error('Support for advisory locks is not implemented for oracledb. Create a PR to add the feature');
}
}
exports.OracleDialect = OracleDialect;
+59
View File
@@ -0,0 +1,59 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { DialectContract, PostgreConfig, QueryClientContract } from '@ioc:Adonis/Lucid/Database';
export declare class PgDialect implements DialectContract {
private client;
private config;
readonly name = "postgres";
readonly supportsAdvisoryLocks = true;
readonly supportsViews = true;
readonly supportsTypes = true;
readonly supportsReturningStatement = true;
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
readonly version: any;
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
readonly dateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ";
constructor(client: QueryClientContract, config: PostgreConfig);
/**
* Returns an array of table names for one or many schemas.
*/
getAllTables(schemas: string[]): Promise<any[]>;
/**
* Returns an array of all views names for one or many schemas
*/
getAllViews(schemas: string[]): Promise<any[]>;
/**
* Returns an array of all types names
*/
getAllTypes(_schemas: string[]): Promise<any[]>;
/**
* Truncate pg table with option to cascade and restart identity
*/
truncate(table: string, cascade?: boolean): Promise<any>;
/**
* Drop all tables inside the database
*/
dropAllTables(schemas: string[]): Promise<void>;
/**
* Drop all views inside the database
*/
dropAllViews(schemas: string[]): Promise<void>;
/**
* Drop all types inside the database
*/
dropAllTypes(schemas: string[]): Promise<void>;
/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
getAdvisoryLock(key: string): Promise<boolean>;
/**
* Releases the advisory lock
*/
releaseAdvisoryLock(key: string): Promise<boolean>;
}
+169
View File
@@ -0,0 +1,169 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PgDialect = void 0;
class PgDialect {
constructor(client, config) {
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: config
});
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: 'postgres'
});
Object.defineProperty(this, "supportsAdvisoryLocks", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "supportsViews", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "supportsTypes", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "supportsReturningStatement", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
Object.defineProperty(this, "version", {
enumerable: true,
configurable: true,
writable: true,
value: this.client.getReadClient()['context']['client'].version
});
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
Object.defineProperty(this, "dateTimeFormat", {
enumerable: true,
configurable: true,
writable: true,
value: "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"
});
}
/**
* Returns an array of table names for one or many schemas.
*/
async getAllTables(schemas) {
const tables = await this.client
.query()
.from('pg_catalog.pg_tables')
.select('tablename as table_name')
.whereIn('schemaname', schemas)
.orderBy('tablename', 'asc');
return tables.map(({ table_name }) => table_name);
}
/**
* Returns an array of all views names for one or many schemas
*/
async getAllViews(schemas) {
const views = await this.client
.query()
.from('pg_catalog.pg_views')
.select('viewname as view_name')
.whereIn('schemaname', schemas)
.orderBy('viewname', 'asc');
return views.map(({ view_name }) => view_name);
}
/**
* Returns an array of all types names
*/
async getAllTypes(_schemas) {
const types = await this.client
.query()
.select('pg_type.typname')
.distinct()
.from('pg_type')
.innerJoin('pg_enum', 'pg_enum.enumtypid', 'pg_type.oid');
return types.map(({ typname }) => typname);
}
/**
* Truncate pg table with option to cascade and restart identity
*/
async truncate(table, cascade = false) {
return cascade
? this.client.rawQuery(`TRUNCATE "${table}" RESTART IDENTITY CASCADE;`)
: this.client.rawQuery(`TRUNCATE "${table}";`);
}
/**
* Drop all tables inside the database
*/
async dropAllTables(schemas) {
let tables = await this.getAllTables(schemas);
/**
* Filter out tables that are not allowed to be dropped
*/
tables = tables.filter((table) => !(this.config.wipe?.ignoreTables || ['spatial_ref_sys']).includes(table));
if (!tables.length) {
return;
}
await this.client.rawQuery(`DROP TABLE "${tables.join('", "')}" CASCADE;`);
}
/**
* Drop all views inside the database
*/
async dropAllViews(schemas) {
const views = await this.getAllViews(schemas);
if (!views.length)
return;
await this.client.rawQuery(`DROP VIEW "${views.join('", "')}" CASCADE;`);
}
/**
* Drop all types inside the database
*/
async dropAllTypes(schemas) {
const types = await this.getAllTypes(schemas);
if (!types.length)
return;
await this.client.rawQuery(`DROP TYPE "${types.join('", "')}" CASCADE;`);
}
/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
async getAdvisoryLock(key) {
const response = await this.client.rawQuery(`SELECT PG_TRY_ADVISORY_LOCK('${key}') as lock_status;`);
return response.rows[0] && response.rows[0].lock_status === true;
}
/**
* Releases the advisory lock
*/
async releaseAdvisoryLock(key) {
const response = await this.client.rawQuery(`SELECT PG_ADVISORY_UNLOCK('${key}') as lock_status;`);
return response.rows[0] && response.rows[0].lock_status === true;
}
}
exports.PgDialect = PgDialect;
+72
View File
@@ -0,0 +1,72 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { DialectContract, PostgreConfig, QueryClientContract } from '@ioc:Adonis/Lucid/Database';
export declare class RedshiftDialect implements DialectContract {
private client;
private config;
readonly name = "redshift";
readonly supportsAdvisoryLocks = false;
readonly supportsViews = true;
readonly supportsTypes = true;
readonly supportsReturningStatement = true;
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
readonly version: any;
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
readonly dateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ";
constructor(client: QueryClientContract, config: PostgreConfig);
/**
* Returns an array of table names for one or many schemas.
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
getAllTables(schemas: string[]): Promise<any[]>;
/**
* Returns an array of all views names for one or many schemas
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
getAllViews(schemas: string[]): Promise<any[]>;
/**
* Returns an array of all types names
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
getAllTypes(_schemas: string[]): Promise<any[]>;
/**
* Truncate redshift table with option to cascade and restart identity.
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
truncate(table: string, cascade?: boolean): Promise<any>;
/**
* Drop all tables inside the database
*/
dropAllTables(schemas: string[]): Promise<void>;
/**
* Drop all views inside the database
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
dropAllViews(schemas: string[]): Promise<void>;
/**
* Drop all types inside the database
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
dropAllTypes(schemas: string[]): Promise<void>;
/**
* Redshift doesn't support advisory locks. Learn more:
* https://tableplus.com/blog/2018/10/redshift-vs-postgres-database-comparison.html
*/
getAdvisoryLock(): Promise<boolean>;
/**
* Redshift doesn't support advisory locks. Learn more:
* https://tableplus.com/blog/2018/10/redshift-vs-postgres-database-comparison.html
*/
releaseAdvisoryLock(): Promise<boolean>;
}
+180
View File
@@ -0,0 +1,180 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedshiftDialect = void 0;
class RedshiftDialect {
constructor(client, config) {
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: config
});
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: 'redshift'
});
Object.defineProperty(this, "supportsAdvisoryLocks", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "supportsViews", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "supportsTypes", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "supportsReturningStatement", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
Object.defineProperty(this, "version", {
enumerable: true,
configurable: true,
writable: true,
value: this.client.getReadClient()['context']['client'].version
});
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
Object.defineProperty(this, "dateTimeFormat", {
enumerable: true,
configurable: true,
writable: true,
value: "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"
});
}
/**
* Returns an array of table names for one or many schemas.
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
async getAllTables(schemas) {
const tables = await this.client
.query()
.from('pg_catalog.pg_tables')
.select('tablename as table_name')
.whereIn('schemaname', schemas)
.orderBy('tablename', 'asc');
return tables.map(({ table_name }) => table_name);
}
/**
* Returns an array of all views names for one or many schemas
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
async getAllViews(schemas) {
const views = await this.client
.query()
.from('pg_catalog.pg_views')
.select('viewname as view_name')
.whereIn('schemaname', schemas)
.orderBy('viewname', 'asc');
return views.map(({ view_name }) => view_name);
}
/**
* Returns an array of all types names
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
async getAllTypes(_schemas) {
const types = await this.client
.query()
.select('pg_type.typname')
.distinct()
.from('pg_type')
.innerJoin('pg_enum', 'pg_enum.enumtypid', 'pg_type.oid');
return types.map(({ typname }) => typname);
}
/**
* Truncate redshift table with option to cascade and restart identity.
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
async truncate(table, cascade = false) {
return cascade
? this.client.rawQuery(`TRUNCATE "${table}" RESTART IDENTITY CASCADE;`)
: this.client.rawQuery(`TRUNCATE "${table}";`);
}
/**
* Drop all tables inside the database
*/
async dropAllTables(schemas) {
let tables = await this.getAllTables(schemas);
/**
* Filter out tables that are not allowed to be dropped
*/
tables = tables.filter((table) => !(this.config.wipe?.ignoreTables || ['spatial_ref_sys']).includes(table));
if (!tables.length) {
return;
}
await this.client.rawQuery(`DROP table ${tables.join(',')} CASCADE;`);
}
/**
* Drop all views inside the database
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
async dropAllViews(schemas) {
const views = await this.getAllViews(schemas);
if (!views.length)
return;
await this.client.rawQuery(`DROP view ${views.join(',')} CASCADE;`);
}
/**
* Drop all types inside the database
*
* NOTE: ASSUMING FEATURE PARITY WITH POSTGRESQL HERE (NOT TESTED)
*/
async dropAllTypes(schemas) {
const types = await this.getAllTypes(schemas);
if (!types.length)
return;
await this.client.rawQuery(`DROP type ${types.join(',')};`);
}
/**
* Redshift doesn't support advisory locks. Learn more:
* https://tableplus.com/blog/2018/10/redshift-vs-postgres-database-comparison.html
*/
getAdvisoryLock() {
throw new Error("Redshift doesn't support advisory locks");
}
/**
* Redshift doesn't support advisory locks. Learn more:
* https://tableplus.com/blog/2018/10/redshift-vs-postgres-database-comparison.html
*/
releaseAdvisoryLock() {
throw new Error("Redshift doesn't support advisory locks");
}
}
exports.RedshiftDialect = RedshiftDialect;
+6
View File
@@ -0,0 +1,6 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { DialectContract } from '@ioc:Adonis/Lucid/Database';
import { BaseSqliteDialect } from './SqliteBase';
export declare class SqliteDialect extends BaseSqliteDialect implements DialectContract {
readonly name = "sqlite3";
}
+24
View File
@@ -0,0 +1,24 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqliteDialect = void 0;
const SqliteBase_1 = require("./SqliteBase");
class SqliteDialect extends SqliteBase_1.BaseSqliteDialect {
constructor() {
super(...arguments);
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: 'sqlite3'
});
}
}
exports.SqliteDialect = SqliteDialect;
+59
View File
@@ -0,0 +1,59 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { DialectContract, QueryClientContract, SqliteConfig } from '@ioc:Adonis/Lucid/Database';
export declare abstract class BaseSqliteDialect implements DialectContract {
private client;
private config;
abstract readonly name: 'sqlite3' | 'better-sqlite3';
readonly supportsAdvisoryLocks = false;
readonly supportsViews = true;
readonly supportsTypes = false;
readonly supportsReturningStatement = false;
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
readonly version: any;
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
readonly dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
constructor(client: QueryClientContract, config: SqliteConfig);
/**
* Returns an array of table names
*/
getAllTables(): Promise<any[]>;
/**
* Returns an array of all views names
*/
getAllViews(): Promise<string[]>;
/**
* Returns an array of all types names
*/
getAllTypes(): Promise<string[]>;
/**
* Truncate SQLITE tables
*/
truncate(table: string): Promise<void>;
/**
* Drop all tables inside the database
*/
dropAllTables(): Promise<void>;
/**
* Drop all views inside the database
*/
dropAllViews(): Promise<void>;
/**
* Drop all custom types inside the database
*/
dropAllTypes(): Promise<void>;
/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
getAdvisoryLock(): Promise<boolean>;
/**
* Releases the advisory lock
*/
releaseAdvisoryLock(): Promise<boolean>;
}
+152
View File
@@ -0,0 +1,152 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseSqliteDialect = void 0;
class BaseSqliteDialect {
constructor(client, config) {
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: config
});
Object.defineProperty(this, "supportsAdvisoryLocks", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "supportsViews", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "supportsTypes", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "supportsReturningStatement", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
Object.defineProperty(this, "version", {
enumerable: true,
configurable: true,
writable: true,
value: this.client.getReadClient()['context']['client'].version
});
/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
Object.defineProperty(this, "dateTimeFormat", {
enumerable: true,
configurable: true,
writable: true,
value: 'yyyy-MM-dd HH:mm:ss'
});
}
/**
* Returns an array of table names
*/
async getAllTables() {
const tables = await this.client
.query()
.from('sqlite_master')
.select('name as table_name')
.where('type', 'table')
.whereNot('name', 'like', 'sqlite_%')
.orderBy('name', 'asc');
return tables.map(({ table_name }) => table_name);
}
/**
* Returns an array of all views names
*/
async getAllViews() {
const tables = await this.client
.query()
.from('sqlite_master')
.select('name as table_name')
.where('type', 'view')
.whereNot('name', 'like', 'sqlite_%')
.orderBy('name', 'asc');
return tables.map(({ table_name }) => table_name);
}
/**
* Returns an array of all types names
*/
async getAllTypes() {
throw new Error("Sqlite doesn't support types");
}
/**
* Truncate SQLITE tables
*/
async truncate(table) {
return this.client.knexQuery().table(table).truncate();
}
/**
* Drop all tables inside the database
*/
async dropAllTables() {
await this.client.rawQuery('PRAGMA writable_schema = 1;');
await this.client
.knexQuery()
.delete()
.from('sqlite_master')
.whereIn('type', ['table', 'index', 'trigger'])
.whereNotIn('name', this.config.wipe?.ignoreTables || []);
await this.client.rawQuery('PRAGMA writable_schema = 0;');
await this.client.rawQuery('VACUUM;');
}
/**
* Drop all views inside the database
*/
async dropAllViews() {
await this.client.rawQuery('PRAGMA writable_schema = 1;');
await this.client.rawQuery(`delete from sqlite_schema where type = 'view';`);
await this.client.rawQuery('PRAGMA writable_schema = 0;');
await this.client.rawQuery('VACUUM;');
}
/**
* Drop all custom types inside the database
*/
async dropAllTypes() {
throw new Error("Sqlite doesn't support types");
}
/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
getAdvisoryLock() {
throw new Error("Sqlite doesn't support advisory locks");
}
/**
* Releases the advisory lock
*/
releaseAdvisoryLock() {
throw new Error("Sqlite doesn't support advisory locks");
}
}
exports.BaseSqliteDialect = BaseSqliteDialect;
+4
View File
@@ -0,0 +1,4 @@
import { DialectContract, QueryClientContract, SharedConfigNode } from '@ioc:Adonis/Lucid/Database';
export declare const dialects: {
[key: string]: new (client: QueryClientContract, config: SharedConfigNode) => DialectContract;
};
+28
View File
@@ -0,0 +1,28 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.dialects = void 0;
const Pg_1 = require("./Pg");
const Mysql_1 = require("./Mysql");
const Mssql_1 = require("./Mssql");
const Sqlite_1 = require("./Sqlite");
const Oracle_1 = require("./Oracle");
const Redshift_1 = require("./Redshift");
const BetterSqlite_1 = require("./BetterSqlite");
exports.dialects = {
'mssql': Mssql_1.MssqlDialect,
'mysql': Mysql_1.MysqlDialect,
'mysql2': Mysql_1.MysqlDialect,
'oracledb': Oracle_1.OracleDialect,
'postgres': Pg_1.PgDialect,
'redshift': Redshift_1.RedshiftDialect,
'sqlite3': Sqlite_1.SqliteDialect,
'better-sqlite3': BetterSqlite_1.BetterSqliteDialect,
};
+181
View File
@@ -0,0 +1,181 @@
import { QueryClientContract } from '@ioc:Adonis/Lucid/Database';
import { LucidRow, LucidModel, ModelAdapterOptions, ModelObject } from '@ioc:Adonis/Lucid/Orm';
import { FactoryModelContract, FactoryContextContract, FactoryBuilderContract, FactoryRelationContract } from '@ioc:Adonis/Lucid/Factory';
import { FactoryModel } from './FactoryModel';
/**
* Factory builder exposes the API to create/persist factory model instances.
*/
export declare class FactoryBuilder implements FactoryBuilderContract<FactoryModelContract<LucidModel>> {
factory: FactoryModel<LucidModel>;
private options?;
/**
* The relationship via which this factory builder was
* created
*/ private viaRelation?;
/**
* Relationships to setup. Do note: It is possible to load one relationship
* twice. A practical use case is to apply different states. For example:
*
* Make user with "3 active posts" and "2 draft posts"
*/
private withRelations;
/**
* An array of callbacks to execute before persisting the model instance
*/
private tapCallbacks;
/**
* Belongs to relationships are treated different, since they are
* persisted before the parent model
*/
private withBelongsToRelations;
/**
* The current index. Updated by `makeMany` and `createMany`
*/
private currentIndex;
/**
* Custom attributes to pass to model merge method
*/
private attributes;
/**
* Custom attributes to pass to relationship merge methods
*/
private recursiveAttributes;
/**
* States to apply. One state can be applied only once and hence
* a set is used.
*/
private appliedStates;
/**
* Custom context passed using `useCtx` method. It not defined, we will
* create one inline inside `create` and `make` methods
*/
private ctx?;
/**
* Pivot attributes for a many to many relationship
*/
private attributesForPivotTable?;
/**
* Instead of relying on the `FactoryModelContract`, we rely on the
* `FactoryModel`, since it exposes certain API's required for
* the runtime operations and those API's are not exposed
* on the interface to keep the API clean
*/
constructor(factory: FactoryModel<LucidModel>, options?: ModelAdapterOptions | undefined,
/**
* The relationship via which this factory builder was
* created
*/ viaRelation?: FactoryRelationContract | undefined);
/**
* Access the parent relationship for which the model instance
* is created
*/
get parent(): LucidRow | undefined;
/**
* Returns factory state
*/
private getCtx;
/**
* Returns attributes to merge for a given index
*/
private getMergeAttributes;
/**
* Returns a new model instance with filled attributes
*/
private getModelInstance;
/**
* Apply states by invoking state callback
*/
private applyStates;
/**
* Invoke tap callbacks
*/
private invokeTapCallback;
/**
* Compile factory by instantiating model instance, applying merge
* attributes, apply state
*/
private compile;
/**
* Makes relationship instances. Call [[createRelation]] to
* also persist them.
*/
private makeRelations;
/**
* Makes and persists relationship instances
*/
private createRelations;
/**
* Persist the model instance along with its relationships
*/
private persistModelInstance;
/**
* Define custom database connection
*/
connection(connection: string): this;
/**
* Define custom query client
*/
client(client: QueryClientContract): this;
/**
* Define custom context. Usually called by the relationships
* to share the parent context with relationship factory
*/
useCtx(ctx: FactoryContextContract): this;
/**
* Load relationship
*/
with(name: string, count?: number, callback?: (factory: never) => void): this;
/**
* Apply one or more states. Multiple calls to apply a single
* state will be ignored
*/
apply(...states: string[]): this;
/**
* Fill custom set of attributes. They are passed down to the newUp
* method of the factory
*/
merge(attributes: any): this;
/**
* Merge custom set of attributes with the correct factory builder
* model and all of its relationships as well
*/
mergeRecursive(attributes: any): this;
/**
* Define pivot attributes when persisting a many to many
* relationship. Results in a noop, when not called
* for a many to many relationship
*/
pivotAttributes(attributes: ModelObject | ModelObject[]): this;
/**
* Tap into the persistence layer of factory builder. Allows one
* to modify the model instance just before it is persisted
* to the database
*/
tap(callback: (row: LucidRow, state: FactoryContextContract, builder: this) => void): this;
/**
* Make model instance. Relationships are not processed with the make function.
*/
make(): Promise<LucidRow>;
/**
* Create many of the factory model instances
*/
makeMany(count: number): Promise<LucidRow[]>;
/**
* Returns a model instance without persisting it to the database.
* Relationships are still loaded and states are also applied.
*/
makeStubbed(): Promise<LucidRow>;
/**
* Create many of model factory instances
*/
makeStubbedMany(count: number): Promise<LucidRow[]>;
/**
* Similar to make, but also persists the model instance to the
* database.
*/
create(): Promise<LucidRow>;
/**
* Create and persist many of factory model instances
*/
createMany(count: number): Promise<LucidRow[]>;
}
+483
View File
@@ -0,0 +1,483 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FactoryBuilder = void 0;
const FactoryContext_1 = require("./FactoryContext");
/**
* Factory builder exposes the API to create/persist factory model instances.
*/
class FactoryBuilder {
/**
* Instead of relying on the `FactoryModelContract`, we rely on the
* `FactoryModel`, since it exposes certain API's required for
* the runtime operations and those API's are not exposed
* on the interface to keep the API clean
*/
constructor(factory, options,
/**
* The relationship via which this factory builder was
* created
*/ viaRelation) {
Object.defineProperty(this, "factory", {
enumerable: true,
configurable: true,
writable: true,
value: factory
});
Object.defineProperty(this, "options", {
enumerable: true,
configurable: true,
writable: true,
value: options
});
Object.defineProperty(this, "viaRelation", {
enumerable: true,
configurable: true,
writable: true,
value: viaRelation
});
/**
* Relationships to setup. Do note: It is possible to load one relationship
* twice. A practical use case is to apply different states. For example:
*
* Make user with "3 active posts" and "2 draft posts"
*/
Object.defineProperty(this, "withRelations", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
/**
* An array of callbacks to execute before persisting the model instance
*/
Object.defineProperty(this, "tapCallbacks", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
/**
* Belongs to relationships are treated different, since they are
* persisted before the parent model
*/
Object.defineProperty(this, "withBelongsToRelations", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
/**
* The current index. Updated by `makeMany` and `createMany`
*/
Object.defineProperty(this, "currentIndex", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
/**
* Custom attributes to pass to model merge method
*/
Object.defineProperty(this, "attributes", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
/**
* Custom attributes to pass to relationship merge methods
*/
Object.defineProperty(this, "recursiveAttributes", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
/**
* States to apply. One state can be applied only once and hence
* a set is used.
*/
Object.defineProperty(this, "appliedStates", {
enumerable: true,
configurable: true,
writable: true,
value: new Set()
});
/**
* Custom context passed using `useCtx` method. It not defined, we will
* create one inline inside `create` and `make` methods
*/
Object.defineProperty(this, "ctx", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Pivot attributes for a many to many relationship
*/
Object.defineProperty(this, "attributesForPivotTable", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
/**
* Access the parent relationship for which the model instance
* is created
*/
get parent() {
return this.viaRelation ? this.viaRelation.parent : undefined;
}
/**
* Returns factory state
*/
async getCtx(isStubbed, withTransaction) {
if (withTransaction === false) {
return new FactoryContext_1.FactoryContext(isStubbed, undefined);
}
const client = this.factory.model.$adapter.modelConstructorClient(this.factory.model, this.options);
const trx = await client.transaction();
return new FactoryContext_1.FactoryContext(isStubbed, trx);
}
/**
* Returns attributes to merge for a given index
*/
getMergeAttributes(index) {
const attributes = Array.isArray(this.attributes) ? this.attributes[index] : this.attributes;
const recursiveAttributes = Array.isArray(this.recursiveAttributes)
? this.recursiveAttributes[index]
: this.recursiveAttributes;
return {
...recursiveAttributes,
...attributes,
};
}
/**
* Returns a new model instance with filled attributes
*/
async getModelInstance(ctx) {
const modelAttributes = await this.factory.define(ctx);
const modelInstance = this.factory.newUpModelInstance(modelAttributes, ctx, this.factory.model, this);
this.factory.mergeAttributes(modelInstance, this.getMergeAttributes(this.currentIndex), ctx, this);
return modelInstance;
}
/**
* Apply states by invoking state callback
*/
async applyStates(modelInstance, ctx) {
for (let state of this.appliedStates) {
await this.factory.getState(state)(modelInstance, ctx, this);
}
}
/**
* Invoke tap callbacks
*/
invokeTapCallback(modelInstance, ctx) {
this.tapCallbacks.forEach((callback) => callback(modelInstance, ctx, this));
}
/**
* Compile factory by instantiating model instance, applying merge
* attributes, apply state
*/
async compile(ctx) {
try {
/**
* Newup the model instance
*/
const modelInstance = await this.getModelInstance(ctx);
/**
* Apply state
*/
await this.applyStates(modelInstance, ctx);
/**
* Invoke tap callbacks as the last step
*/
this.invokeTapCallback(modelInstance, ctx);
/**
* Pass pivot attributes to the relationship instance
*/
if (this.viaRelation && this.viaRelation.pivotAttributes) {
this.viaRelation.pivotAttributes(this.attributesForPivotTable || {});
}
return modelInstance;
}
catch (error) {
if (!this.ctx && ctx.$trx) {
await ctx.$trx.rollback();
}
throw error;
}
}
/**
* Makes relationship instances. Call [[createRelation]] to
* also persist them.
*/
async makeRelations(modelInstance, ctx) {
for (let { name, count, callback } of this.withBelongsToRelations) {
const relation = this.factory.getRelation(name);
await relation
.useCtx(ctx)
.merge(this.recursiveAttributes)
.make(modelInstance, callback, count);
}
for (let { name, count, callback } of this.withRelations) {
const relation = this.factory.getRelation(name);
await relation
.useCtx(ctx)
.merge(this.recursiveAttributes)
.make(modelInstance, callback, count);
}
}
/**
* Makes and persists relationship instances
*/
async createRelations(modelInstance, ctx, cycle) {
const relationships = cycle === 'before' ? this.withBelongsToRelations : this.withRelations;
for (let { name, count, callback } of relationships) {
const relation = this.factory.getRelation(name);
await relation
.useCtx(ctx)
.merge(this.recursiveAttributes)
.create(modelInstance, callback, count);
}
}
/**
* Persist the model instance along with its relationships
*/
async persistModelInstance(modelInstance, ctx) {
/**
* Fire the after "make" hook. There is no before make hook
*/
await this.factory.hooks.exec('after', 'make', this, modelInstance, ctx);
/**
* Fire the before "create" hook
*/
await this.factory.hooks.exec('before', 'create', this, modelInstance, ctx);
/**
* Sharing transaction with the model
*/
modelInstance.$trx = ctx.$trx;
/**
* Create belongs to relationships before calling the save method. Even though
* we can update the foriegn key after the initial insert call, we avoid it
* for cases, where FK is a not nullable.
*/
await this.createRelations(modelInstance, ctx, 'before');
/**
* Persist model instance
*/
await modelInstance.save();
/**
* Create relationships that are meant to be created after the parent
* row. Basically all types of relationships except belongsTo
*/
await this.createRelations(modelInstance, ctx, 'after');
/**
* Fire after hook before the transaction is committed, so that
* hook can run db operations using the same transaction
*/
await this.factory.hooks.exec('after', 'create', this, modelInstance, ctx);
}
/**
* Define custom database connection
*/
connection(connection) {
this.options = this.options || {};
this.options.connection = connection;
return this;
}
/**
* Define custom query client
*/
client(client) {
this.options = this.options || {};
this.options.client = client;
return this;
}
/**
* Define custom context. Usually called by the relationships
* to share the parent context with relationship factory
*/
useCtx(ctx) {
this.ctx = ctx;
return this;
}
/**
* Load relationship
*/
with(name, count, callback) {
const relation = this.factory.getRelation(name);
if (relation.relation.type === 'belongsTo') {
this.withBelongsToRelations.push({ name, count, callback });
return this;
}
this.withRelations.push({ name, count, callback });
return this;
}
/**
* Apply one or more states. Multiple calls to apply a single
* state will be ignored
*/
apply(...states) {
states.forEach((state) => this.appliedStates.add(state));
return this;
}
/**
* Fill custom set of attributes. They are passed down to the newUp
* method of the factory
*/
merge(attributes) {
this.attributes = attributes;
return this;
}
/**
* Merge custom set of attributes with the correct factory builder
* model and all of its relationships as well
*/
mergeRecursive(attributes) {
this.recursiveAttributes = attributes;
return this;
}
/**
* Define pivot attributes when persisting a many to many
* relationship. Results in a noop, when not called
* for a many to many relationship
*/
pivotAttributes(attributes) {
this.attributesForPivotTable = attributes;
return this;
}
/**
* Tap into the persistence layer of factory builder. Allows one
* to modify the model instance just before it is persisted
* to the database
*/
tap(callback) {
this.tapCallbacks.push(callback);
return this;
}
/**
* Make model instance. Relationships are not processed with the make function.
*/
async make() {
const ctx = this.ctx || (await this.getCtx(false, false));
const modelInstance = await this.compile(ctx);
await this.factory.hooks.exec('after', 'make', this, modelInstance, ctx);
return modelInstance;
}
/**
* Create many of the factory model instances
*/
async makeMany(count) {
let modelInstances = [];
const counter = new Array(count).fill(0).map((_, i) => i);
for (let index of counter) {
this.currentIndex = index;
modelInstances.push(await this.make());
}
return modelInstances;
}
/**
* Returns a model instance without persisting it to the database.
* Relationships are still loaded and states are also applied.
*/
async makeStubbed() {
const ctx = this.ctx || (await this.getCtx(true, false));
const modelInstance = await this.compile(ctx);
await this.factory.hooks.exec('after', 'make', this, modelInstance, ctx);
await this.factory.hooks.exec('before', 'makeStubbed', this, modelInstance, ctx);
const id = modelInstance.$primaryKeyValue || this.factory.manager.getNextId(modelInstance);
modelInstance[this.factory.model.primaryKey] = id;
/**
* Make relationships. The relationships will be not persisted
*/
await this.makeRelations(modelInstance, ctx);
/**
* Fire the after hook
*/
await this.factory.hooks.exec('after', 'makeStubbed', this, modelInstance, ctx);
return modelInstance;
}
/**
* Create many of model factory instances
*/
async makeStubbedMany(count) {
let modelInstances = [];
const counter = new Array(count).fill(0).map((_, i) => i);
for (let index of counter) {
this.currentIndex = index;
modelInstances.push(await this.makeStubbed());
}
return modelInstances;
}
/**
* Similar to make, but also persists the model instance to the
* database.
*/
async create() {
/**
* Use pre-defined ctx or create a new one
*/
const ctx = this.ctx || (await this.getCtx(false, true));
/**
* Compile a model instance
*/
const modelInstance = await this.compile(ctx);
try {
await this.persistModelInstance(modelInstance, ctx);
if (!this.ctx && ctx.$trx) {
await ctx.$trx.commit();
}
return modelInstance;
}
catch (error) {
if (!this.ctx && ctx.$trx) {
await ctx.$trx.rollback();
}
throw error;
}
}
/**
* Create and persist many of factory model instances
*/
async createMany(count) {
let modelInstances = [];
/**
* Use pre-defined ctx or create a new one
*/
const ctx = this.ctx || (await this.getCtx(false, true));
const counter = new Array(count).fill(0).map((_, i) => i);
try {
for (let index of counter) {
this.currentIndex = index;
/**
* Compile a model instance
*/
const modelInstance = await this.compile(ctx);
await this.persistModelInstance(modelInstance, ctx);
modelInstances.push(modelInstance);
}
if (!this.ctx && ctx.$trx) {
await ctx.$trx.commit();
}
return modelInstances;
}
catch (error) {
if (!this.ctx && ctx.$trx) {
await ctx.$trx.rollback();
}
throw error;
}
}
}
exports.FactoryBuilder = FactoryBuilder;
+8
View File
@@ -0,0 +1,8 @@
import { FactoryContextContract } from '@ioc:Adonis/Lucid/Factory';
import { TransactionClientContract } from '@ioc:Adonis/Lucid/Database';
export declare class FactoryContext implements FactoryContextContract {
isStubbed: boolean;
$trx: TransactionClientContract | undefined;
faker: import("@faker-js/faker").Faker;
constructor(isStubbed: boolean, $trx: TransactionClientContract | undefined);
}
+35
View File
@@ -0,0 +1,35 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FactoryContext = void 0;
const faker_1 = require("@faker-js/faker");
class FactoryContext {
constructor(isStubbed, $trx) {
Object.defineProperty(this, "isStubbed", {
enumerable: true,
configurable: true,
writable: true,
value: isStubbed
});
Object.defineProperty(this, "$trx", {
enumerable: true,
configurable: true,
writable: true,
value: $trx
});
Object.defineProperty(this, "faker", {
enumerable: true,
configurable: true,
writable: true,
value: faker_1.faker
});
}
}
exports.FactoryContext = FactoryContext;
+80
View File
@@ -0,0 +1,80 @@
import { Hooks } from '@poppinss/hooks';
import { LucidModel, ExtractModelRelations } from '@ioc:Adonis/Lucid/Orm';
import { EventsList, HooksHandler, StateCallback, MergeCallback, NewUpCallback, DefineCallback, FactoryModelContract, FactoryRelationContract } from '@ioc:Adonis/Lucid/Factory';
import { FactoryManager } from './index';
/**
* Factory model exposes the API to define a model factory with custom
* states and relationships
*/
export declare class FactoryModel<Model extends LucidModel> implements FactoryModelContract<Model> {
model: Model;
define: DefineCallback<Model>;
manager: FactoryManager;
/**
* Method to instantiate a new model instance. This method can be
* overridden using the `newUp` public method.
*/
newUpModelInstance: NewUpCallback<FactoryModelContract<LucidModel>>;
/**
* Method to merge runtime attributes with the model instance. This method
* can be overridden using the `merge` method.
*/
mergeAttributes: MergeCallback<FactoryModelContract<LucidModel>>;
/**
* A collection of factory states
*/
states: {
[key: string]: StateCallback<Model>;
};
/**
* A collection of factory relations
*/
relations: {
[relation: string]: FactoryRelationContract;
};
/**
* A set of registered hooks
*/
hooks: Hooks;
constructor(model: Model, define: DefineCallback<Model>, manager: FactoryManager);
/**
* Register a before event hook
*/
before(event: EventsList, handler: HooksHandler<any>): this;
/**
* Register an after event hook
*/
after(event: EventsList, handler: HooksHandler<any>): this;
/**
* Returns state callback defined on the model factory. Raises an
* exception, when state is not registered
*/
getState(state: string): StateCallback<Model>;
/**
* Returns the pre-registered relationship factory function, along with
* the original model relation.
*/
getRelation(relation: string): FactoryRelationContract;
/**
* Define custom state for the factory. When executing the factory,
* you can apply the pre-defined states
*/
state(state: string, callback: StateCallback<Model>): any;
/**
* Define a relationship on another factory
*/
relation<K extends ExtractModelRelations<InstanceType<Model>>>(relation: Extract<K, string>, callback: any): any;
/**
* Define a custom `newUp` method
*/
newUp(callback: NewUpCallback<any>): this;
/**
* Define a custom `merge` method
*/
merge(callback: MergeCallback<any>): this;
/**
* Build factory model and return factory builder. The builder is then
* used to make/create model instances
*/
build(): any;
}
+255
View File
@@ -0,0 +1,255 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FactoryModel = void 0;
const hooks_1 = require("@poppinss/hooks");
const HasOne_1 = require("./Relations/HasOne");
const HasMany_1 = require("./Relations/HasMany");
const FactoryBuilder_1 = require("./FactoryBuilder");
const BelongsTo_1 = require("./Relations/BelongsTo");
const ManyToMany_1 = require("./Relations/ManyToMany");
/**
* Factory model exposes the API to define a model factory with custom
* states and relationships
*/
class FactoryModel {
constructor(model, define, manager) {
Object.defineProperty(this, "model", {
enumerable: true,
configurable: true,
writable: true,
value: model
});
Object.defineProperty(this, "define", {
enumerable: true,
configurable: true,
writable: true,
value: define
});
Object.defineProperty(this, "manager", {
enumerable: true,
configurable: true,
writable: true,
value: manager
});
/**
* Method to instantiate a new model instance. This method can be
* overridden using the `newUp` public method.
*/
Object.defineProperty(this, "newUpModelInstance", {
enumerable: true,
configurable: true,
writable: true,
value: (attributes, _, model) => {
/**
* Handling case, where someone returns model instance directly
*/
if (attributes instanceof model) {
return attributes;
}
const modelInstance = new model();
modelInstance.merge(attributes);
return modelInstance;
}
});
/**
* Method to merge runtime attributes with the model instance. This method
* can be overridden using the `merge` method.
*/
Object.defineProperty(this, "mergeAttributes", {
enumerable: true,
configurable: true,
writable: true,
value: (model, attributes) => {
model.merge(attributes);
}
});
/**
* A collection of factory states
*/
Object.defineProperty(this, "states", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
/**
* A collection of factory relations
*/
Object.defineProperty(this, "relations", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
/**
* A set of registered hooks
*/
Object.defineProperty(this, "hooks", {
enumerable: true,
configurable: true,
writable: true,
value: new hooks_1.Hooks()
});
}
/**
* Register a before event hook
*/
before(event, handler) {
this.hooks.add('before', event, handler);
return this;
}
/**
* Register an after event hook
*/
after(event, handler) {
this.hooks.add('after', event, handler);
return this;
}
/**
* Returns state callback defined on the model factory. Raises an
* exception, when state is not registered
*/
getState(state) {
const stateCallback = this.states[state];
if (!stateCallback) {
throw new Error(`Cannot apply undefined state "${state}". Double check the model factory`);
}
return stateCallback;
}
/**
* Returns the pre-registered relationship factory function, along with
* the original model relation.
*/
getRelation(relation) {
const relationship = this.relations[relation];
if (!relationship) {
throw new Error(`Cannot reference "${relation}" relationship. Make sure to setup the relationship within the factory`);
}
return relationship;
}
/**
* Define custom state for the factory. When executing the factory,
* you can apply the pre-defined states
*/
state(state, callback) {
this.states[state] = callback;
return this;
}
/**
* Define a relationship on another factory
*/
relation(relation, callback) {
const modelRelation = this.model.$getRelation(relation);
/**
* Only whitelisted relationships are allowed on the factory
*/
if (!modelRelation) {
throw new Error([
`Cannot define "${relation}" relationship.`,
`The relationship must exist on the "${this.model.name}" model first`,
].join(' '));
}
switch (modelRelation.type) {
case 'belongsTo':
this.relations[relation] = new BelongsTo_1.BelongsTo(modelRelation, callback);
break;
case 'hasOne':
this.relations[relation] = new HasOne_1.HasOne(modelRelation, callback);
break;
case 'hasMany':
this.relations[relation] = new HasMany_1.HasMany(modelRelation, callback);
break;
case 'manyToMany':
this.relations[relation] = new ManyToMany_1.ManyToMany(modelRelation, callback);
break;
case 'hasManyThrough':
throw new Error([
`Cannot define "${relation}" relationship.`,
'"hasManyThrough" relationship does not have any persistance API',
].join(' '));
}
return this;
}
/**
* Define a custom `newUp` method
*/
newUp(callback) {
this.newUpModelInstance = callback;
return this;
}
/**
* Define a custom `merge` method
*/
merge(callback) {
this.mergeAttributes = callback;
return this;
}
/**
* Build factory model and return factory builder. The builder is then
* used to make/create model instances
*/
build() {
/**
* Return a build object, which proxies all of the factory builder
* method and invokes them with a fresh instance.
*/
const builder = {
factory: this,
query(options, viaRelation) {
return new FactoryBuilder_1.FactoryBuilder(this.factory, options, viaRelation);
},
tap(callback) {
return this.query().tap(callback);
},
client(...args) {
return this.query().client(...args);
},
connection(...args) {
return this.query().connection(...args);
},
apply(...args) {
return this.query().apply(...args);
},
with(relation, ...args) {
return this.query().with(relation, ...args);
},
merge(attributes) {
return this.query().merge(attributes);
},
mergeRecursive(attributes) {
return this.query().mergeRecursive(attributes);
},
useCtx(ctx) {
return this.query().useCtx(ctx);
},
make(callback) {
return this.query().make(callback);
},
makeStubbed(callback) {
return this.query().makeStubbed(callback);
},
create(callback) {
return this.query().create(callback);
},
makeMany(count, callback) {
return this.query().makeMany(count, callback);
},
makeStubbedMany(count, callback) {
return this.query().makeStubbedMany(count, callback);
},
createMany(count, callback) {
return this.query().createMany(count, callback);
},
};
return builder;
}
}
exports.FactoryModel = FactoryModel;
+26
View File
@@ -0,0 +1,26 @@
/// <reference path="../../../adonis-typings/factory.d.ts" />
import { LucidModel, LucidRow } from '@ioc:Adonis/Lucid/Orm';
import { RelationCallback, FactoryModelContract, FactoryContextContract, FactoryBuilderQueryContract, FactoryRelationContract } from '@ioc:Adonis/Lucid/Factory';
/**
* Base relation to be extended by other factory relations
*/
export declare abstract class BaseRelation {
private factory;
protected ctx: FactoryContextContract;
private attributes;
parent: LucidRow;
constructor(factory: () => FactoryBuilderQueryContract<FactoryModelContract<LucidModel>>);
/**
* Instantiates the relationship factory
*/
protected compile(relation: FactoryRelationContract, parent: LucidRow, callback?: RelationCallback): import("@ioc:Adonis/Lucid/Factory").FactoryBuilderContract<FactoryModelContract<LucidModel>>;
/**
* Merge attributes with the relationship and its children
*/
merge(attributes: any): this;
/**
* Use custom ctx. This must always be called by the factory, otherwise
* `make` and `create` calls will fail.
*/
useCtx(ctx: FactoryContextContract): this;
}
+70
View File
@@ -0,0 +1,70 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseRelation = void 0;
/**
* Base relation to be extended by other factory relations
*/
class BaseRelation {
constructor(factory) {
Object.defineProperty(this, "factory", {
enumerable: true,
configurable: true,
writable: true,
value: factory
});
Object.defineProperty(this, "ctx", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "attributes", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "parent", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
/**
* Instantiates the relationship factory
*/
compile(relation, parent, callback) {
this.parent = parent;
const builder = this.factory().query(undefined, relation);
if (typeof callback === 'function') {
callback(builder);
}
builder.useCtx(this.ctx).mergeRecursive(this.attributes);
return builder;
}
/**
* Merge attributes with the relationship and its children
*/
merge(attributes) {
this.attributes = attributes;
return this;
}
/**
* Use custom ctx. This must always be called by the factory, otherwise
* `make` and `create` calls will fail.
*/
useCtx(ctx) {
this.ctx = ctx;
return this;
}
}
exports.BaseRelation = BaseRelation;
@@ -0,0 +1,18 @@
import { LucidModel, LucidRow, BelongsToRelationContract } from '@ioc:Adonis/Lucid/Orm';
import { RelationCallback, FactoryModelContract, FactoryRelationContract, FactoryBuilderQueryContract } from '@ioc:Adonis/Lucid/Factory';
import { BaseRelation } from './Base';
/**
* A belongs to factory relation
*/
export declare class BelongsTo extends BaseRelation implements FactoryRelationContract {
relation: BelongsToRelationContract<LucidModel, LucidModel>;
constructor(relation: BelongsToRelationContract<LucidModel, LucidModel>, factory: () => FactoryBuilderQueryContract<FactoryModelContract<LucidModel>>);
/**
* Make relationship and set it on the parent model instance
*/
make(parent: LucidRow, callback?: RelationCallback): Promise<void>;
/**
* Persist relationship and set it on the parent model instance
*/
create(parent: LucidRow, callback?: RelationCallback): Promise<void>;
}
+46
View File
@@ -0,0 +1,46 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BelongsTo = void 0;
const Base_1 = require("./Base");
/**
* A belongs to factory relation
*/
class BelongsTo extends Base_1.BaseRelation {
constructor(relation, factory) {
super(factory);
Object.defineProperty(this, "relation", {
enumerable: true,
configurable: true,
writable: true,
value: relation
});
this.relation.boot();
}
/**
* Make relationship and set it on the parent model instance
*/
async make(parent, callback) {
const factory = this.compile(this, parent, callback);
const related = await factory.makeStubbed();
this.relation.hydrateForPersistance(parent, related);
parent.$setRelated(this.relation.relationName, related);
}
/**
* Persist relationship and set it on the parent model instance
*/
async create(parent, callback) {
const factory = this.compile(this, parent, callback);
const related = await factory.create();
this.relation.hydrateForPersistance(parent, related);
parent.$setRelated(this.relation.relationName, related);
}
}
exports.BelongsTo = BelongsTo;
+18
View File
@@ -0,0 +1,18 @@
import { LucidModel, LucidRow, HasManyRelationContract } from '@ioc:Adonis/Lucid/Orm';
import { RelationCallback, FactoryModelContract, FactoryRelationContract, FactoryBuilderQueryContract } from '@ioc:Adonis/Lucid/Factory';
import { BaseRelation } from './Base';
/**
* Has many to factory relation
*/
export declare class HasMany extends BaseRelation implements FactoryRelationContract {
relation: HasManyRelationContract<LucidModel, LucidModel>;
constructor(relation: HasManyRelationContract<LucidModel, LucidModel>, factory: () => FactoryBuilderQueryContract<FactoryModelContract<LucidModel>>);
/**
* Make relationship and set it on the parent model instance
*/
make(parent: LucidRow, callback?: RelationCallback, count?: number): Promise<void>;
/**
* Persist relationship and set it on the parent model instance
*/
create(parent: LucidRow, callback?: RelationCallback, count?: number): Promise<void>;
}
+56
View File
@@ -0,0 +1,56 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.HasMany = void 0;
const Base_1 = require("./Base");
/**
* Has many to factory relation
*/
class HasMany extends Base_1.BaseRelation {
constructor(relation, factory) {
super(factory);
Object.defineProperty(this, "relation", {
enumerable: true,
configurable: true,
writable: true,
value: relation
});
this.relation.boot();
}
/**
* Make relationship and set it on the parent model instance
*/
async make(parent, callback, count) {
const factory = this.compile(this, parent, callback);
const customAttributes = {};
this.relation.hydrateForPersistance(parent, customAttributes);
const instances = await factory
.tap((related) => {
related.merge(customAttributes);
})
.makeStubbedMany(count || 1);
parent.$setRelated(this.relation.relationName, instances);
}
/**
* Persist relationship and set it on the parent model instance
*/
async create(parent, callback, count) {
const factory = this.compile(this, parent, callback);
const customAttributes = {};
this.relation.hydrateForPersistance(parent, customAttributes);
const instance = await factory
.tap((related) => {
related.merge(customAttributes);
})
.createMany(count || 1);
parent.$setRelated(this.relation.relationName, instance);
}
}
exports.HasMany = HasMany;
+18
View File
@@ -0,0 +1,18 @@
import { LucidModel, LucidRow, HasOneRelationContract } from '@ioc:Adonis/Lucid/Orm';
import { RelationCallback, FactoryModelContract, FactoryRelationContract, FactoryBuilderQueryContract } from '@ioc:Adonis/Lucid/Factory';
import { BaseRelation } from './Base';
/**
* Has one to factory relation
*/
export declare class HasOne extends BaseRelation implements FactoryRelationContract {
relation: HasOneRelationContract<LucidModel, LucidModel>;
constructor(relation: HasOneRelationContract<LucidModel, LucidModel>, factory: () => FactoryBuilderQueryContract<FactoryModelContract<LucidModel>>);
/**
* Make relationship and set it on the parent model instance
*/
make(parent: LucidRow, callback?: RelationCallback): Promise<void>;
/**
* Persist relationship and set it on the parent model instance
*/
create(parent: LucidRow, callback?: RelationCallback): Promise<void>;
}
+48
View File
@@ -0,0 +1,48 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.HasOne = void 0;
const Base_1 = require("./Base");
/**
* Has one to factory relation
*/
class HasOne extends Base_1.BaseRelation {
constructor(relation, factory) {
super(factory);
Object.defineProperty(this, "relation", {
enumerable: true,
configurable: true,
writable: true,
value: relation
});
this.relation.boot();
}
/**
* Make relationship and set it on the parent model instance
*/
async make(parent, callback) {
const factory = this.compile(this, parent, callback);
const customAttributes = {};
this.relation.hydrateForPersistance(parent, customAttributes);
const instance = await factory.tap((related) => related.merge(customAttributes)).makeStubbed();
parent.$setRelated(this.relation.relationName, instance);
}
/**
* Persist relationship and set it on the parent model instance
*/
async create(parent, callback) {
const factory = this.compile(this, parent, callback);
const customAttributes = {};
this.relation.hydrateForPersistance(parent, customAttributes);
const instance = await factory.tap((related) => related.merge(customAttributes)).create();
parent.$setRelated(this.relation.relationName, instance);
}
}
exports.HasOne = HasOne;
@@ -0,0 +1,23 @@
import { ManyToManyRelationContract, LucidModel, LucidRow, ModelObject } from '@ioc:Adonis/Lucid/Orm';
import { RelationCallback, FactoryModelContract, FactoryRelationContract, FactoryBuilderQueryContract } from '@ioc:Adonis/Lucid/Factory';
import { BaseRelation } from './Base';
/**
* Many to many factory relation
*/
export declare class ManyToMany extends BaseRelation implements FactoryRelationContract {
relation: ManyToManyRelationContract<LucidModel, LucidModel>;
private attributesForPivotTable;
constructor(relation: ManyToManyRelationContract<LucidModel, LucidModel>, factory: () => FactoryBuilderQueryContract<FactoryModelContract<LucidModel>>);
/**
* Make relationship and set it on the parent model instance
*/
make(parent: LucidRow, callback?: RelationCallback, count?: number): Promise<void>;
/**
* Define pivot attributes
*/
pivotAttributes(attributes: ModelObject | ModelObject[]): this;
/**
* Persist relationship and set it on the parent model instance
*/
create(parent: LucidRow, callback?: RelationCallback, count?: number): Promise<void>;
}
+75
View File
@@ -0,0 +1,75 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ManyToMany = void 0;
const Base_1 = require("./Base");
/**
* Many to many factory relation
*/
class ManyToMany extends Base_1.BaseRelation {
constructor(relation, factory) {
super(factory);
Object.defineProperty(this, "relation", {
enumerable: true,
configurable: true,
writable: true,
value: relation
});
Object.defineProperty(this, "attributesForPivotTable", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
this.relation.boot();
}
/**
* Make relationship and set it on the parent model instance
*/
async make(parent, callback, count) {
const builder = this.compile(this, parent, callback);
const instances = await builder.makeStubbedMany(count || 1);
parent.$setRelated(this.relation.relationName, instances);
}
/**
* Define pivot attributes
*/
pivotAttributes(attributes) {
this.attributesForPivotTable = attributes;
return this;
}
/**
* Persist relationship and set it on the parent model instance
*/
async create(parent, callback, count) {
const builder = this.compile(this, parent, callback);
const instances = await builder.createMany(count || 1);
/**
* Create object for the pivot table. We merge user defined pivot attributes with
* the required foreign keys
*/
const relatedForeignKeyValues = instances.reduce((result, one, index) => {
const [, relatedForeignKeyValue] = this.relation.getPivotRelatedPair(one);
result[relatedForeignKeyValue] = Array.isArray(this.attributesForPivotTable)
? this.attributesForPivotTable[index] || {}
: this.attributesForPivotTable || {};
return result;
}, {});
/**
* Make pivot insert query
*/
await this.relation.client(parent, this.ctx.$trx).attach(relatedForeignKeyValues);
/**
* Setup in-memory relationship
*/
parent.$setRelated(this.relation.relationName, instances);
}
}
exports.ManyToMany = ManyToMany;
+22
View File
@@ -0,0 +1,22 @@
import { LucidModel, LucidRow } from '@ioc:Adonis/Lucid/Orm';
import { FactoryManagerContract, DefineCallback, StubIdCallback } from '@ioc:Adonis/Lucid/Factory';
import { FactoryModel } from './FactoryModel';
/**
* Factory manager exposes the API to register factories.
*/
export declare class FactoryManager implements FactoryManagerContract {
private stubCounter;
private stubIdCallback;
/**
* Returns the next id
*/
getNextId(model: LucidRow): any;
/**
* Define a factory model
*/
define<Model extends LucidModel>(model: Model, callback: DefineCallback<Model>): FactoryModel<Model>;
/**
* Define custom callback to generate stub ids
*/
stubId(callback: StubIdCallback): void;
}
+50
View File
@@ -0,0 +1,50 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FactoryManager = void 0;
const FactoryModel_1 = require("./FactoryModel");
/**
* Factory manager exposes the API to register factories.
*/
class FactoryManager {
constructor() {
Object.defineProperty(this, "stubCounter", {
enumerable: true,
configurable: true,
writable: true,
value: 1
});
Object.defineProperty(this, "stubIdCallback", {
enumerable: true,
configurable: true,
writable: true,
value: (counter) => counter
});
}
/**
* Returns the next id
*/
getNextId(model) {
return this.stubIdCallback(this.stubCounter++, model);
}
/**
* Define a factory model
*/
define(model, callback) {
return new FactoryModel_1.FactoryModel(model, callback, this);
}
/**
* Define custom callback to generate stub ids
*/
stubId(callback) {
this.stubIdCallback = callback;
}
}
exports.FactoryManager = FactoryManager;
+5
View File
@@ -0,0 +1,5 @@
import { DbQueryEventNode } from '@ioc:Adonis/Lucid/Database';
/**
* Pretty print queries
*/
export declare function prettyPrint(queryLog: DbQueryEventNode): void;
+71
View File
@@ -0,0 +1,71 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.prettyPrint = void 0;
const util_1 = require("util");
const igniculus_1 = __importDefault(require("igniculus"));
const illuminate = (0, igniculus_1.default)({
comments: { fg: 'gray' },
constants: { fg: 'red' },
delimitedIdentifiers: { fg: 'yellow' },
variables: { fg: 'cyan' },
dataTypes: { fg: 'green', casing: 'uppercase' },
standardKeywords: { fg: 'green', casing: 'uppercase' },
lesserKeywords: { mode: 'bold', fg: 'cyan', casing: 'uppercase' },
prefix: { replace: /.*?: / },
output: (line) => line,
});
/**
* Colorizes the sql query
*/
function colorizeQuery(sql) {
return illuminate(sql);
}
/**
* Pretty print queries
*/
function prettyPrint(queryLog) {
/**
* Lazy loading pretty printed dependencies
*/
const color = require('kleur');
const prettyHrtime = require('pretty-hrtime');
let output = '';
if (!queryLog.ddl) {
output += color.gray(`"${queryLog.connection}" `);
}
/**
* Concatenate the model
*/
if (queryLog.model) {
output += `${queryLog.model} `;
}
/**
* Concatenate the duration
*/
if (queryLog.duration) {
output += `(${prettyHrtime(queryLog.duration)}) `;
}
/**
* Colorize query and bindings
*/
output += colorizeQuery(queryLog.sql);
if (!queryLog.ddl) {
output += color.gray(` ${(0, util_1.inspect)(queryLog.bindings)}`);
}
/**
* Print it to the console
*/
console.log(output);
}
exports.prettyPrint = prettyPrint;
+5
View File
@@ -0,0 +1,5 @@
import { LucidModel, QueryScope, QueryScopeCallback } from '@ioc:Adonis/Lucid/Orm';
/**
* Helper to mark a function as query scope
*/
export declare function scope<Model extends LucidModel, Callback extends QueryScopeCallback<Model>>(callback: Callback): QueryScope<Callback>;
+18
View File
@@ -0,0 +1,18 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.scope = void 0;
/**
* Helper to mark a function as query scope
*/
function scope(callback) {
return callback;
}
exports.scope = scope;
+27
View File
@@ -0,0 +1,27 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="@adonisjs/application/build/adonis-typings" />
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
import { ConnectionConfig, FileNode } from '@ioc:Adonis/Lucid/Database';
/**
* Migration source exposes the API to read the migration files
* from disk for a given connection.
*/
export declare class MigrationSource {
private config;
private app;
constructor(config: ConnectionConfig, app: ApplicationContract);
/**
* Returns an array of files inside a given directory. Relative
* paths are resolved from the project root
*/
private getDirectoryFiles;
/**
* Returns an array of migrations paths for a given connection. If paths
* are not defined, then `database/migrations` fallback is used
*/
private getMigrationsPath;
/**
* Returns an array of files for all defined directories
*/
getMigrations(): Promise<FileNode<unknown>[]>;
}
+63
View File
@@ -0,0 +1,63 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MigrationSource = void 0;
const utils_1 = require("../utils");
/**
* Migration source exposes the API to read the migration files
* from disk for a given connection.
*/
class MigrationSource {
constructor(config, app) {
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: config
});
Object.defineProperty(this, "app", {
enumerable: true,
configurable: true,
writable: true,
value: app
});
}
/**
* Returns an array of files inside a given directory. Relative
* paths are resolved from the project root
*/
async getDirectoryFiles(directoryPath) {
const { files } = await (0, utils_1.sourceFiles)(this.app.appRoot, directoryPath, this.config.migrations?.naturalSort || false);
return files;
}
/**
* Returns an array of migrations paths for a given connection. If paths
* are not defined, then `database/migrations` fallback is used
*/
getMigrationsPath() {
const directories = (this.config.migrations || {}).paths;
const defaultDirectory = this.app.directoriesMap.get('migrations') || 'database/migrations';
return directories && directories.length ? directories : [`./${defaultDirectory}`];
}
/**
* Returns an array of files for all defined directories
*/
async getMigrations() {
const migrationPaths = this.getMigrationsPath();
const directories = await Promise.all(migrationPaths.map((directoryPath) => {
return this.getDirectoryFiles(directoryPath);
}));
return directories.reduce((result, directory) => {
result = result.concat(directory);
return result;
}, []);
}
}
exports.MigrationSource = MigrationSource;
+200
View File
@@ -0,0 +1,200 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="@adonisjs/application/build/adonis-typings" />
/// <reference types="node" />
import { EventEmitter } from 'events';
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
import { MigratorOptions, MigratedFileNode, MigratorContract, MigrationListNode } from '@ioc:Adonis/Lucid/Migrator';
import { DatabaseContract } from '@ioc:Adonis/Lucid/Database';
/**
* Migrator exposes the API to execute migrations using the schema files
* for a given connection at a time.
*/
export declare class Migrator extends EventEmitter implements MigratorContract {
private db;
private app;
private options;
private client;
private config;
/**
* Reference to the migrations config for the given connection
*/
private migrationsConfig;
/**
* Table names for storing schema files and schema versions
*/
private schemaTableName;
private schemaVersionsTableName;
/**
* Whether or not the migrator has been booted
*/
private booted;
/**
* Migration source to collect schema files from the disk
*/
private migrationSource;
/**
* Mode decides in which mode the migrator is executing migrations. The migrator
* instance can only run in one mode at a time.
*
* The value is set when `migrate` or `rollback` method is invoked
*/
direction: 'up' | 'down';
/**
* Instead of executing migrations, just return the generated SQL queries
*/
dryRun: boolean;
/**
* Disable advisory locks
*/
disableLocks: boolean;
/**
* An array of files we have successfully migrated. The files are
* collected regardless of `up` or `down` methods
*/
migratedFiles: {
[file: string]: MigratedFileNode;
};
/**
* Last error occurred when executing migrations
*/
error: null | Error;
/**
* Current status of the migrator
*/
get status(): "error" | "completed" | "pending" | "skipped";
/**
* Existing version of migrations. We use versioning to upgrade
* existing migrations if we are plan to make a breaking
* change.
*/
version: number;
constructor(db: DatabaseContract, app: ApplicationContract, options: MigratorOptions);
/**
* Returns the client for a given schema file. Schema instructions are
* wrapped in a transaction unless transaction is not disabled
*/
private getClient;
/**
* Roll back the transaction when it's client is a transaction client
*/
private rollback;
/**
* Commits a transaction when it's client is a transaction client
*/
private commit;
/**
* Writes the migrated file to the migrations table. This ensures that
* we are not re-running the same migration again
*/
private recordMigrated;
/**
* Removes the migrated file from the migrations table. This allows re-running
* the migration
*/
private recordRollback;
/**
* Returns the migration source by ensuring value is a class constructor and
* has disableTransactions property.
*/
private getMigrationSource;
/**
* Executes a given migration node and cleans up any created transactions
* in case of failure
*/
private executeMigration;
/**
* Acquires a lock to disallow concurrent transactions. Only works with
* `Mysql`, `PostgreSQL` and `MariaDb` for now.
*
* Make sure we are acquiring lock outside the transactions, since we want
* to block other processes from acquiring the same lock.
*
* Locks are always acquired in dry run too, since we want to stay close
* to the real execution cycle
*/
private acquireLock;
/**
* Release a lock once complete the migration process. Only works with
* `Mysql`, `PostgreSQL` and `MariaDb` for now.
*/
private releaseLock;
/**
* Makes the migrations table (if missing). Also created in dry run, since
* we always reads from the schema table to find which migrations files to
* execute and that cannot done without missing table.
*/
private makeMigrationsTable;
/**
* Makes the migrations version table (if missing).
*/
private makeMigrationsVersionsTable;
/**
* Returns the latest migrations version. If no rows exists
* it inserts a new row for version 1
*/
private getLatestVersion;
/**
* Upgrade migrations name from version 1 to version 2
*/
private upgradeFromOnetoTwo;
/**
* Upgrade migrations version
*/
private upgradeVersion;
/**
* Returns the latest batch from the migrations
* table
*/
private getLatestBatch;
/**
* Returns an array of files migrated till now
*/
private getMigratedFiles;
/**
* Returns an array of files migrated till now. The latest
* migrations are on top
*/
private getMigratedFilesTillBatch;
/**
* Boot the migrator to perform actions. All boot methods must
* work regardless of dryRun is enabled or not.
*/
private boot;
/**
* Shutdown gracefully
*/
private shutdown;
/**
* Migrate up
*/
private runUp;
/**
* Migrate down (aka rollback)
*/
private runDown;
on(event: 'start', callback: () => void): this;
on(event: 'end', callback: () => void): this;
on(event: 'acquire:lock', callback: () => void): this;
on(event: 'release:lock', callback: () => void): this;
on(event: 'create:schema:table', callback: () => void): this;
on(event: 'create:schema_versions:table', callback: () => void): this;
on(event: 'upgrade:version', callback: (payload: {
from: number;
to: number;
}) => void): this;
on(event: 'migration:start', callback: (file: MigratedFileNode) => void): this;
on(event: 'migration:completed', callback: (file: MigratedFileNode) => void): this;
on(event: 'migration:error', callback: (file: MigratedFileNode) => void): this;
/**
* Returns a merged list of completed and pending migrations
*/
getList(): Promise<MigrationListNode[]>;
/**
* Migrate the database by calling the up method
*/
run(): Promise<void>;
/**
* Close database connections
*/
close(): Promise<void>;
}
+571
View File
@@ -0,0 +1,571 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Migrator = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const slash_1 = __importDefault(require("slash"));
const events_1 = require("events");
const utils_1 = require("@poppinss/utils");
const MigrationSource_1 = require("./MigrationSource");
/**
* Migrator exposes the API to execute migrations using the schema files
* for a given connection at a time.
*/
class Migrator extends events_1.EventEmitter {
constructor(db, app, options) {
super();
Object.defineProperty(this, "db", {
enumerable: true,
configurable: true,
writable: true,
value: db
});
Object.defineProperty(this, "app", {
enumerable: true,
configurable: true,
writable: true,
value: app
});
Object.defineProperty(this, "options", {
enumerable: true,
configurable: true,
writable: true,
value: options
});
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: this.db.connection(this.options.connectionName || this.db.primaryConnectionName)
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: this.db.getRawConnection(this.client.connectionName).config
});
/**
* Reference to the migrations config for the given connection
*/
Object.defineProperty(this, "migrationsConfig", {
enumerable: true,
configurable: true,
writable: true,
value: Object.assign({
tableName: 'adonis_schema',
disableTransactions: false,
}, this.config.migrations)
});
/**
* Table names for storing schema files and schema versions
*/
Object.defineProperty(this, "schemaTableName", {
enumerable: true,
configurable: true,
writable: true,
value: this.migrationsConfig.tableName
});
Object.defineProperty(this, "schemaVersionsTableName", {
enumerable: true,
configurable: true,
writable: true,
value: `${this.schemaTableName}_versions`
});
/**
* Whether or not the migrator has been booted
*/
Object.defineProperty(this, "booted", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
/**
* Migration source to collect schema files from the disk
*/
Object.defineProperty(this, "migrationSource", {
enumerable: true,
configurable: true,
writable: true,
value: new MigrationSource_1.MigrationSource(this.config, this.app)
});
/**
* Mode decides in which mode the migrator is executing migrations. The migrator
* instance can only run in one mode at a time.
*
* The value is set when `migrate` or `rollback` method is invoked
*/
Object.defineProperty(this, "direction", {
enumerable: true,
configurable: true,
writable: true,
value: this.options.direction
});
/**
* Instead of executing migrations, just return the generated SQL queries
*/
Object.defineProperty(this, "dryRun", {
enumerable: true,
configurable: true,
writable: true,
value: !!this.options.dryRun
});
/**
* Disable advisory locks
*/
Object.defineProperty(this, "disableLocks", {
enumerable: true,
configurable: true,
writable: true,
value: !!this.options.disableLocks
});
/**
* An array of files we have successfully migrated. The files are
* collected regardless of `up` or `down` methods
*/
Object.defineProperty(this, "migratedFiles", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
/**
* Last error occurred when executing migrations
*/
Object.defineProperty(this, "error", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
/**
* Existing version of migrations. We use versioning to upgrade
* existing migrations if we are plan to make a breaking
* change.
*/
Object.defineProperty(this, "version", {
enumerable: true,
configurable: true,
writable: true,
value: 2
});
}
/**
* Current status of the migrator
*/
get status() {
return !this.booted
? 'pending'
: this.error
? 'error'
: Object.keys(this.migratedFiles).length
? 'completed'
: 'skipped';
}
/**
* Returns the client for a given schema file. Schema instructions are
* wrapped in a transaction unless transaction is not disabled
*/
async getClient(disableTransactions) {
/**
* We do not create a transaction when
*
* 1. Migration itself disables transaction
* 2. Transactions are globally disabled
* 3. Doing a dry run
*/
if (disableTransactions || this.migrationsConfig.disableTransactions || this.dryRun) {
return this.client;
}
return this.client.transaction();
}
/**
* Roll back the transaction when it's client is a transaction client
*/
async rollback(client) {
if (client.isTransaction) {
await client.rollback();
}
}
/**
* Commits a transaction when it's client is a transaction client
*/
async commit(client) {
if (client.isTransaction) {
await client.commit();
}
}
/**
* Writes the migrated file to the migrations table. This ensures that
* we are not re-running the same migration again
*/
async recordMigrated(client, name, executionResponse) {
if (this.dryRun) {
this.migratedFiles[name].queries = executionResponse;
return;
}
await client.insertQuery().table(this.schemaTableName).insert({
name,
batch: this.migratedFiles[name].batch,
});
}
/**
* Removes the migrated file from the migrations table. This allows re-running
* the migration
*/
async recordRollback(client, name, executionResponse) {
if (this.dryRun) {
this.migratedFiles[name].queries = executionResponse;
return;
}
await client.query().from(this.schemaTableName).where({ name }).del();
}
/**
* Returns the migration source by ensuring value is a class constructor and
* has disableTransactions property.
*/
async getMigrationSource(migration) {
const source = await migration.getSource();
if (typeof source === 'function' && 'disableTransactions' in source) {
return source;
}
throw new Error(`Invalid schema class exported by "${migration.name}"`);
}
/**
* Executes a given migration node and cleans up any created transactions
* in case of failure
*/
async executeMigration(migration) {
const Schema = await this.getMigrationSource(migration);
const client = await this.getClient(Schema.disableTransactions);
try {
const schema = new Schema(client, migration.name, this.dryRun);
this.emit('migration:start', this.migratedFiles[migration.name]);
if (this.direction === 'up') {
const response = await schema.execUp(); // Handles dry run itself
await this.recordMigrated(client, migration.name, response); // Handles dry run itself
}
else if (this.direction === 'down') {
const response = await schema.execDown(); // Handles dry run itself
await this.recordRollback(client, migration.name, response); // Handles dry run itself
}
await this.commit(client);
this.migratedFiles[migration.name].status = 'completed';
this.emit('migration:completed', this.migratedFiles[migration.name]);
}
catch (error) {
this.error = error;
this.migratedFiles[migration.name].status = 'error';
this.emit('migration:error', this.migratedFiles[migration.name]);
await this.rollback(client);
throw error;
}
}
/**
* Acquires a lock to disallow concurrent transactions. Only works with
* `Mysql`, `PostgreSQL` and `MariaDb` for now.
*
* Make sure we are acquiring lock outside the transactions, since we want
* to block other processes from acquiring the same lock.
*
* Locks are always acquired in dry run too, since we want to stay close
* to the real execution cycle
*/
async acquireLock() {
if (!this.client.dialect.supportsAdvisoryLocks || this.disableLocks) {
return;
}
const acquired = await this.client.dialect.getAdvisoryLock(1);
if (!acquired) {
throw new utils_1.Exception('Unable to acquire lock. Concurrent migrations are not allowed');
}
this.emit('acquire:lock');
}
/**
* Release a lock once complete the migration process. Only works with
* `Mysql`, `PostgreSQL` and `MariaDb` for now.
*/
async releaseLock() {
if (!this.client.dialect.supportsAdvisoryLocks || this.disableLocks) {
return;
}
const released = await this.client.dialect.releaseAdvisoryLock(1);
if (!released) {
throw new utils_1.Exception('Migration completed, but unable to release database lock');
}
this.emit('release:lock');
}
/**
* Makes the migrations table (if missing). Also created in dry run, since
* we always reads from the schema table to find which migrations files to
* execute and that cannot done without missing table.
*/
async makeMigrationsTable() {
const hasTable = await this.client.schema.hasTable(this.schemaTableName);
if (hasTable) {
return;
}
this.emit('create:schema:table');
await this.client.schema.createTable(this.schemaTableName, (table) => {
table.increments().notNullable();
table.string('name').notNullable();
table.integer('batch').notNullable();
table.timestamp('migration_time').defaultTo(this.client.getWriteClient().fn.now());
});
}
/**
* Makes the migrations version table (if missing).
*/
async makeMigrationsVersionsTable() {
/**
* Return early when table already exists
*/
const hasTable = await this.client.schema.hasTable(this.schemaVersionsTableName);
if (hasTable) {
return;
}
/**
* Create table
*/
this.emit('create:schema_versions:table');
await this.client.schema.createTable(this.schemaVersionsTableName, (table) => {
table.integer('version').notNullable();
});
}
/**
* Returns the latest migrations version. If no rows exists
* it inserts a new row for version 1
*/
async getLatestVersion() {
const rows = await this.client.from(this.schemaVersionsTableName).select('version').limit(1);
if (rows.length) {
return Number(rows[0].version);
}
else {
await this.client.table(this.schemaVersionsTableName).insert({ version: 1 });
return 1;
}
}
/**
* Upgrade migrations name from version 1 to version 2
*/
async upgradeFromOnetoTwo() {
const migrations = await this.getMigratedFilesTillBatch(0);
const client = await this.getClient(false);
try {
await Promise.all(migrations.map((migration) => {
return client
.from(this.schemaTableName)
.where('id', migration.id)
.update({
name: (0, slash_1.default)(migration.name),
});
}));
await client.from(this.schemaVersionsTableName).where('version', 1).update({ version: 2 });
await this.commit(client);
}
catch (error) {
this.rollback(client);
throw error;
}
}
/**
* Upgrade migrations version
*/
async upgradeVersion(latestVersion) {
if (latestVersion === 1) {
this.emit('upgrade:version', { from: 1, to: 2 });
await this.upgradeFromOnetoTwo();
}
}
/**
* Returns the latest batch from the migrations
* table
*/
async getLatestBatch() {
const rows = await this.client.from(this.schemaTableName).max('batch as batch');
return Number(rows[0].batch);
}
/**
* Returns an array of files migrated till now
*/
async getMigratedFiles() {
const rows = await this.client
.query()
.from(this.schemaTableName)
.select('name');
return new Set(rows.map(({ name }) => name));
}
/**
* Returns an array of files migrated till now. The latest
* migrations are on top
*/
async getMigratedFilesTillBatch(batch) {
return this.client
.query()
.from(this.schemaTableName)
.select('name', 'batch', 'migration_time', 'id')
.where('batch', '>', batch)
.orderBy('id', 'desc');
}
/**
* Boot the migrator to perform actions. All boot methods must
* work regardless of dryRun is enabled or not.
*/
async boot() {
this.emit('start');
this.booted = true;
await this.acquireLock();
await this.makeMigrationsTable();
}
/**
* Shutdown gracefully
*/
async shutdown() {
await this.releaseLock();
this.emit('end');
}
/**
* Migrate up
*/
async runUp() {
const batch = await this.getLatestBatch();
const existing = await this.getMigratedFiles();
const collected = await this.migrationSource.getMigrations();
/**
* Upfront collecting the files to be executed
*/
collected.forEach((migration) => {
if (!existing.has(migration.name)) {
this.migratedFiles[migration.name] = {
status: 'pending',
queries: [],
file: migration,
batch: batch + 1,
};
}
});
const filesToMigrate = Object.keys(this.migratedFiles);
for (let name of filesToMigrate) {
await this.executeMigration(this.migratedFiles[name].file);
}
}
/**
* Migrate down (aka rollback)
*/
async runDown(batch) {
if (this.app.inProduction && this.migrationsConfig.disableRollbacksInProduction) {
throw new Error('Rollback in production environment is disabled. Check "config/database" file for options.');
}
if (batch === undefined) {
batch = (await this.getLatestBatch()) - 1;
}
const existing = await this.getMigratedFilesTillBatch(batch);
const collected = await this.migrationSource.getMigrations();
/**
* Finding schema files for migrations to rollback. We do not perform
* rollback when any of the files are missing
*/
existing.forEach((file) => {
const migration = collected.find(({ name }) => name === file.name);
if (!migration) {
throw new utils_1.Exception(`Cannot perform rollback. Schema file {${file.name}} is missing`, 500, 'E_MISSING_SCHEMA_FILES');
}
this.migratedFiles[migration.name] = {
status: 'pending',
queries: [],
file: migration,
batch: file.batch,
};
});
const filesToMigrate = Object.keys(this.migratedFiles);
for (let name of filesToMigrate) {
await this.executeMigration(this.migratedFiles[name].file);
}
}
on(event, callback) {
return super.on(event, callback);
}
/**
* Returns a merged list of completed and pending migrations
*/
async getList() {
const existingCollected = new Set();
await this.makeMigrationsTable();
const existing = await this.getMigratedFilesTillBatch(0);
const collected = await this.migrationSource.getMigrations();
const list = collected.map((migration) => {
const migrated = existing.find(({ name }) => migration.name === name);
/**
* Already migrated. We move to an additional list, so that we can later
* find the one's which are migrated but now missing on the disk
*/
if (migrated) {
existingCollected.add(migrated.name);
return {
name: migration.name,
batch: migrated.batch,
status: 'migrated',
migrationTime: migrated.migration_time,
};
}
return {
name: migration.name,
status: 'pending',
};
});
/**
* These are the one's which were migrated earlier, but now missing
* on the disk
*/
existing.forEach(({ name, batch, migration_time }) => {
if (!existingCollected.has(name)) {
list.push({ name, batch, migrationTime: migration_time, status: 'corrupt' });
}
});
return list;
}
/**
* Migrate the database by calling the up method
*/
async run() {
try {
await this.boot();
/**
* Upgrading migrations (if required)
*/
await this.makeMigrationsVersionsTable();
const latestVersion = await this.getLatestVersion();
if (latestVersion < this.version) {
await this.upgradeVersion(latestVersion);
}
if (this.direction === 'up') {
await this.runUp();
}
else if (this.options.direction === 'down') {
await this.runDown(this.options.batch);
}
}
catch (error) {
this.error = error;
}
await this.shutdown();
}
/**
* Close database connections
*/
async close() {
await this.db.manager.closeAll(true);
}
}
exports.Migrator = Migrator;
+42
View File
@@ -0,0 +1,42 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
/// <reference path="../../../adonis-typings/querybuilder.d.ts" />
/// <reference path="../../../adonis-typings/database.d.ts" />
import { DatabaseContract } from '@ioc:Adonis/Lucid/Database';
import { LucidRow, LucidModel, AdapterContract, ModelAdapterOptions } from '@ioc:Adonis/Lucid/Orm';
/**
* Adapter exposes the API to make database queries and constructor
* model instances from it.
*/
export declare class Adapter implements AdapterContract {
private db;
constructor(db: DatabaseContract);
private getPrimaryKeyColumnName;
/**
* Returns the query client based upon the model instance
*/
modelConstructorClient(modelConstructor: LucidModel, options?: ModelAdapterOptions): import("@ioc:Adonis/Lucid/Database").QueryClientContract;
/**
* Returns the model query builder instance for a given model
*/
query(modelConstructor: LucidModel, options?: ModelAdapterOptions): any;
/**
* Returns query client for a model instance by inspecting it's options
*/
modelClient(instance: LucidRow): any;
/**
* Perform insert query on a given model instance
*/
insert(instance: LucidRow, attributes: any): Promise<void>;
/**
* Perform update query on a given model instance
*/
update(instance: LucidRow, dirty: any): Promise<void>;
/**
* Perform delete query on a given model instance
*/
delete(instance: LucidRow): Promise<void>;
/**
* Refresh the model instance attributes
*/
refresh(instance: LucidRow): Promise<void>;
}
+106
View File
@@ -0,0 +1,106 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Adapter = void 0;
/// <reference path="../../../adonis-typings/index.ts" />
const utils_1 = require("@poppinss/utils");
const utils_2 = require("../../utils");
/**
* Adapter exposes the API to make database queries and constructor
* model instances from it.
*/
class Adapter {
constructor(db) {
Object.defineProperty(this, "db", {
enumerable: true,
configurable: true,
writable: true,
value: db
});
}
getPrimaryKeyColumnName(Model) {
return Model.$keys.attributesToColumns.get(Model.primaryKey, Model.primaryKey);
}
/**
* Returns the query client based upon the model instance
*/
modelConstructorClient(modelConstructor, options) {
if (options && options.client) {
return options.client;
}
const connection = (options && options.connection) || modelConstructor.connection;
const profiler = options && options.profiler;
return this.db.connection(connection, { profiler });
}
/**
* Returns the model query builder instance for a given model
*/
query(modelConstructor, options) {
const client = this.modelConstructorClient(modelConstructor, options);
return client.modelQuery(modelConstructor);
}
/**
* Returns query client for a model instance by inspecting it's options
*/
modelClient(instance) {
const modelConstructor = instance.constructor;
return instance.$trx
? instance.$trx
: this.modelConstructorClient(modelConstructor, instance.$options);
}
/**
* Perform insert query on a given model instance
*/
async insert(instance, attributes) {
const query = instance.$getQueryFor('insert', this.modelClient(instance));
const Model = instance.constructor;
const result = await query.insert(attributes).reporterData({ model: Model.name });
if (!Model.selfAssignPrimaryKey && Array.isArray(result) && result[0]) {
if ((0, utils_2.isObject)(result[0])) {
instance.$consumeAdapterResult(result[0]);
}
else {
const primaryKeyColumnName = this.getPrimaryKeyColumnName(Model);
instance.$consumeAdapterResult({ [primaryKeyColumnName]: result[0] });
}
}
}
/**
* Perform update query on a given model instance
*/
async update(instance, dirty) {
await instance.$getQueryFor('update', this.modelClient(instance)).update(dirty);
}
/**
* Perform delete query on a given model instance
*/
async delete(instance) {
await instance.$getQueryFor('delete', this.modelClient(instance)).del();
}
/**
* Refresh the model instance attributes
*/
async refresh(instance) {
const Model = instance.constructor;
const primaryKeyColumnName = this.getPrimaryKeyColumnName(Model);
const freshModelInstance = await instance
.$getQueryFor('refresh', this.modelClient(instance))
.first();
if (!freshModelInstance) {
throw new utils_1.Exception([
'"Model.refresh" failed. ',
`Unable to lookup "${Model.table}" table where "${primaryKeyColumnName}" = ${instance.$primaryKeyValue}`,
].join(''));
}
instance.fill(freshModelInstance.$attributes);
instance.$hydrateOriginals();
}
}
exports.Adapter = Adapter;
+559
View File
@@ -0,0 +1,559 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import { Hooks } from '@poppinss/hooks';
import { IocContract } from '@ioc:Adonis/Core/Application';
import { QueryClientContract, TransactionClientContract } from '@ioc:Adonis/Lucid/Database';
import { LucidRow, CacheNode, LucidModel, CherryPick, EventsList, ModelObject, HooksHandler, ModelOptions, ColumnOptions, ComputedOptions, AdapterContract, CherryPickFields, ModelColumnOptions, ModelKeysContract, ModelAssignOptions, ModelAdapterOptions, ModelRelationOptions, ModelRelations, RelationOptions, RelationshipsContract, ThroughRelationOptions, ManyToManyRelationOptions } from '@ioc:Adonis/Lucid/Orm';
import { SnakeCaseNamingStrategy } from '../NamingStrategies/SnakeCase';
import { LazyLoadAggregates } from '../Relations/AggregatesLoader/LazyLoad';
/**
* Abstract class to define fully fledged data models
*/
export declare class BaseModel implements LucidRow {
/**
* The adapter to be used for persisting and fetching data.
*
* NOTE: Adapter is a singleton and share among all the models, unless
* a user wants to swap the adapter for a given model
*/
static $adapter: AdapterContract;
/**
* Naming strategy for model properties
*/
static namingStrategy: SnakeCaseNamingStrategy;
/**
* The container required to resolve hooks
*
* NOTE: Container is a singleton and share among all the models, unless
* a user wants to swap the container for a given model
*/
static $container: IocContract;
/**
* Primary key is required to build relationships across models
*/
static primaryKey: string;
/**
* Whether or not the model has been booted. Booting the model initializes it's
* static properties. Base models must not be initialized.
*/
static booted: boolean;
/**
* Query scopes defined on the model
*/
static $queryScopes: any;
/**
* A set of properties marked as computed. Computed properties are included in
* the `toJSON` result, else they behave the same way as any other instance
* property.
*/
static $computedDefinitions: Map<string, ComputedOptions>;
/**
* Columns makes it easier to define extra props on the model
* and distinguish them with the attributes to be sent
* over to the adapter
*/
static $columnsDefinitions: Map<string, ModelColumnOptions>;
/**
* Registered relationships for the given model
*/
static $relationsDefinitions: Map<string, RelationshipsContract>;
/**
* The name of database table. It is auto generated from the model name, unless
* specified
*/
static table: string;
/**
* Self assign the primary instead of relying on the database to
* return it back
*/
static selfAssignPrimaryKey: boolean;
/**
* A custom connection to use for queries. The connection defined on
* query builder is preferred over the model connection
*/
static connection?: string;
/**
* Storing model hooks
*/
static $hooks: Hooks;
/**
* Keys mappings to make the lookups easy
*/
static $keys: {
attributesToColumns: ModelKeysContract;
attributesToSerialized: ModelKeysContract;
columnsToAttributes: ModelKeysContract;
columnsToSerialized: ModelKeysContract;
serializedToColumns: ModelKeysContract;
serializedToAttributes: ModelKeysContract;
};
/**
* Creates a new model instance with payload and adapter options
*/
private static newUpWithOptions;
/**
* Helper method for `fetchOrNewUpMany`, `fetchOrCreateMany` and `createOrUpdate`
* many.
*/
private static newUpIfMissing;
/**
* Returns the model query instance for the given model
*/
static query(options?: ModelAdapterOptions): any;
/**
* Create a model instance from the adapter result. The result value must
* be a valid object, otherwise `null` is returned.
*/
static $createFromAdapterResult(adapterResult: ModelObject, sideloadAttributes?: ModelObject, options?: ModelAdapterOptions): any | null;
/**
* Creates an array of models from the adapter results. The `adapterResults`
* must be an array with valid Javascript objects.
*
* 1. If top level value is not an array, then an empty array is returned.
* 2. If row is not an object, then it will be ignored.
*/
static $createMultipleFromAdapterResult<T extends LucidModel>(this: T, adapterResults: ModelObject[], sideloadAttributes?: ModelObject, options?: ModelAdapterOptions): InstanceType<T>[];
/**
* Define a new column on the model. This is required, so that
* we differentiate between plain properties vs model attributes.
*/
static $addColumn(name: string, options: Partial<ColumnOptions>): ModelColumnOptions;
/**
* Returns a boolean telling if column exists on the model
*/
static $hasColumn(name: string): boolean;
/**
* Returns the column for a given name
*/
static $getColumn(name: string): ModelColumnOptions | undefined;
/**
* Adds a computed node
*/
static $addComputed(name: string, options: Partial<ComputedOptions>): ComputedOptions;
/**
* Find if some property is marked as computed
*/
static $hasComputed(name: string): boolean;
/**
* Get computed node
*/
static $getComputed(name: string): ComputedOptions | undefined;
/**
* Register has one relationship
*/
protected static $addHasOne(name: string, relatedModel: () => LucidModel, options: RelationOptions<ModelRelations>): void;
/**
* Register has many relationship
*/
protected static $addHasMany(name: string, relatedModel: () => LucidModel, options: RelationOptions<ModelRelations>): void;
/**
* Register belongs to relationship
*/
protected static $addBelongsTo(name: string, relatedModel: () => LucidModel, options: RelationOptions<ModelRelations>): void;
/**
* Register many to many relationship
*/
protected static $addManyToMany(name: string, relatedModel: () => LucidModel, options: ManyToManyRelationOptions<ModelRelations>): void;
/**
* Register many to many relationship
*/
protected static $addHasManyThrough(name: string, relatedModel: () => LucidModel, options: ThroughRelationOptions<ModelRelations>): void;
/**
* Adds a relationship
*/
static $addRelation(name: string, type: ModelRelations['__opaque_type'], relatedModel: () => LucidModel, options: ModelRelationOptions): void;
/**
* Find if some property is marked as a relation or not
*/
static $hasRelation(name: any): boolean;
/**
* Returns relationship node for a given relation
*/
static $getRelation(name: any): any;
/**
* Define a static property on the model using the inherit or
* define strategy.
*
* Inherit strategy will clone the property from the parent model
* and will set it on the current model
*/
static $defineProperty<Model extends LucidModel, Prop extends keyof Model>(this: Model, propertyName: Prop, defaultValue: Model[Prop], strategy: 'inherit' | 'define' | ((value: Model[Prop]) => Model[Prop])): void;
/**
* Boot the model
*/
static boot(): void;
/**
* Register before hooks
*/
static before(event: EventsList, handler: HooksHandler<any, EventsList>): typeof BaseModel;
/**
* Register after hooks
*/
static after(event: EventsList, handler: HooksHandler<any, EventsList>): typeof BaseModel;
/**
* Returns a fresh persisted instance of model by applying
* attributes to the model instance
*/
static create(values: any, options?: ModelAssignOptions): Promise<any>;
/**
* Same as [[BaseModel.create]], but persists multiple instances. The create
* many call will be wrapped inside a managed transaction for consistency.
* If required, you can also pass a transaction client and the method
* will use that instead of create a new one.
*/
static createMany(values: any, options?: ModelAssignOptions): Promise<any[]>;
/**
* Find model instance using the primary key
*/
static find(value: any, options?: ModelAdapterOptions): Promise<any>;
/**
* Find model instance using the primary key
*/
static findOrFail(value: any, options?: ModelAdapterOptions): Promise<any>;
/**
* Find model instance using a key/value pair
*/
static findBy(key: string, value: any, options?: ModelAdapterOptions): Promise<any>;
/**
* Find model instance using a key/value pair
*/
static findByOrFail(key: string, value: any, options?: ModelAdapterOptions): Promise<any>;
/**
* Same as `query().first()`
*/
static first(options?: ModelAdapterOptions): Promise<any>;
/**
* Same as `query().firstOrFail()`
*/
static firstOrFail(options?: ModelAdapterOptions): Promise<any>;
/**
* Find model instance using a key/value pair
*/
static findMany(value: any[], options?: ModelAdapterOptions): Promise<any>;
/**
* Find model instance using a key/value pair or create a
* new one without persisting it.
*/
static firstOrNew(searchPayload: any, savePayload?: any, options?: ModelAssignOptions): Promise<any>;
/**
* Same as `firstOrNew`, but also persists the newly created model instance.
*/
static firstOrCreate(searchPayload: any, savePayload?: any, options?: ModelAssignOptions): Promise<any>;
/**
* Updates or creates a new row inside the database
*/
static updateOrCreate(searchPayload: any, updatedPayload: any, options?: ModelAssignOptions): Promise<any>;
/**
* Find existing rows or create an in-memory instances of the missing ones.
*/
static fetchOrNewUpMany(uniqueKeys: any, payload: any, options?: ModelAssignOptions): Promise<any[]>;
/**
* Find existing rows or create missing one's. One database call per insert
* is invoked, so that each insert goes through the lifecycle of model
* hooks.
*/
static fetchOrCreateMany(uniqueKeys: any, payload: any, options?: ModelAssignOptions): Promise<any[]>;
/**
* Update existing rows or create missing one's. One database call per insert
* is invoked, so that each insert and update goes through the lifecycle
* of model hooks.
*/
static updateOrCreateMany(uniqueKeys: any, payload: any, options?: ModelAssignOptions): Promise<any>;
/**
* Returns all rows from the model table
*/
static all(options?: ModelAdapterOptions): Promise<any>;
/**
* Truncate model table
*/
static truncate(cascade?: boolean): any;
constructor();
/**
* Custom options defined on the model instance that are
* passed to the adapter
*/
private modelOptions?;
/**
* Reference to transaction that will be used for performing queries on a given
* model instance.
*/
private modelTrx?;
/**
* The transaction listener listens for the `commit` and `rollback` events and
* cleansup the `$trx` reference
*/
private transactionListener;
/**
* When `fill` method is called, then we may have a situation where it
* removed the values which exists in `original` and hence the dirty
* diff has to do a negative diff as well
*/
private fillInvoked;
/**
* A copy of cached getters
*/
private cachedGetters;
/**
* Find if force updates are enabled
*/
private forceUpdate;
/**
* Raises exception when mutations are performed on a delete model
*/
private ensureIsntDeleted;
/**
* Invoked when performing the insert call. The method initiates
* all `datetime` columns, if there are not initiated already
* and `autoCreate` or `autoUpdate` flags are turned on.
*/
protected initiateAutoCreateColumns(): void;
/**
* Invoked when performing the update call. The method initiates
* all `datetime` columns, if there have `autoUpdate` flag
* turned on.
*/
protected initiateAutoUpdateColumns(): void;
/**
* Preparing the object to be sent to the adapter. We need
* to create the object with the property names to be
* used by the adapter.
*/
protected prepareForAdapter(attributes: ModelObject): {};
/**
* Returns true when the field must be included
* inside the serialized object.
*/
private shouldSerializeField;
/**
* A type only reference to the columns
*/
$columns: any;
/**
* A copy of attributes that will be sent over to adapter
*/
$attributes: ModelObject;
/**
* Original represents the properties that already has been
* persisted or loaded by the adapter.
*/
$original: ModelObject;
/**
* Preloaded relationships on the model instance
*/
$preloaded: {
[relation: string]: LucidRow | LucidRow[];
};
/**
* Extras are dynamic properties set on the model instance, which
* are not serialized and neither casted for adapter calls.
*
* This is helpful when adapter wants to load some extra data conditionally
* and that data must not be persisted back the adapter.
*/
$extras: ModelObject;
/**
* Sideloaded are dynamic properties set on the model instance, which
* are not serialized and neither casted for adapter calls.
*
* This is helpful when you want to add dynamic meta data to the model
* and it's children as well.
*
* The difference between [[extras]] and [[sideloaded]] is:
*
* - Extras can be different for each model instance
* - Extras are not shared down the hierarchy (example relationships)
* - Sideloaded are shared across multiple model instances created via `$createMultipleFromAdapterResult`.
* - Sideloaded are passed to the relationships as well.
*/
$sideloaded: ModelObject;
/**
* Persisted means the model has been persisted with the adapter. This will
* also be true, when model instance is created as a result of fetch
* call from the adapter.
*/
$isPersisted: boolean;
/**
* Once deleted the model instance cannot make calls to the adapter
*/
$isDeleted: boolean;
/**
* `$isLocal` tells if the model instance was created locally vs
* one generated as a result of fetch call from the adapter.
*/
$isLocal: boolean;
/**
* Returns the value of primary key. The value must be
* set inside attributes object
*/
get $primaryKeyValue(): any | undefined;
/**
* Opposite of [[this.isPersisted]]
*/
get $isNew(): boolean;
/**
* Returns dirty properties of a model by doing a diff
* between original values and current attributes
*/
get $dirty(): any;
/**
* Finding if model is dirty with changes or not
*/
get $isDirty(): boolean;
/**
* Returns the transaction
*/
get $trx(): TransactionClientContract | undefined;
/**
* Set the trx to be used by the model to executing queries
*/
set $trx(trx: TransactionClientContract | undefined);
/**
* Get options
*/
get $options(): ModelOptions | undefined;
/**
* Set options
*/
set $options(options: ModelOptions | undefined);
/**
* Set options on the model instance along with transaction
*/
$setOptionsAndTrx(options?: ModelAdapterOptions): void;
/**
* A chainable method to set transaction on the model
*/
useTransaction(trx: TransactionClientContract): this;
/**
* A chainable method to set transaction on the model
*/
useConnection(connection: string): this;
/**
* Set attribute
*/
$setAttribute(key: string, value: any): void;
/**
* Get value of attribute
*/
$getAttribute(key: string): any;
/**
* Returns the attribute value from the cache which was resolved by
* the mutated by a getter. This is done to avoid re-mutating
* the same attribute value over and over again.
*/
$getAttributeFromCache(key: string, callback: CacheNode['getter']): any;
/**
* Returns the related model or default value when model is missing
*/
$getRelated(key: any): any;
/**
* A boolean to know if relationship has been preloaded or not
*/
$hasRelated(key: any): boolean;
/**
* Sets the related data on the model instance. The method internally handles
* `one to one` or `many` relations
*/
$setRelated(key: any, models: LucidRow | LucidRow[]): void;
/**
* Push related adds to the existing related collection
*/
$pushRelated(key: any, models: LucidRow | LucidRow[]): void;
/**
* Merges the object with the model attributes, assuming object keys
* are coming the database.
*
* 1. If key is unknown, it will be added to the `extras` object.
* 2. If key is defined as a relationship, it will be ignored and one must call `$setRelated`.
*/
$consumeAdapterResult(adapterResult: ModelObject, sideloadedAttributes?: ModelObject): void;
/**
* Sync originals with the attributes. After this `isDirty` will
* return false
*/
$hydrateOriginals(): void;
/**
* Set bulk attributes on the model instance. Setting relationships via
* fill isn't allowed, since we disallow setting relationships
* locally
*/
fill(values: any, allowExtraProperties?: boolean): this;
/**
* Merge bulk attributes with existing attributes.
*
* 1. If key is unknown, it will be added to the `extras` object.
* 2. If key is defined as a relationship, it will be ignored and one must call `$setRelated`.
*/
merge(values: any, allowExtraProperties?: boolean): this;
/**
* Enable force update even when no attributes
* are dirty
*/
enableForceUpdate(): this;
/**
* Preloads one or more relationships for the current model
*/
load(relationName: any, callback?: any): Promise<void>;
/**
* @deprecated
*/
preload(relationName: any, callback?: any): Promise<void>;
/**
* Lazy load the relationship aggregate value
*/
loadAggregate(relationName: any, callback?: any): LazyLoadAggregates<this>;
/**
* Lazy load the relationship count value
*/
loadCount(relationName: any, callback?: any): LazyLoadAggregates<this>;
/**
* Perform save on the model instance to commit mutations.
*/
save(): Promise<this>;
/**
* Perform delete by issuing a delete request on the adapter
*/
delete(): Promise<void>;
/**
* Serializes model attributes to a plain object
*/
serializeAttributes(fields?: CherryPickFields, raw?: boolean): ModelObject;
/**
* Serializes model compute properties to an object.
*/
serializeComputed(fields?: CherryPickFields): ModelObject;
/**
* Serializes relationships to a plain object. When `raw=true`, it will
* recurisvely serialize the relationships as well.
*/
serializeRelations(cherryPick?: CherryPick['relations'], raw?: boolean): ModelObject | {
[key: string]: LucidRow | LucidRow[];
};
/**
* Converting model to it's JSON representation
*/
serialize(cherryPick?: CherryPick): any;
/**
* Convert model to a plain Javascript object
*/
toObject(): {
$extras: ModelObject;
};
/**
* Returns the serialize method output. However, any model can overwrite
* it to define it's custom serialize output
*/
toJSON(): any;
/**
* Returns the query for `insert`, `update` or `delete` actions.
* Since the query builder for these actions are not exposed to
* the end user, this method gives a way to compose queries.
*/
$getQueryFor(action: 'insert' | 'update' | 'delete' | 'refresh', client: QueryClientContract): any;
/**
* Returns an instance of relationship on the given model
*/
related(relationName: any): any;
/**
* Reload/Refresh the model instance
*/
refresh(): Promise<this>;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,9 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
/**
* A proxy trap to add support for custom getters and setters
*/
export declare const proxyHandler: {
get(target: any, key: any, receiver: any): any;
set(target: any, key: any, value: any, receiver: any): boolean;
defineProperty(target: any, key: any, value: any): boolean;
};
+72
View File
@@ -0,0 +1,72 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.proxyHandler = void 0;
/**
* A proxy trap to add support for custom getters and setters
*/
exports.proxyHandler = {
get(target, key, receiver) {
const Model = target.constructor;
const column = Model.$getColumn(key);
/**
* Fetch the attribute value, when attribute exists and
* doesn't have a getter
*/
if (column && !column.hasGetter) {
const attributeValue = target.$getAttribute(key);
if (attributeValue === undefined) {
return Reflect.get(target, key, receiver);
}
return attributeValue;
}
/**
* Fetch the relation when property is defined as a relationship
*/
const relation = Model.$getRelation(key);
if (relation) {
return target.$getRelated(key);
}
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const Model = target.constructor;
const column = Model.$getColumn(key);
/**
* Set value as an attribute when column is defined and
* their isn't any setter for it.
*/
if (column && !column.hasSetter) {
target.$setAttribute(key, value);
Reflect.set(target, key, value, receiver);
return true;
}
/**
* Fetch the relation when property is defined as a relationship
*/
const relation = Model.$getRelation(key);
if (relation) {
target.$setRelated(key, value);
return true;
}
return Reflect.set(target, key, value, receiver);
},
defineProperty(target, key, value) {
const Model = target.constructor;
const column = Model.$getColumn(key);
/**
* Set the attribute along side defining the property
*/
if (column && !column.hasSetter && value.value !== undefined) {
target.$setAttribute(key, value.value);
}
return Reflect.defineProperty(target, key, value);
},
};
+5
View File
@@ -0,0 +1,5 @@
import { DateColumnDecorator } from '@ioc:Adonis/Lucid/Orm';
/**
* Decorator to define a new date column
*/
export declare const dateColumn: DateColumnDecorator;
+96
View File
@@ -0,0 +1,96 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.dateColumn = void 0;
const luxon_1 = require("luxon");
const utils_1 = require("@poppinss/utils");
/**
* The method to prepare the date column before persisting it's
* value to the database
*/
function prepareDateColumn(value, attributeName, modelInstance) {
/**
* Return string or missing values as it is. If `auto` is set to true on
* the column, then the hook will always initialize the date
*/
if (typeof value === 'string' || !value) {
return value;
}
const modelName = modelInstance.constructor.name;
/**
* Format luxon instances to SQL formatted date
*/
if (luxon_1.DateTime.isDateTime(value)) {
if (!value.isValid) {
throw new utils_1.Exception(`Invalid value for "${modelName}.${attributeName}". ${value.invalidReason}`, 500, 'E_INVALID_DATE_COLUMN_VALUE');
}
return value.toISODate();
}
/**
* Anything else if not an acceptable value for date column
*/
throw new utils_1.Exception(`The value for "${modelName}.${attributeName}" must be an instance of "luxon.DateTime"`, 500, 'E_INVALID_DATE_COLUMN_VALUE');
}
/**
* Consume database return value and convert it to an instance of luxon.DateTime
*/
function consumeDateColumn(value, attributeName, modelInstance) {
/**
* Bypass null columns
*/
if (!value) {
return value;
}
/**
* Convert from string
*/
if (typeof value === 'string') {
return luxon_1.DateTime.fromSQL(value);
}
/**
* Convert from date
*/
if (value instanceof Date) {
return luxon_1.DateTime.fromJSDate(value);
}
/**
* Any another value cannot be formatted
*/
const modelName = modelInstance.constructor.name;
throw new utils_1.Exception(`Cannot format "${modelName}.${attributeName}" ${typeof value} value to an instance of "luxon.DateTime"`, 500, 'E_INVALID_DATE_COLUMN_VALUE');
}
/**
* Decorator to define a new date column
*/
const dateColumn = (options) => {
return function decorateAsColumn(target, property) {
const Model = target.constructor;
Model.boot();
const normalizedOptions = Object.assign({
prepare: prepareDateColumn,
consume: consumeDateColumn,
serialize: (value) => {
if (luxon_1.DateTime.isDateTime(value)) {
return value.toISODate();
}
return value;
},
meta: {},
}, options);
/**
* Type always has to be a date
*/
normalizedOptions.meta.type = 'date';
normalizedOptions.meta.autoCreate = normalizedOptions.autoCreate === true;
normalizedOptions.meta.autoUpdate = normalizedOptions.autoUpdate === true;
Model.$addColumn(property, normalizedOptions);
};
};
exports.dateColumn = dateColumn;
+5
View File
@@ -0,0 +1,5 @@
import { DateTimeColumnDecorator } from '@ioc:Adonis/Lucid/Orm';
/**
* Decorator to define a new date time column
*/
export declare const dateTimeColumn: DateTimeColumnDecorator;
+98
View File
@@ -0,0 +1,98 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.dateTimeColumn = void 0;
const luxon_1 = require("luxon");
const utils_1 = require("@poppinss/utils");
/**
* The method to prepare the datetime column before persisting it's
* value to the database
*/
function prepareDateTimeColumn(value, attributeName, modelInstance) {
/**
* Return string or missing values as it is. If `auto` is set to true on
* the column, then the hook will always initialize the date
*/
if (typeof value === 'string' || !value) {
return value;
}
const model = modelInstance.constructor;
const modelName = model.name;
/**
* Format luxon instances to SQL formatted date
*/
if (luxon_1.DateTime.isDateTime(value)) {
if (!value.isValid) {
throw new utils_1.Exception(`Invalid value for "${modelName}.${attributeName}". ${value.invalidReason}`, 500, 'E_INVALID_DATETIME_COLUMN_VALUE');
}
const dateTimeFormat = model.query(modelInstance.$options).client.dialect.dateTimeFormat;
return value.toFormat(dateTimeFormat);
}
/**
* Anything else if not an acceptable value for date column
*/
throw new utils_1.Exception(`The value for "${modelName}.${attributeName}" must be an instance of "luxon.DateTime"`, 500, 'E_INVALID_DATETIME_COLUMN_VALUE');
}
/**
* Consume database return value and convert it to an instance of luxon.DateTime
*/
function consumeDateTimeColumn(value, attributeName, modelInstance) {
/**
* Bypass null columns
*/
if (!value) {
return value;
}
/**
* Convert from string
*/
if (typeof value === 'string') {
return luxon_1.DateTime.fromSQL(value);
}
/**
* Convert from date
*/
if (value instanceof Date) {
return luxon_1.DateTime.fromJSDate(value);
}
/**
* Any another value cannot be formatted
*/
const modelName = modelInstance.constructor.name;
throw new utils_1.Exception(`Cannot format "${modelName}.${attributeName}" ${typeof value} value to an instance of "luxon.DateTime"`, 500, 'E_INVALID_DATETIME_COLUMN_VALUE');
}
/**
* Decorator to define a new date time column
*/
const dateTimeColumn = (options) => {
return function decorateAsColumn(target, property) {
const Model = target.constructor;
Model.boot();
const normalizedOptions = Object.assign({
prepare: prepareDateTimeColumn,
consume: consumeDateTimeColumn,
serialize: (value) => {
if (luxon_1.DateTime.isDateTime(value)) {
return value.toISO();
}
return value;
},
meta: {},
}, options);
/**
* Type always has to be a datetime
*/
normalizedOptions.meta.type = 'datetime';
normalizedOptions.meta.autoCreate = normalizedOptions.autoCreate === true;
normalizedOptions.meta.autoUpdate = normalizedOptions.autoUpdate === true;
Model.$addColumn(property, normalizedOptions);
};
};
exports.dateTimeColumn = dateTimeColumn;
+71
View File
@@ -0,0 +1,71 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import 'reflect-metadata';
import { HasOneDecorator, HasManyDecorator, HooksDecorator, ColumnDecorator, ComputedDecorator, BelongsToDecorator, DateColumnDecorator, ManyToManyDecorator, DateTimeColumnDecorator, HasManyThroughDecorator } from '@ioc:Adonis/Lucid/Orm';
/**
* Define property on a model as a column. The decorator needs a
* proper model class inheriting the base model
*/
export declare const column: ColumnDecorator & {
date: DateColumnDecorator;
dateTime: DateTimeColumnDecorator;
};
/**
* Define computed property on a model. The decorator needs a
* proper model class inheriting the base model
*/
export declare const computed: ComputedDecorator;
/**
* Define belongsTo relationship
*/
export declare const belongsTo: BelongsToDecorator;
/**
* Define hasOne relationship
*/
export declare const hasOne: HasOneDecorator;
/**
* Define hasMany relationship
*/
export declare const hasMany: HasManyDecorator;
/**
* Define manyToMany relationship
*/
export declare const manyToMany: ManyToManyDecorator;
/**
* Define hasManyThrough relationship
*/
export declare const hasManyThrough: HasManyThroughDecorator;
/**
* Before/After save hook
*/
export declare const beforeSave: HooksDecorator;
export declare const afterSave: HooksDecorator;
/**
* Before/After create hook
*/
export declare const beforeCreate: HooksDecorator;
export declare const afterCreate: HooksDecorator;
/**
* Before/After update hook
*/
export declare const beforeUpdate: HooksDecorator;
export declare const afterUpdate: HooksDecorator;
/**
* Before/After delete hook
*/
export declare const beforeDelete: HooksDecorator;
export declare const afterDelete: HooksDecorator;
/**
* Before/After find hook
*/
export declare const beforeFind: HooksDecorator;
export declare const afterFind: HooksDecorator;
/**
* Before/After fetchs hook
*/
export declare const beforeFetch: HooksDecorator;
export declare const afterFetch: HooksDecorator;
/**
* Before/After paginate hook
*/
export declare const beforePaginate: HooksDecorator;
export declare const afterPaginate: HooksDecorator;
+215
View File
@@ -0,0 +1,215 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.afterPaginate = exports.beforePaginate = exports.afterFetch = exports.beforeFetch = exports.afterFind = exports.beforeFind = exports.afterDelete = exports.beforeDelete = exports.afterUpdate = exports.beforeUpdate = exports.afterCreate = exports.beforeCreate = exports.afterSave = exports.beforeSave = exports.hasManyThrough = exports.manyToMany = exports.hasMany = exports.hasOne = exports.belongsTo = exports.computed = exports.column = void 0;
/// <reference path="../../../adonis-typings/index.ts" />
require("reflect-metadata");
const date_1 = require("./date");
const datetime_1 = require("./datetime");
/**
* Define property on a model as a column. The decorator needs a
* proper model class inheriting the base model
*/
const column = (options) => {
return function decorateAsColumn(target, property) {
const Model = target.constructor;
Model.boot();
Model.$addColumn(property, options || {});
};
};
exports.column = column;
exports.column.date = date_1.dateColumn;
exports.column.dateTime = datetime_1.dateTimeColumn;
/**
* Define computed property on a model. The decorator needs a
* proper model class inheriting the base model
*/
const computed = (options) => {
return function decorateAsComputed(target, property) {
const Model = target.constructor;
Model.boot();
Model.$addComputed(property, options || {});
};
};
exports.computed = computed;
/**
* Define belongsTo relationship
*/
const belongsTo = (relatedModel, relation) => {
return function decorateAsRelation(target, property) {
const Model = target.constructor;
Model.boot();
Model.$addRelation(property, 'belongsTo', relatedModel, Object.assign({ relatedModel }, relation));
};
};
exports.belongsTo = belongsTo;
/**
* Define hasOne relationship
*/
const hasOne = (relatedModel, relation) => {
return function decorateAsRelation(target, property) {
const Model = target.constructor;
Model.boot();
Model.$addRelation(property, 'hasOne', relatedModel, Object.assign({ relatedModel }, relation));
};
};
exports.hasOne = hasOne;
/**
* Define hasMany relationship
*/
const hasMany = (relatedModel, relation) => {
return function decorateAsRelation(target, property) {
const Model = target.constructor;
Model.boot();
Model.$addRelation(property, 'hasMany', relatedModel, Object.assign({ relatedModel }, relation));
};
};
exports.hasMany = hasMany;
/**
* Define manyToMany relationship
*/
const manyToMany = (relatedModel, relation) => {
return function decorateAsRelation(target, property) {
const Model = target.constructor;
Model.boot();
Model.$addRelation(property, 'manyToMany', relatedModel, Object.assign({ relatedModel }, relation));
};
};
exports.manyToMany = manyToMany;
/**
* Define hasManyThrough relationship
*/
const hasManyThrough = ([relatedModel, throughModel], relation) => {
return function decorateAsRelation(target, property) {
const Model = target.constructor;
Model.boot();
Model.$addRelation(property, 'hasManyThrough', relatedModel, Object.assign({ relatedModel, throughModel }, relation));
};
};
exports.hasManyThrough = hasManyThrough;
/**
* Before/After save hook
*/
const beforeSave = () => {
return function decorateAsHook(target, property) {
target.boot();
target.before('save', target[property].bind(target));
};
};
exports.beforeSave = beforeSave;
const afterSave = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.after('save', target[property].bind(target));
};
};
exports.afterSave = afterSave;
/**
* Before/After create hook
*/
const beforeCreate = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.before('create', target[property].bind(target));
};
};
exports.beforeCreate = beforeCreate;
const afterCreate = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.after('create', target[property].bind(target));
};
};
exports.afterCreate = afterCreate;
/**
* Before/After update hook
*/
const beforeUpdate = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.before('update', target[property].bind(target));
};
};
exports.beforeUpdate = beforeUpdate;
const afterUpdate = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.after('update', target[property].bind(target));
};
};
exports.afterUpdate = afterUpdate;
/**
* Before/After delete hook
*/
const beforeDelete = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.before('delete', target[property].bind(target));
};
};
exports.beforeDelete = beforeDelete;
const afterDelete = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.after('delete', target[property].bind(target));
};
};
exports.afterDelete = afterDelete;
/**
* Before/After find hook
*/
const beforeFind = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.before('find', target[property].bind(target));
};
};
exports.beforeFind = beforeFind;
const afterFind = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.after('find', target[property].bind(target));
};
};
exports.afterFind = afterFind;
/**
* Before/After fetchs hook
*/
const beforeFetch = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.before('fetch', target[property].bind(target));
};
};
exports.beforeFetch = beforeFetch;
const afterFetch = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.after('fetch', target[property].bind(target));
};
};
exports.afterFetch = afterFetch;
/**
* Before/After paginate hook
*/
const beforePaginate = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.before('paginate', target[property].bind(target));
};
};
exports.beforePaginate = beforePaginate;
const afterPaginate = () => {
return function decorateAsColumn(target, property) {
target.boot();
target.after('paginate', target[property].bind(target));
};
};
exports.afterPaginate = afterPaginate;
+25
View File
@@ -0,0 +1,25 @@
import { ModelKeysContract, ModelObject } from '@ioc:Adonis/Lucid/Orm';
/**
* Exposes the API to collect, get and resolve model keys
*/
export declare class ModelKeys implements ModelKeysContract {
private keys;
constructor(keys?: ModelObject);
/**
* Add a new key
*/
add(key: string, value: string): void;
/**
* Get value for a given key
*/
get(key: string, defaultValue: string): string;
/**
* Resolve key, if unable to resolve, the key will be
* returned as it is.
*/
resolve(key: string): string;
/**
* Return all keys
*/
all(): ModelObject;
}
+47
View File
@@ -0,0 +1,47 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelKeys = void 0;
/**
* Exposes the API to collect, get and resolve model keys
*/
class ModelKeys {
constructor(keys = {}) {
Object.defineProperty(this, "keys", {
enumerable: true,
configurable: true,
writable: true,
value: keys
});
}
/**
* Add a new key
*/
add(key, value) {
this.keys[key] = value;
}
get(key, defaultValue) {
return this.keys[key] || defaultValue;
}
/**
* Resolve key, if unable to resolve, the key will be
* returned as it is.
*/
resolve(key) {
return this.get(key, key);
}
/**
* Return all keys
*/
all() {
return this.keys;
}
}
exports.ModelKeys = ModelKeys;
@@ -0,0 +1,48 @@
import { NamingStrategyContract, LucidModel, ModelRelations } from '@ioc:Adonis/Lucid/Orm';
/**
* Uses snake case as the naming strategy for different model properties
*/
export declare class SnakeCaseNamingStrategy implements NamingStrategyContract {
/**
* The default table name for the given model
*/
tableName(model: LucidModel): string;
/**
* The database column name for a given model attribute
*/
columnName(_: LucidModel, attributeName: string): string;
/**
* The post serialization name for a given model attribute
*/
serializedName(_: LucidModel, attributeName: string): string;
/**
* The local key for a given model relationship
*/
relationLocalKey(relation: ModelRelations['__opaque_type'], model: LucidModel, relatedModel: LucidModel): string;
/**
* The foreign key for a given model relationship
*/
relationForeignKey(relation: ModelRelations['__opaque_type'], model: LucidModel, relatedModel: LucidModel): string;
/**
* Pivot table name for many to many relationship
*/
relationPivotTable(_: 'manyToMany', model: LucidModel, relatedModel: LucidModel): string;
/**
* Pivot foreign key for many to many relationship
*/
relationPivotForeignKey(_: 'manyToMany', model: LucidModel): string;
/**
* Keys for the pagination meta
*/
paginationMetaKeys(): {
total: string;
perPage: string;
currentPage: string;
lastPage: string;
firstPage: string;
firstPageUrl: string;
lastPageUrl: string;
nextPageUrl: string;
previousPageUrl: string;
};
}
@@ -0,0 +1,82 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SnakeCaseNamingStrategy = void 0;
const helpers_1 = require("@poppinss/utils/build/helpers");
/**
* Uses snake case as the naming strategy for different model properties
*/
class SnakeCaseNamingStrategy {
/**
* The default table name for the given model
*/
tableName(model) {
return helpers_1.string.pluralize(helpers_1.string.snakeCase(model.name));
}
/**
* The database column name for a given model attribute
*/
columnName(_, attributeName) {
return helpers_1.string.snakeCase(attributeName);
}
/**
* The post serialization name for a given model attribute
*/
serializedName(_, attributeName) {
return helpers_1.string.snakeCase(attributeName);
}
/**
* The local key for a given model relationship
*/
relationLocalKey(relation, model, relatedModel) {
if (relation === 'belongsTo') {
return relatedModel.primaryKey;
}
return model.primaryKey;
}
/**
* The foreign key for a given model relationship
*/
relationForeignKey(relation, model, relatedModel) {
if (relation === 'belongsTo') {
return helpers_1.string.camelCase(`${relatedModel.name}_${relatedModel.primaryKey}`);
}
return helpers_1.string.camelCase(`${model.name}_${model.primaryKey}`);
}
/**
* Pivot table name for many to many relationship
*/
relationPivotTable(_, model, relatedModel) {
return helpers_1.string.snakeCase([relatedModel.name, model.name].sort().join('_'));
}
/**
* Pivot foreign key for many to many relationship
*/
relationPivotForeignKey(_, model) {
return helpers_1.string.snakeCase(`${model.name}_${model.primaryKey}`);
}
/**
* Keys for the pagination meta
*/
paginationMetaKeys() {
return {
total: 'total',
perPage: 'per_page',
currentPage: 'current_page',
lastPage: 'last_page',
firstPage: 'first_page',
firstPageUrl: 'first_page_url',
lastPageUrl: 'last_page_url',
nextPageUrl: 'next_page_url',
previousPageUrl: 'previous_page_url',
};
}
}
exports.SnakeCaseNamingStrategy = SnakeCaseNamingStrategy;
+16
View File
@@ -0,0 +1,16 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import { ModelPaginatorContract, CherryPick } from '@ioc:Adonis/Lucid/Orm';
import { SimplePaginator } from '../../Database/Paginator/SimplePaginator';
/**
* Model paginator extends the simple paginator and adds support for
* serializing models as well
*/
export declare class ModelPaginator extends SimplePaginator implements ModelPaginatorContract<any> {
/**
* Serialize models
*/
serialize(cherryPick?: CherryPick): {
meta: any;
data: any[];
};
}
+28
View File
@@ -0,0 +1,28 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelPaginator = void 0;
const SimplePaginator_1 = require("../../Database/Paginator/SimplePaginator");
/**
* Model paginator extends the simple paginator and adds support for
* serializing models as well
*/
class ModelPaginator extends SimplePaginator_1.SimplePaginator {
/**
* Serialize models
*/
serialize(cherryPick) {
return {
meta: this.getMeta(),
data: this.all().map((row) => row.serialize(cherryPick)),
};
}
}
exports.ModelPaginator = ModelPaginator;
+51
View File
@@ -0,0 +1,51 @@
import { LucidRow, LucidModel, ModelObject, PreloaderContract } from '@ioc:Adonis/Lucid/Orm';
import { QueryClientContract } from '@ioc:Adonis/Lucid/Database';
/**
* Exposes the API to define and preload relationships in reference to
* a model
*/
export declare class Preloader implements PreloaderContract<LucidRow> {
private model;
private preloads;
/**
* When invoked via query builder. The preloader will get the sideloaded
* object, that should be transferred to relationship model instances.
*/
private sideloaded;
private debugQueries;
constructor(model: LucidModel);
/**
* Processes a relationship for a single parent
*/
private processRelation;
/**
* Process a given relationship for many parent instances. This happens
* during eagerloading
*/
private processRelationForMany;
/**
* Define a relationship to preload
*/
load(name: any, callback?: any): this;
/**
* Alias for "this.load"
*/
preload(name: any, callback?: any): this;
/**
* Toggle query debugging
*/
debug(debug: boolean): this;
/**
* Define attributes to be passed to all the model instance as
* sideloaded attributes
*/
sideload(values: ModelObject): this;
/**
* Process of all the preloaded relationships for a single parent
*/
processAllForOne(parent: LucidRow, client: QueryClientContract): Promise<void>;
/**
* Process of all the preloaded relationships for many parents
*/
processAllForMany(parent: LucidRow[], client: QueryClientContract): Promise<void>;
}
+154
View File
@@ -0,0 +1,154 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Preloader = void 0;
const utils_1 = require("@poppinss/utils");
/**
* Exposes the API to define and preload relationships in reference to
* a model
*/
class Preloader {
constructor(model) {
Object.defineProperty(this, "model", {
enumerable: true,
configurable: true,
writable: true,
value: model
});
Object.defineProperty(this, "preloads", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
/**
* When invoked via query builder. The preloader will get the sideloaded
* object, that should be transferred to relationship model instances.
*/
Object.defineProperty(this, "sideloaded", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "debugQueries", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
/**
* Processes a relationship for a single parent
*/
async processRelation(name, parent, client) {
const { relation, callback } = this.preloads[name];
const query = relation
.eagerQuery(parent, client)
.debug(this.debugQueries)
.sideload(this.sideloaded);
/**
* Pass query to end user for adding more constraints
*/
if (typeof callback === 'function') {
callback(query);
}
const result = await query.selectRelationKeys().exec();
/**
* hasOne and belongsTo will always return an array of a single row (if done right)
*/
if (relation.type === 'hasOne' || relation.type === 'belongsTo') {
relation.setRelated(parent, result[0] || null);
return;
}
/**
* Set array of related instances
*/
relation.setRelated(parent, result);
}
/**
* Process a given relationship for many parent instances. This happens
* during eagerloading
*/
async processRelationForMany(name, parent, client) {
const { relation, callback } = this.preloads[name];
const query = relation
.eagerQuery(parent, client)
.debug(this.debugQueries)
.sideload(this.sideloaded);
/**
* Pass query to end user for adding more constraints
*/
if (typeof callback === 'function') {
callback(query);
}
const result = await query.selectRelationKeys().exec();
/**
* Set array of related instances
*/
relation.setRelatedForMany(parent, result);
}
/**
* Define a relationship to preload
*/
load(name, callback) {
const relation = this.model.$getRelation(name);
if (!relation) {
throw new utils_1.Exception(`"${name}" is not defined as a relationship on "${this.model.name}" model`, 500, 'E_UNDEFINED_RELATIONSHIP');
}
relation.boot();
this.preloads[name] = {
relation: relation,
callback: callback,
};
return this;
}
/**
* Alias for "this.load"
*/
preload(name, callback) {
return this.load(name, callback);
}
/**
* Toggle query debugging
*/
debug(debug) {
this.debugQueries = debug;
return this;
}
/**
* Define attributes to be passed to all the model instance as
* sideloaded attributes
*/
sideload(values) {
this.sideloaded = values;
return this;
}
/**
* Process of all the preloaded relationships for a single parent
*/
async processAllForOne(parent, client) {
await Promise.all(Object.keys(this.preloads).map((relationName) => {
return this.processRelation(relationName, parent, client);
}));
}
/**
* Process of all the preloaded relationships for many parents
*/
async processAllForMany(parent, client) {
if (!parent.length) {
return;
}
await Promise.all(Object.keys(this.preloads).map((relationName) => {
return this.processRelationForMany(relationName, parent, client);
}));
}
}
exports.Preloader = Preloader;
+277
View File
@@ -0,0 +1,277 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import { Knex } from 'knex';
import { LucidRow, LucidModel, ModelObject, PreloaderContract, ModelAdapterOptions, RelationshipsContract, ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
import { DialectContract, DBQueryCallback, QueryClientContract, TransactionClientContract } from '@ioc:Adonis/Lucid/Database';
import { Chainable } from '../../Database/QueryBuilder/Chainable';
/**
* Database query builder exposes the API to construct and run queries for selecting,
* updating and deleting records.
*/
export declare class ModelQueryBuilder extends Chainable implements ModelQueryBuilderContract<LucidModel> {
model: LucidModel;
client: QueryClientContract;
/**
* Sideloaded attributes that will be passed to the model instances
*/
protected sideloaded: ModelObject;
/**
* A copy of defined preloads on the model instance
*/
protected preloader: PreloaderContract<LucidRow>;
/**
* A custom callback to transform each model row
*/
protected rowTransformerCallback: (row: LucidRow) => void;
/**
* Required by macroable
*/
protected static macros: {};
protected static getters: {};
/**
* A references to model scopes wrapper. It is lazily initialized
* only when the `apply` method is invoked
*/
private scopesWrapper;
/**
* Control whether or not to wrap adapter result to model
* instances or not
*/
protected wrapResultsToModelInstances: boolean;
/**
* Custom data someone want to send to the profiler and the
* query event
*/
protected customReporterData: any;
/**
* Control whether to debug the query or not. The initial
* value is inherited from the query client
*/
protected debugQueries: boolean;
/**
* Self join counter, increments with every "withCount"
* "has" and "whereHas" queries.
*/
private joinCounter;
/**
* Options that must be passed to all new model instances
*/
clientOptions: ModelAdapterOptions;
/**
* Whether or not query is a subquery for `.where` callback
*/
isChildQuery: boolean;
constructor(builder: Knex.QueryBuilder, model: LucidModel, client: QueryClientContract, customFn?: DBQueryCallback);
/**
* Executes the current query
*/
private execQuery;
/**
* Ensures that we are not executing `update` or `del` when using read only
* client
*/
private ensureCanPerformWrites;
/**
* Defines sub query for checking the existance of a relationship
*/
private addWhereHas;
/**
* Returns the profiler action. Protected, since the class is extended
* by relationships
*/
protected getQueryData(): {
connection: string;
inTransaction: boolean;
model: string;
};
/**
* Returns the relationship instance from the model. An exception is
* raised when relationship is missing
*/
protected getRelationship(name: string): RelationshipsContract;
/**
* Define custom reporter data. It will be merged with
* the existing data
*/
reporterData(data: any): this;
/**
* Define a custom callback to transform rows
*/
rowTransformer(callback: (row: LucidRow) => void): this;
/**
* Clone the current query builder
*/
clone(): ModelQueryBuilder;
/**
* Define returning columns
*/
returning(columns: any): this;
/**
* Define a query to constraint to be defined when condition is truthy
*/
ifDialect(dialects: DialectContract['name'] | DialectContract['name'][], matchCallback: (query: this) => any, noMatchCallback?: (query: this) => any): this;
/**
* Define a query to constraint to be defined when condition is falsy
*/
unlessDialect(dialects: DialectContract['name'] | DialectContract['name'][], matchCallback: (query: this) => any, noMatchCallback?: (query: this) => any): this;
/**
* Applies the query scopes on the current query builder
* instance
*/
withScopes(callback: (scopes: any) => void): this;
/**
* Applies the query scopes on the current query builder
* instance
*/
apply(callback: (scopes: any) => void): this;
/**
* Define a custom preloader instance for preloading relationships
*/
usePreloader(preloader: PreloaderContract<LucidRow>): this;
/**
* Set sideloaded properties to be passed to the model instance
*/
sideload(value: ModelObject): this;
/**
* Fetch and return first results from the results set. This method
* will implicitly set a `limit` on the query
*/
first(): Promise<any>;
/**
* Fetch and return first results from the results set. This method
* will implicitly set a `limit` on the query
*/
firstOrFail(): Promise<any>;
/**
* Load aggregate value as a subquery for a relationship
*/
withAggregate(relationName: any, userCallback: any): this;
/**
* Get count of a relationship along side the main query results
*/
withCount(relationName: any, userCallback?: any): this;
/**
* Add where constraint using the relationship
*/
whereHas(relationName: any, callback: any, operator?: string, value?: any): this;
/**
* Add or where constraint using the relationship
*/
orWhereHas(relationName: any, callback: any, operator?: string, value?: any): this;
/**
* Alias of [[whereHas]]
*/
andWhereHas(relationName: any, callback: any, operator?: string, value?: any): this;
/**
* Add where not constraint using the relationship
*/
whereDoesntHave(relationName: any, callback: any, operator?: string, value?: any): this;
/**
* Add or where not constraint using the relationship
*/
orWhereDoesntHave(relationName: any, callback: any, operator?: string, value?: any): this;
/**
* Alias of [[whereDoesntHave]]
*/
andWhereDoesntHave(relationName: any, callback: any, operator?: string, value?: any): this;
/**
* Add where constraint using the relationship
*/
has(relationName: any, operator?: string, value?: any): this;
/**
* Add or where constraint using the relationship
*/
orHas(relationName: any, operator?: string, value?: any): this;
/**
* Alias of [[has]]
*/
andHas(relationName: any, operator?: string, value?: any): this;
/**
* Add where not constraint using the relationship
*/
doesntHave(relationName: any, operator?: string, value?: any): this;
/**
* Add or where not constraint using the relationship
*/
orDoesntHave(relationName: any, operator?: string, value?: any): this;
/**
* Alias of [[doesntHave]]
*/
andDoesntHave(relationName: any, operator?: string, value?: any): this;
/**
* Define a relationship to be preloaded
*/
preload(relationName: any, userCallback?: any): this;
/**
* Perform update by incrementing value for a given column. Increments
* can be clubbed with `update` as well
*/
increment(column: any, counter?: any): any;
/**
* Perform update by decrementing value for a given column. Decrements
* can be clubbed with `update` as well
*/
decrement(column: any, counter?: any): any;
/**
* Perform update
*/
update(column: any, value?: any, returning?: string[]): any;
/**
* Delete rows under the current query
*/
del(): any;
/**
* Alias for [[del]]
*/
delete(): any;
/**
* Turn on/off debugging for this query
*/
debug(debug: boolean): this;
/**
* Define query timeout
*/
timeout(time: number, options?: {
cancel: boolean;
}): this;
/**
* Returns SQL query as a string
*/
toQuery(): string;
/**
* Run query inside the given transaction
*/
useTransaction(transaction: TransactionClientContract): this;
/**
* Executes the query
*/
exec(): Promise<any[]>;
/**
* Paginate through rows inside a given table
*/
paginate(page: number, perPage?: number): Promise<any>;
/**
* Get sql representation of the query
*/
toSQL(): Knex.Sql;
/**
* Get rows back as a plain javascript object and not an array
* of model instances
*/
pojo(): this;
/**
* Implementation of `then` for the promise API
*/
then(resolve: any, reject?: any): any;
/**
* Implementation of `catch` for the promise API
*/
catch(reject: any): any;
/**
* Implementation of `finally` for the promise API
*/
finally(fullfilled: any): Promise<any[]>;
/**
* Required when Promises are extended
*/
get [Symbol.toStringTag](): string;
}
+756
View File
@@ -0,0 +1,756 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelQueryBuilder = void 0;
const utils_1 = require("@poppinss/utils");
const utils_2 = require("../../utils");
const Preloader_1 = require("../Preloader");
const Paginator_1 = require("../Paginator");
const QueryRunner_1 = require("../../QueryRunner");
const Chainable_1 = require("../../Database/QueryBuilder/Chainable");
const SimplePaginator_1 = require("../../Database/Paginator/SimplePaginator");
/**
* A wrapper to invoke scope methods on the query builder
* underlying model
*/
class ModelScopes {
constructor(builder) {
Object.defineProperty(this, "builder", {
enumerable: true,
configurable: true,
writable: true,
value: builder
});
return new Proxy(this, {
get(target, key) {
if (typeof target.builder.model[key] === 'function') {
return (...args) => {
return target.builder.model[key](target.builder, ...args);
};
}
/**
* Unknown keys are not allowed
*/
throw new Error(`"${String(key)}" is not defined as a query scope on "${target.builder.model.name}" model`);
},
});
}
}
/**
* Database query builder exposes the API to construct and run queries for selecting,
* updating and deleting records.
*/
class ModelQueryBuilder extends Chainable_1.Chainable {
constructor(builder, model, client, customFn = (userFn) => {
return ($builder) => {
const subQuery = new ModelQueryBuilder($builder, this.model, this.client);
subQuery.isChildQuery = true;
userFn(subQuery);
subQuery.applyWhere();
};
}) {
super(builder, customFn, model.$keys.attributesToColumns.resolve.bind(model.$keys.attributesToColumns));
Object.defineProperty(this, "model", {
enumerable: true,
configurable: true,
writable: true,
value: model
});
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: client
});
/**
* Sideloaded attributes that will be passed to the model instances
*/
Object.defineProperty(this, "sideloaded", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
/**
* A copy of defined preloads on the model instance
*/
Object.defineProperty(this, "preloader", {
enumerable: true,
configurable: true,
writable: true,
value: new Preloader_1.Preloader(this.model)
});
/**
* A custom callback to transform each model row
*/
Object.defineProperty(this, "rowTransformerCallback", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* A references to model scopes wrapper. It is lazily initialized
* only when the `apply` method is invoked
*/
Object.defineProperty(this, "scopesWrapper", {
enumerable: true,
configurable: true,
writable: true,
value: undefined
});
/**
* Control whether or not to wrap adapter result to model
* instances or not
*/
Object.defineProperty(this, "wrapResultsToModelInstances", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
/**
* Custom data someone want to send to the profiler and the
* query event
*/
Object.defineProperty(this, "customReporterData", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Control whether to debug the query or not. The initial
* value is inherited from the query client
*/
Object.defineProperty(this, "debugQueries", {
enumerable: true,
configurable: true,
writable: true,
value: this.client.debug
});
/**
* Self join counter, increments with every "withCount"
* "has" and "whereHas" queries.
*/
Object.defineProperty(this, "joinCounter", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
/**
* Options that must be passed to all new model instances
*/
Object.defineProperty(this, "clientOptions", {
enumerable: true,
configurable: true,
writable: true,
value: {
client: this.client,
connection: this.client.connectionName,
profiler: this.client.profiler,
}
});
/**
* Whether or not query is a subquery for `.where` callback
*/
Object.defineProperty(this, "isChildQuery", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
/**
* Assign table when not already assigned
*/
if (!builder['_single'] || !builder['_single'].table) {
builder.table(model.table);
}
}
/**
* Executes the current query
*/
async execQuery() {
this.applyWhere();
const isWriteQuery = ['update', 'del', 'insert'].includes(this.knexQuery['_method']);
const queryData = Object.assign(this.getQueryData(), this.customReporterData);
const rows = await new QueryRunner_1.QueryRunner(this.client, this.debugQueries, queryData).run(this.knexQuery);
/**
* Return the rows as it is when query is a write query
*/
if (isWriteQuery || !this.wrapResultsToModelInstances) {
return Array.isArray(rows) ? rows : [rows];
}
/**
* Convert fetched results to an array of model instances
*/
const modelInstances = rows.reduce((models, row) => {
if ((0, utils_2.isObject)(row)) {
const modelInstance = this.model.$createFromAdapterResult(row, this.sideloaded, this.clientOptions);
/**
* Transform row when row transformer is defined
*/
if (this.rowTransformerCallback) {
this.rowTransformerCallback(modelInstance);
}
models.push(modelInstance);
}
return models;
}, []);
/**
* Preload for model instances
*/
await this.preloader
.sideload(this.sideloaded)
.debug(this.debugQueries)
.processAllForMany(modelInstances, this.client);
return modelInstances;
}
/**
* Ensures that we are not executing `update` or `del` when using read only
* client
*/
ensureCanPerformWrites() {
if (this.client && this.client.mode === 'read') {
throw new utils_1.Exception('Updates and deletes cannot be performed in read mode');
}
}
/**
* Defines sub query for checking the existance of a relationship
*/
addWhereHas(relationName, boolean, operator, value, callback) {
let rawMethod = 'whereRaw';
let existsMethod = 'whereExists';
switch (boolean) {
case 'or':
rawMethod = 'orWhereRaw';
existsMethod = 'orWhereExists';
break;
case 'not':
existsMethod = 'whereNotExists';
break;
case 'orNot':
rawMethod = 'orWhereRaw';
existsMethod = 'orWhereNotExists';
break;
}
const subQuery = this.getRelationship(relationName).subQuery(this.client);
subQuery.selfJoinCounter = this.joinCounter;
/**
* Invoke callback when defined
*/
if (typeof callback === 'function') {
callback(subQuery);
}
/**
* Count all when value and operator are defined.
*/
if (value !== undefined && operator !== undefined) {
/**
* If user callback has not defined any aggregates, then we should
* add a count
*/
if (!subQuery.hasAggregates) {
subQuery.count('*');
}
/**
* Pull sql and bindings from the query
*/
const { sql, bindings } = subQuery.prepare().toSQL();
/**
* Define where raw clause. Query builder doesn't have any "whereNotRaw" method
* and hence we need to prepend the `NOT` keyword manually
*/
boolean === 'orNot' || boolean === 'not'
? this[rawMethod](`not (${sql}) ${operator} (?)`, bindings.concat([value]))
: this[rawMethod](`(${sql}) ${operator} (?)`, bindings.concat([value]));
return this;
}
/**
* Use where exists when no operator and value is defined
*/
this[existsMethod](subQuery.prepare());
return this;
}
/**
* Returns the profiler action. Protected, since the class is extended
* by relationships
*/
getQueryData() {
return {
connection: this.client.connectionName,
inTransaction: this.client.isTransaction,
model: this.model.name,
};
}
/**
* Returns the relationship instance from the model. An exception is
* raised when relationship is missing
*/
getRelationship(name) {
const relation = this.model.$getRelation(name);
/**
* Ensure relationship exists
*/
if (!relation) {
throw new utils_1.Exception(`"${name}" is not defined as a relationship on "${this.model.name}" model`, 500, 'E_UNDEFINED_RELATIONSHIP');
}
relation.boot();
return relation;
}
/**
* Define custom reporter data. It will be merged with
* the existing data
*/
reporterData(data) {
this.customReporterData = data;
return this;
}
/**
* Define a custom callback to transform rows
*/
rowTransformer(callback) {
this.rowTransformerCallback = callback;
return this;
}
/**
* Clone the current query builder
*/
clone() {
const clonedQuery = new ModelQueryBuilder(this.knexQuery.clone(), this.model, this.client);
this.applyQueryFlags(clonedQuery);
clonedQuery.sideloaded = Object.assign({}, this.sideloaded);
clonedQuery.debug(this.debugQueries);
clonedQuery.reporterData(this.customReporterData);
this.rowTransformerCallback && this.rowTransformer(this.rowTransformerCallback);
return clonedQuery;
}
/**
* Define returning columns
*/
returning(columns) {
if (this.client.dialect.supportsReturningStatement) {
columns = Array.isArray(columns)
? columns.map((column) => this.resolveKey(column))
: this.resolveKey(columns);
this.knexQuery.returning(columns);
}
return this;
}
/**
* Define a query to constraint to be defined when condition is truthy
*/
ifDialect(dialects, matchCallback, noMatchCallback) {
dialects = Array.isArray(dialects) ? dialects : [dialects];
if (dialects.includes(this.client.dialect.name)) {
matchCallback(this);
}
else if (noMatchCallback) {
noMatchCallback(this);
}
return this;
}
/**
* Define a query to constraint to be defined when condition is falsy
*/
unlessDialect(dialects, matchCallback, noMatchCallback) {
dialects = Array.isArray(dialects) ? dialects : [dialects];
if (!dialects.includes(this.client.dialect.name)) {
matchCallback(this);
}
else if (noMatchCallback) {
noMatchCallback(this);
}
return this;
}
/**
* Applies the query scopes on the current query builder
* instance
*/
withScopes(callback) {
this.scopesWrapper = this.scopesWrapper || new ModelScopes(this);
callback(this.scopesWrapper);
return this;
}
/**
* Applies the query scopes on the current query builder
* instance
*/
apply(callback) {
return this.withScopes(callback);
}
/**
* Define a custom preloader instance for preloading relationships
*/
usePreloader(preloader) {
this.preloader = preloader;
return this;
}
/**
* Set sideloaded properties to be passed to the model instance
*/
sideload(value) {
this.sideloaded = value;
return this;
}
/**
* Fetch and return first results from the results set. This method
* will implicitly set a `limit` on the query
*/
async first() {
const isFetchCall = this.wrapResultsToModelInstances && this.knexQuery['_method'] === 'select';
if (isFetchCall) {
await this.model.$hooks.exec('before', 'find', this);
}
const result = await this.limit(1).execQuery();
if (result[0] && isFetchCall) {
await this.model.$hooks.exec('after', 'find', result[0]);
}
return result[0] || null;
}
/**
* Fetch and return first results from the results set. This method
* will implicitly set a `limit` on the query
*/
async firstOrFail() {
const row = await this.first();
if (!row) {
throw new utils_1.Exception('Row not found', 404, 'E_ROW_NOT_FOUND');
}
return row;
}
/**
* Load aggregate value as a subquery for a relationship
*/
withAggregate(relationName, userCallback) {
const subQuery = this.getRelationship(relationName).subQuery(this.client);
subQuery.selfJoinCounter = this.joinCounter;
/**
* Invoke user callback
*/
userCallback(subQuery);
/**
* Raise exception if the callback has not defined an aggregate
*/
if (!subQuery.hasAggregates) {
throw new utils_1.Exception('"withAggregate" callback must use an aggregate function');
}
/**
* Select "*" when no custom selects are defined
*/
if (!this.columns.length) {
this.select(`${this.model.table}.*`);
}
/**
* Throw exception when no alias
*/
if (!subQuery.subQueryAlias) {
throw new utils_1.Exception('"withAggregate" callback must define the alias for the aggregate query');
}
/**
* Count subquery selection
*/
this.select(subQuery.prepare());
/**
* Bump the counter
*/
this.joinCounter++;
return this;
}
/**
* Get count of a relationship along side the main query results
*/
withCount(relationName, userCallback) {
this.withAggregate(relationName, (subQuery) => {
if (typeof userCallback === 'function') {
userCallback(subQuery);
}
/**
* Count "*"
*/
if (!subQuery.hasAggregates) {
subQuery.count('*');
}
/**
* Define alias for the subquery
*/
if (!subQuery.subQueryAlias) {
subQuery.as(`${relationName}_count`);
}
});
return this;
}
/**
* Add where constraint using the relationship
*/
whereHas(relationName, callback, operator, value) {
return this.addWhereHas(relationName, 'and', operator, value, callback);
}
/**
* Add or where constraint using the relationship
*/
orWhereHas(relationName, callback, operator, value) {
return this.addWhereHas(relationName, 'or', operator, value, callback);
}
/**
* Alias of [[whereHas]]
*/
andWhereHas(relationName, callback, operator, value) {
return this.addWhereHas(relationName, 'and', operator, value, callback);
}
/**
* Add where not constraint using the relationship
*/
whereDoesntHave(relationName, callback, operator, value) {
return this.addWhereHas(relationName, 'not', operator, value, callback);
}
/**
* Add or where not constraint using the relationship
*/
orWhereDoesntHave(relationName, callback, operator, value) {
return this.addWhereHas(relationName, 'orNot', operator, value, callback);
}
/**
* Alias of [[whereDoesntHave]]
*/
andWhereDoesntHave(relationName, callback, operator, value) {
return this.addWhereHas(relationName, 'not', operator, value, callback);
}
/**
* Add where constraint using the relationship
*/
has(relationName, operator, value) {
return this.addWhereHas(relationName, 'and', operator, value);
}
/**
* Add or where constraint using the relationship
*/
orHas(relationName, operator, value) {
return this.addWhereHas(relationName, 'or', operator, value);
}
/**
* Alias of [[has]]
*/
andHas(relationName, operator, value) {
return this.addWhereHas(relationName, 'and', operator, value);
}
/**
* Add where not constraint using the relationship
*/
doesntHave(relationName, operator, value) {
return this.addWhereHas(relationName, 'not', operator, value);
}
/**
* Add or where not constraint using the relationship
*/
orDoesntHave(relationName, operator, value) {
return this.addWhereHas(relationName, 'orNot', operator, value);
}
/**
* Alias of [[doesntHave]]
*/
andDoesntHave(relationName, operator, value) {
return this.addWhereHas(relationName, 'not', operator, value);
}
/**
* Define a relationship to be preloaded
*/
preload(relationName, userCallback) {
this.preloader.load(relationName, userCallback);
return this;
}
/**
* Perform update by incrementing value for a given column. Increments
* can be clubbed with `update` as well
*/
increment(column, counter) {
this.ensureCanPerformWrites();
this.knexQuery.increment(this.resolveKey(column, true), counter);
return this;
}
/**
* Perform update by decrementing value for a given column. Decrements
* can be clubbed with `update` as well
*/
decrement(column, counter) {
this.ensureCanPerformWrites();
this.knexQuery.decrement(this.resolveKey(column, true), counter);
return this;
}
/**
* Perform update
*/
update(column, value, returning) {
this.ensureCanPerformWrites();
if (value === undefined && returning === undefined) {
this.knexQuery.update(this.resolveKey(column, true));
}
else if (returning === undefined) {
this.knexQuery.update(this.resolveKey(column), value);
}
else {
this.knexQuery.update(this.resolveKey(column), value, returning);
}
return this;
}
/**
* Delete rows under the current query
*/
del() {
this.ensureCanPerformWrites();
this.knexQuery.del();
return this;
}
/**
* Alias for [[del]]
*/
delete() {
return this.del();
}
/**
* Turn on/off debugging for this query
*/
debug(debug) {
this.debugQueries = debug;
return this;
}
/**
* Define query timeout
*/
timeout(time, options) {
this.knexQuery['timeout'](time, options);
return this;
}
/**
* Returns SQL query as a string
*/
toQuery() {
this.applyWhere();
return this.knexQuery.toQuery();
}
/**
* Run query inside the given transaction
*/
useTransaction(transaction) {
this.knexQuery.transacting(transaction.knexClient);
return this;
}
/**
* Executes the query
*/
async exec() {
const isFetchCall = this.wrapResultsToModelInstances && this.knexQuery['_method'] === 'select';
if (isFetchCall) {
await this.model.$hooks.exec('before', 'fetch', this);
}
const result = await this.execQuery();
if (isFetchCall) {
await this.model.$hooks.exec('after', 'fetch', result);
}
return result;
}
/**
* Paginate through rows inside a given table
*/
async paginate(page, perPage = 20) {
const isFetchCall = this.wrapResultsToModelInstances && this.knexQuery['_method'] === 'select';
/**
* Cast to number
*/
page = Number(page);
perPage = Number(perPage);
const countQuery = this.clone()
.clearOrder()
.clearLimit()
.clearOffset()
.clearSelect()
.count('* as total')
.pojo();
/**
* We pass both the counts query and the main query to the
* paginate hook
*/
if (isFetchCall) {
await this.model.$hooks.exec('before', 'paginate', [countQuery, this]);
await this.model.$hooks.exec('before', 'fetch', this);
}
const aggregateResult = await countQuery.exec();
const total = this.hasGroupBy ? aggregateResult.length : aggregateResult[0].total;
const results = total > 0 ? await this.forPage(page, perPage).execQuery() : [];
/**
* Choose paginator
*/
const paginator = this.wrapResultsToModelInstances
? new Paginator_1.ModelPaginator(total, perPage, page, ...results)
: new SimplePaginator_1.SimplePaginator(total, perPage, page, ...results);
paginator.namingStrategy = this.model.namingStrategy;
if (isFetchCall) {
await this.model.$hooks.exec('after', 'paginate', paginator);
await this.model.$hooks.exec('after', 'fetch', results);
}
return paginator;
}
/**
* Get sql representation of the query
*/
toSQL() {
this.applyWhere();
return this.knexQuery.toSQL();
}
/**
* Get rows back as a plain javascript object and not an array
* of model instances
*/
pojo() {
this.wrapResultsToModelInstances = false;
return this;
}
/**
* Implementation of `then` for the promise API
*/
then(resolve, reject) {
return this.exec().then(resolve, reject);
}
/**
* Implementation of `catch` for the promise API
*/
catch(reject) {
return this.exec().catch(reject);
}
/**
* Implementation of `finally` for the promise API
*/
finally(fullfilled) {
return this.exec().finally(fullfilled);
}
/**
* Required when Promises are extended
*/
get [Symbol.toStringTag]() {
return this.constructor.name;
}
}
exports.ModelQueryBuilder = ModelQueryBuilder;
/**
* Required by macroable
*/
Object.defineProperty(ModelQueryBuilder, "macros", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(ModelQueryBuilder, "getters", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
@@ -0,0 +1,37 @@
import { LucidRow, LazyLoadAggregatesContract } from '@ioc:Adonis/Lucid/Orm';
/**
* An implementation for lazy loading model relationship aggregates
*/
export declare class LazyLoadAggregates<Model extends LucidRow> implements LazyLoadAggregatesContract<Model> {
private model;
private query;
constructor(model: Model);
/**
* Load aggregate of relationship
*/
loadAggregate(relationName: any, userCallback?: any): this;
/**
* Load count of relationship
*/
loadCount(relationName: any, userCallback?: any): this;
/**
* Execute query
*/
exec(): Promise<void>;
/**
* Implementation of `then` for the promise API
*/
then(resolve: any, reject?: any): any;
/**
* Implementation of `catch` for the promise API
*/
catch(reject: any): any;
/**
* Implementation of `finally` for the promise API
*/
finally(fullfilled: any): Promise<void>;
/**
* Required when Promises are extended
*/
get [Symbol.toStringTag](): string;
}
@@ -0,0 +1,94 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LazyLoadAggregates = void 0;
/**
* An implementation for lazy loading model relationship aggregates
*/
class LazyLoadAggregates {
constructor(model) {
Object.defineProperty(this, "model", {
enumerable: true,
configurable: true,
writable: true,
value: model
});
Object.defineProperty(this, "query", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Model must be persisted before the lazy loading can happen
*/
const Model = this.model.constructor;
/**
* The "refresh" query has the where clause already assigned
*/
this.query = this.model.$getQueryFor('refresh', Model.$adapter.modelClient(this.model));
/**
* Selecting just the primary key
*/
this.query.select(Model.primaryKey);
}
/**
* Load aggregate of relationship
*/
loadAggregate(relationName, userCallback) {
this.query.withAggregate(relationName, userCallback);
return this;
}
/**
* Load count of relationship
*/
loadCount(relationName, userCallback) {
this.query.withCount(relationName, userCallback);
return this;
}
/**
* Execute query
*/
async exec() {
const result = await this.query.pojo().first();
if (!result) {
return;
}
/**
* Consume adapter result
*/
this.model.$consumeAdapterResult(result);
}
/**
* Implementation of `then` for the promise API
*/
then(resolve, reject) {
return this.exec().then(resolve, reject);
}
/**
* Implementation of `catch` for the promise API
*/
catch(reject) {
return this.exec().catch(reject);
}
/**
* Implementation of `finally` for the promise API
*/
finally(fullfilled) {
return this.exec().finally(fullfilled);
}
/**
* Required when Promises are extended
*/
get [Symbol.toStringTag]() {
return this.constructor.name;
}
}
exports.LazyLoadAggregates = LazyLoadAggregates;
@@ -0,0 +1,98 @@
import { Knex } from 'knex';
import { DBQueryCallback, QueryClientContract } from '@ioc:Adonis/Lucid/Database';
import { LucidRow, LucidModel, RelationshipsContract, ModelQueryBuilderContract, RelationQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
import { ModelQueryBuilder } from '../../QueryBuilder';
/**
* Base query builder for ORM Relationships
*/
export declare abstract class BaseQueryBuilder extends ModelQueryBuilder implements RelationQueryBuilderContract<LucidModel, LucidRow> {
/**
* Eager constraints
*/
protected groupConstraints: {
limit?: number;
orderBy?: {
column: string;
direction?: 'asc' | 'desc';
};
};
/**
* Is query a relationship query obtained using `related('relation').query()`
*/
get isRelatedQuery(): true;
/**
* Is query a relationship query obtained using `related('relation').subQuery()`
*/
get isRelatedSubQuery(): false;
/**
* Is query a relationship query obtained using one of the preload methods.
*/
isRelatedPreloadQuery: boolean;
constructor(builder: Knex.QueryBuilder, client: QueryClientContract, relation: RelationshipsContract, dbCallback: DBQueryCallback);
/**
* Returns the selected columns
*/
protected getSelectedColumns(): undefined | {
grouping: 'columns';
value: any[];
};
/**
* Returns the profiler action. Protected, since the class is extended
* by relationships
*/
protected getQueryData(): Knex.Sql & {
connection: string;
inTransaction: boolean;
model: string;
eagerLoading: boolean;
relation: any;
};
/**
* Profiler data for the relationship
*/
protected abstract profilerData(): any;
/**
* Returns the sql query keys for the join query
*/
protected abstract getRelationKeys(): string[];
/**
* The relationship query builder must implement this method
* to apply relationship related constraints
*/
protected abstract applyConstraints(): void;
/**
* Must be implemented by relationships to return query which
* handles the limit with eagerloading.
*/
protected abstract getGroupLimitQuery(): never | ModelQueryBuilderContract<LucidModel>;
/**
* Returns the name of the query action. Used mainly for
* raising descriptive errors
*/
protected queryAction(): string;
/**
* Selects the relation keys. Invoked by the preloader
*/
selectRelationKeys(): this;
/**
* Define the group limit
*/
groupLimit(limit: number): this;
/**
* Define the group limit
*/
groupOrderBy(column: string, direction?: 'asc' | 'desc'): this;
/**
* Get query sql
*/
toSQL(): Knex.Sql;
/**
* Apply constraints before fetching the first
* row
*/
first(): Promise<any>;
/**
* Execute query
*/
exec(): Promise<any[]>;
}
@@ -0,0 +1,169 @@
"use strict";
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseQueryBuilder = void 0;
const QueryBuilder_1 = require("../../QueryBuilder");
/**
* Base query builder for ORM Relationships
*/
class BaseQueryBuilder extends QueryBuilder_1.ModelQueryBuilder {
constructor(builder, client, relation, dbCallback) {
super(builder, relation.relatedModel(), client, dbCallback);
/**
* Eager constraints
*/
Object.defineProperty(this, "groupConstraints", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
/**
* Is query a relationship query obtained using one of the preload methods.
*/
Object.defineProperty(this, "isRelatedPreloadQuery", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
}
/**
* Is query a relationship query obtained using `related('relation').query()`
*/
get isRelatedQuery() {
return true;
}
/**
* Is query a relationship query obtained using `related('relation').subQuery()`
*/
get isRelatedSubQuery() {
return false;
}
/**
* Returns the selected columns
*/
getSelectedColumns() {
return this.knexQuery['_statements'].find(({ grouping }) => grouping === 'columns');
}
/**
* Returns the profiler action. Protected, since the class is extended
* by relationships
*/
getQueryData() {
return Object.assign(this.knexQuery.toSQL(), {
connection: this.client.connectionName,
inTransaction: this.client.isTransaction,
model: this.model.name,
eagerLoading: this.isRelatedPreloadQuery,
relation: this.profilerData(),
});
}
/**
* Returns the name of the query action. Used mainly for
* raising descriptive errors
*/
queryAction() {
let action = this.knexQuery['_method'];
if (action === 'del') {
action = 'delete';
}
if (action === 'select' && this.isRelatedPreloadQuery) {
action = 'preload';
}
return action;
}
/**
* Selects the relation keys. Invoked by the preloader
*/
selectRelationKeys() {
const columns = this.getSelectedColumns();
/**
* No columns have been defined, we will let knex do it's job by
* adding `select *`
*/
if (!columns) {
return this;
}
/**
* Finally push relation columns to existing selected columns
*/
this.getRelationKeys().forEach((key) => {
key = this.resolveKey(key);
if (!columns.value.includes(key)) {
columns.value.push(key);
}
});
return this;
}
/**
* Define the group limit
*/
groupLimit(limit) {
this.groupConstraints.limit = limit;
return this;
}
/**
* Define the group limit
*/
groupOrderBy(column, direction) {
this.groupConstraints.orderBy = { column, direction };
return this;
}
/**
* Get query sql
*/
toSQL() {
this.applyConstraints();
if (this.isRelatedPreloadQuery) {
return this.groupConstraints.limit ? this.getGroupLimitQuery().toSQL() : super.toSQL();
}
/**
* Apply orderBy and limit on the standard query when not
* an eagerloading query
*/
if (this.groupConstraints.limit) {
this.limit(this.groupConstraints.limit);
}
if (this.groupConstraints.orderBy) {
this.orderBy(this.groupConstraints.orderBy.column, this.groupConstraints.orderBy.direction);
}
return super.toSQL();
}
/**
* Apply constraints before fetching the first
* row
*/
first() {
this.applyConstraints();
return super.first();
}
/**
* Execute query
*/
exec() {
this.applyConstraints();
if (this.isRelatedPreloadQuery) {
return this.groupConstraints.limit ? this.getGroupLimitQuery().exec() : super.exec();
}
/**
* Apply orderBy and limit on the standard query when not
* an eagerloading query
*/
if (this.groupConstraints.limit) {
this.limit(this.groupConstraints.limit);
}
if (this.groupConstraints.orderBy) {
this.orderBy(this.groupConstraints.orderBy.column, this.groupConstraints.orderBy.direction);
}
return super.exec();
}
}
exports.BaseQueryBuilder = BaseQueryBuilder;

Some files were not shown because too many files have changed in this diff Show More