@igojs/db 6.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/examples/PaginatedOptimizedQueryExample.js +936 -0
- package/index.js +27 -0
- package/package.json +27 -0
- package/src/CacheStats.js +33 -0
- package/src/CachedQuery.js +40 -0
- package/src/DataTypes.js +23 -0
- package/src/Db.js +147 -0
- package/src/Model.js +261 -0
- package/src/PaginatedOptimizedQuery.js +902 -0
- package/src/PaginatedOptimizedSql.js +1352 -0
- package/src/Query.js +584 -0
- package/src/Schema.js +52 -0
- package/src/Sql.js +311 -0
- package/src/context.js +12 -0
- package/src/dbs.js +26 -0
- package/src/drivers/mysql.js +74 -0
- package/src/drivers/postgresql.js +70 -0
- package/src/migrations.js +140 -0
- package/test/AssociationsTest.js +301 -0
- package/test/CacheStatsTest.js +40 -0
- package/test/CachedQueryTest.js +49 -0
- package/test/JoinTest.js +207 -0
- package/test/ModelTest.js +510 -0
- package/test/PaginatedOptimizedQueryTest.js +1183 -0
- package/test/PerfTest.js +58 -0
- package/test/PostgreSqlTest.js +95 -0
- package/test/QueryTest.js +27 -0
- package/test/SimplifiedSyntaxTest.js +473 -0
- package/test/SqlTest.js +95 -0
- package/test/init.js +2 -0
package/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
const context = require('./src/context');
|
|
3
|
+
|
|
4
|
+
// Initialize @igojs/db with dependencies from @igojs/server
|
|
5
|
+
function init({ config, cache, logger, utils, errorhandler }) {
|
|
6
|
+
context.config = config;
|
|
7
|
+
context.cache = cache;
|
|
8
|
+
context.logger = logger;
|
|
9
|
+
context.utils = utils;
|
|
10
|
+
context.errorhandler = errorhandler;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
init,
|
|
15
|
+
get Model() { return require('./src/Model'); },
|
|
16
|
+
get Query() { return require('./src/Query'); },
|
|
17
|
+
get CachedQuery() { return require('./src/CachedQuery'); },
|
|
18
|
+
get Schema() { return require('./src/Schema'); },
|
|
19
|
+
get Sql() { return require('./src/Sql'); },
|
|
20
|
+
get Db() { return require('./src/Db'); },
|
|
21
|
+
get dbs() { return require('./src/dbs'); },
|
|
22
|
+
get migrations() { return require('./src/migrations'); },
|
|
23
|
+
get DataTypes() { return require('./src/DataTypes'); },
|
|
24
|
+
get CacheStats() { return require('./src/CacheStats'); },
|
|
25
|
+
get PaginatedOptimizedQuery() { return require('./src/PaginatedOptimizedQuery'); },
|
|
26
|
+
get PaginatedOptimizedSql() { return require('./src/PaginatedOptimizedSql'); },
|
|
27
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@igojs/db",
|
|
3
|
+
"version": "6.0.0-beta.1",
|
|
4
|
+
"description": "Igo ORM - Database abstraction layer for MySQL and PostgreSQL",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "mocha --exit 'test/**/*.js'"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"orm",
|
|
11
|
+
"mysql",
|
|
12
|
+
"postgresql",
|
|
13
|
+
"database"
|
|
14
|
+
],
|
|
15
|
+
"author": "@igocreate",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"lodash": "^4.17.21"
|
|
22
|
+
},
|
|
23
|
+
"optionalDependencies": {
|
|
24
|
+
"mysql2": "^3.15.3",
|
|
25
|
+
"pg": "^8.16.3"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const context = require('./context');
|
|
3
|
+
|
|
4
|
+
const NAMESPACE = '_cache_statistics';
|
|
5
|
+
|
|
6
|
+
//
|
|
7
|
+
module.exports.incr = (key, type) => {
|
|
8
|
+
const { cache } = context;
|
|
9
|
+
cache.incr(NAMESPACE, `${key}.${type}`);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
//
|
|
13
|
+
module.exports.getStats = async () => {
|
|
14
|
+
const { cache } = context;
|
|
15
|
+
const statistics = {};
|
|
16
|
+
|
|
17
|
+
await cache.scan(`${NAMESPACE}/*`, async (key) => {
|
|
18
|
+
key = key.substr(NAMESPACE.length + 1);
|
|
19
|
+
const value = await cache.get(NAMESPACE, key);
|
|
20
|
+
_.set(statistics, key, value);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
_.each(statistics, (statistic, key) => {
|
|
24
|
+
let { hits, misses } = statistic;
|
|
25
|
+
hits = hits || 0;
|
|
26
|
+
misses = misses || 0;
|
|
27
|
+
statistic.table = key;
|
|
28
|
+
statistic.total = hits + misses;
|
|
29
|
+
statistic.rate = Math.round(hits / statistic.total * 100);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return _.values(statistics);
|
|
33
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
const context = require('./context');
|
|
3
|
+
|
|
4
|
+
const Query = require('./Query');
|
|
5
|
+
const CacheStats = require('./CacheStats');
|
|
6
|
+
|
|
7
|
+
//
|
|
8
|
+
module.exports = class CachedQuery extends Query {
|
|
9
|
+
|
|
10
|
+
async runQuery() {
|
|
11
|
+
const { cache } = context;
|
|
12
|
+
const { query, schema } = this;
|
|
13
|
+
const sqlQuery = this.toSQL();
|
|
14
|
+
const db = this.getDb();
|
|
15
|
+
|
|
16
|
+
const namespace = '_cached.' + query.table;
|
|
17
|
+
|
|
18
|
+
if (query.verb !== 'select') {
|
|
19
|
+
cache.flush(namespace + '/*'); // non-blocking flush for performance
|
|
20
|
+
const result = await db.query(sqlQuery.sql, sqlQuery.params, query.options);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const key = JSON.stringify(sqlQuery);
|
|
25
|
+
let type = 'hits';
|
|
26
|
+
|
|
27
|
+
const result = await cache.fetch(
|
|
28
|
+
namespace,
|
|
29
|
+
key,
|
|
30
|
+
async () => {
|
|
31
|
+
type = 'misses';
|
|
32
|
+
return await db.query(sqlQuery.sql, sqlQuery.params, query.options);
|
|
33
|
+
},
|
|
34
|
+
schema.cache.ttl
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CacheStats.incr(query.table, type);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
};
|
package/src/DataTypes.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
const context = require('./context');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
default: {
|
|
8
|
+
set: _.identity,
|
|
9
|
+
get: _.identity,
|
|
10
|
+
},
|
|
11
|
+
boolean: {
|
|
12
|
+
set: value => (value === null || value === undefined) ? null : !!value,
|
|
13
|
+
get: value => (value === null || value === undefined) ? null : !!value,
|
|
14
|
+
},
|
|
15
|
+
json: {
|
|
16
|
+
set: value => context.utils.toJSON(value),
|
|
17
|
+
get: value => context.utils.fromJSON(value),
|
|
18
|
+
},
|
|
19
|
+
array: {
|
|
20
|
+
set: value => value && Array.isArray(value) ? value.join(',') : value,
|
|
21
|
+
get: value => value && value.split ? value.split(',') : []
|
|
22
|
+
},
|
|
23
|
+
};
|
package/src/Db.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
const context = require('./context');
|
|
4
|
+
|
|
5
|
+
// Dynamic driver loading
|
|
6
|
+
let loadedDriver = null;
|
|
7
|
+
const getDriver = (driverName) => {
|
|
8
|
+
if (!loadedDriver) {
|
|
9
|
+
loadedDriver = require(`./drivers/${driverName}`);
|
|
10
|
+
}
|
|
11
|
+
return loadedDriver;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
//
|
|
15
|
+
const logQuery = (sql, params, err) => {
|
|
16
|
+
const { logger, errorhandler } = context;
|
|
17
|
+
const _log = err ? logger.error : logger.info;
|
|
18
|
+
_log('Db.query: ' + sql);
|
|
19
|
+
if (params?.length) {
|
|
20
|
+
_log('With params: ' + params);
|
|
21
|
+
}
|
|
22
|
+
if (err) {
|
|
23
|
+
errorhandler.errorSQL(err);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
//
|
|
29
|
+
class Db {
|
|
30
|
+
|
|
31
|
+
constructor(name) {
|
|
32
|
+
const { config } = context;
|
|
33
|
+
this.name = name;
|
|
34
|
+
this.config = config[name];
|
|
35
|
+
this.driver = getDriver(this.config.driver);
|
|
36
|
+
this.connection = null;
|
|
37
|
+
this.config.migrations_dir = `sql/${this.name}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async init() {
|
|
41
|
+
const { config } = context;
|
|
42
|
+
this.pool = await this.driver.createPool(this.config);
|
|
43
|
+
this.connection = null;
|
|
44
|
+
this.TEST_ENV = config.env === 'test';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//
|
|
48
|
+
async getConnection() {
|
|
49
|
+
const { driver, pool, TEST_ENV } = this;
|
|
50
|
+
// if connection is in local storage
|
|
51
|
+
if (TEST_ENV && this.connection) {
|
|
52
|
+
// console.log('keep same connection');
|
|
53
|
+
return { connection: this.connection, keep: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const connection = await driver.getConnection(pool);
|
|
57
|
+
|
|
58
|
+
if (TEST_ENV) {
|
|
59
|
+
this.connection = connection;
|
|
60
|
+
}
|
|
61
|
+
return { connection, keep: false };
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//
|
|
66
|
+
async query(sql, params=[], options={}) {
|
|
67
|
+
const { logger } = context;
|
|
68
|
+
const { driver, config, TEST_ENV } = this;
|
|
69
|
+
const { dialect } = driver;
|
|
70
|
+
|
|
71
|
+
const runquery = async() => {
|
|
72
|
+
|
|
73
|
+
const { connection, keep } = await this.getConnection();
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
|
|
77
|
+
const result = await this.driver.query(connection, sql, params, options);
|
|
78
|
+
if (config.debugsql) {
|
|
79
|
+
logQuery(sql, params);
|
|
80
|
+
}
|
|
81
|
+
return dialect.getRows(result);
|
|
82
|
+
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (options.silent) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// log & rethrow error
|
|
88
|
+
logQuery(sql, params, err);
|
|
89
|
+
throw err;
|
|
90
|
+
|
|
91
|
+
} finally {
|
|
92
|
+
if (!keep) {
|
|
93
|
+
// console.log('query: release transaction');
|
|
94
|
+
driver.release(connection);
|
|
95
|
+
if (TEST_ENV) {
|
|
96
|
+
this.connection = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (this.pool) {
|
|
103
|
+
return await runquery();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
logger.info('Db.query: Trying to reinitialize db connection pool');
|
|
107
|
+
await this.init();
|
|
108
|
+
if (!this.pool) {
|
|
109
|
+
logger.error('could not create db connection pool');
|
|
110
|
+
} else {
|
|
111
|
+
return await runquery();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
//
|
|
118
|
+
async beginTransaction() {
|
|
119
|
+
const { driver } = this;
|
|
120
|
+
const { connection } = await this.getConnection();
|
|
121
|
+
await driver.beginTransaction(connection);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//
|
|
125
|
+
async commitTransaction() {
|
|
126
|
+
const { driver, TEST_ENV } = this;
|
|
127
|
+
const { connection } = await this.getConnection();
|
|
128
|
+
await driver.commit(connection);
|
|
129
|
+
driver.release(connection);
|
|
130
|
+
if (TEST_ENV) {
|
|
131
|
+
this.connection = null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
//
|
|
136
|
+
async rollbackTransaction() {
|
|
137
|
+
const { driver, TEST_ENV } = this;
|
|
138
|
+
const { connection } = await this.getConnection();
|
|
139
|
+
await driver.rollback(connection);
|
|
140
|
+
driver.release(connection);
|
|
141
|
+
if (TEST_ENV) {
|
|
142
|
+
this.connection = null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = Db;
|
package/src/Model.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const CachedQuery = require('./CachedQuery');
|
|
3
|
+
|
|
4
|
+
const Query = require('./Query');
|
|
5
|
+
const PaginatedOptimizedQuery = require('./PaginatedOptimizedQuery');
|
|
6
|
+
const Schema = require('./Schema');
|
|
7
|
+
const context = require('./context');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const newQuery = (constructor, verb) => {
|
|
11
|
+
if (constructor.schema.cache) {
|
|
12
|
+
return new CachedQuery(constructor, verb);
|
|
13
|
+
}
|
|
14
|
+
return new Query(constructor, verb);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Simple mixin implementation to set the schema as a static attribute
|
|
18
|
+
module.exports = function(schema) {
|
|
19
|
+
|
|
20
|
+
class Model {
|
|
21
|
+
|
|
22
|
+
constructor(values) {
|
|
23
|
+
_.assign(this, values);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//
|
|
27
|
+
assignValues(values) {
|
|
28
|
+
const keys = _.keys(Model.schema.colsByAttr);
|
|
29
|
+
_.assign(this, _.pick(values, keys));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// returns object with primary keys
|
|
33
|
+
primaryObject() {
|
|
34
|
+
return _.pick(this, this.constructor.schema.primary);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// update
|
|
38
|
+
async update(values) {
|
|
39
|
+
values.updated_at = new Date();
|
|
40
|
+
await this.beforeUpdate(values);
|
|
41
|
+
|
|
42
|
+
await newQuery(this.constructor, 'update')
|
|
43
|
+
.unscoped()
|
|
44
|
+
.values(values)
|
|
45
|
+
.where(this.primaryObject())
|
|
46
|
+
.execute();
|
|
47
|
+
|
|
48
|
+
if (this.constructor.schema.cache) {
|
|
49
|
+
const { cache } = context;
|
|
50
|
+
await cache.del('_cached.' + this.constructor.schema.table);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.assignValues(values);
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// reload
|
|
58
|
+
async reload(includes) {
|
|
59
|
+
const query = this.constructor.unscoped();
|
|
60
|
+
includes && query.includes(includes);
|
|
61
|
+
return await query.find(this.id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// delete
|
|
65
|
+
delete() {
|
|
66
|
+
return newQuery(this.constructor, 'delete').unscoped().where(this.primaryObject()).execute();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async beforeCreate() { }
|
|
70
|
+
async beforeUpdate(values) { }
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
// find by id
|
|
74
|
+
static async find(id) {
|
|
75
|
+
return await newQuery(this).find(id);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// create
|
|
79
|
+
static async create(values, options) {
|
|
80
|
+
const _this = this;
|
|
81
|
+
|
|
82
|
+
const now = new Date();
|
|
83
|
+
const obj = new this(values);
|
|
84
|
+
|
|
85
|
+
if (this.schema.subclasses && !obj[this.schema.subclass_column]) {
|
|
86
|
+
obj[this.schema.subclass_column] = _.findKey(this.schema.subclasses, { name: this.name });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
obj.created_at = obj.created_at || now;
|
|
90
|
+
obj.updated_at = obj.updated_at || now;
|
|
91
|
+
|
|
92
|
+
const create = async () => {
|
|
93
|
+
await obj.beforeCreate();
|
|
94
|
+
|
|
95
|
+
const query = newQuery(_this, 'insert').values(obj).options(options);
|
|
96
|
+
const result = await query.execute();
|
|
97
|
+
|
|
98
|
+
if (result.err) {
|
|
99
|
+
throw result.err;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { insertId } = result;
|
|
103
|
+
if (insertId) {
|
|
104
|
+
return _this.unscoped().find(insertId);
|
|
105
|
+
}
|
|
106
|
+
return _this.unscoped().find(obj.primaryObject());
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return await create();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
// return first
|
|
114
|
+
static first() {
|
|
115
|
+
return newQuery(this).first();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// return last
|
|
119
|
+
static last() {
|
|
120
|
+
return newQuery(this).last();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// return list
|
|
124
|
+
static list() {
|
|
125
|
+
return newQuery(this).list();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//
|
|
129
|
+
static select(select) {
|
|
130
|
+
return newQuery(this).select(select);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// filter
|
|
134
|
+
static where(where, params) {
|
|
135
|
+
return newQuery(this).where(where, params);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// filter
|
|
139
|
+
static whereNot(whereNot) {
|
|
140
|
+
return newQuery(this).whereNot(whereNot);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// limit
|
|
144
|
+
static limit(limit) {
|
|
145
|
+
return newQuery(this).limit(limit);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// offset
|
|
149
|
+
static offset(offset) {
|
|
150
|
+
return newQuery(this).offset(offset);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// page
|
|
154
|
+
static page(page, nb) {
|
|
155
|
+
return newQuery(this).page(page, nb);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// order
|
|
159
|
+
static order(order) {
|
|
160
|
+
return newQuery(this).order(order);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// distinct
|
|
164
|
+
static distinct(columns) {
|
|
165
|
+
return newQuery(this).distinct(columns);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// group
|
|
169
|
+
static group(columns) {
|
|
170
|
+
return newQuery(this).group(columns);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// count
|
|
174
|
+
static count() {
|
|
175
|
+
return newQuery(this).count();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// delete
|
|
179
|
+
static delete(id, ) {
|
|
180
|
+
return newQuery(this, 'delete').unscoped().where({ id: id }).execute();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// delete all
|
|
184
|
+
static async deleteAll() {
|
|
185
|
+
return newQuery(this, 'delete').unscoped().execute();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// destroy all
|
|
189
|
+
static destroyAll() {
|
|
190
|
+
return this.deleteAll();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
//
|
|
194
|
+
static update(values, ) {
|
|
195
|
+
values.updated_at = new Date();
|
|
196
|
+
return newQuery(this).unscoped().update(values, );
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// includes
|
|
200
|
+
static includes(includes) {
|
|
201
|
+
return newQuery(this).includes(includes);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// includes
|
|
205
|
+
static join(associationName, columns, type='LEFT') {
|
|
206
|
+
const query = newQuery(this);
|
|
207
|
+
if (_.isString(associationName)) {
|
|
208
|
+
return query.joinOne(associationName, columns, type);
|
|
209
|
+
} else if (_.isArray(associationName)) {
|
|
210
|
+
return query.joinMany(associationName, columns, type);
|
|
211
|
+
} else if (_.isObject(associationName)) {
|
|
212
|
+
return query.joinNested(associationName);
|
|
213
|
+
}
|
|
214
|
+
console.warn('Invalid join argument for Model.join(). Must be a string, array, or object.');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
//unscoped
|
|
218
|
+
static unscoped() {
|
|
219
|
+
return newQuery(this).unscoped();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
//scope
|
|
223
|
+
static scope(scope) {
|
|
224
|
+
return newQuery(this).scope(scope);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* paginatedOptimized - Retourne une PaginatedOptimizedQuery pour des requêtes optimisées avec pattern COUNT/IDS/FULL
|
|
229
|
+
*
|
|
230
|
+
* Cette méthode permet d'utiliser le pattern d'optimisation pour les requêtes avec de nombreuses jointures.
|
|
231
|
+
* Au lieu de faire un LEFT JOIN complet, on utilise :
|
|
232
|
+
* 1. COUNT avec EXISTS pour compter sans jointures
|
|
233
|
+
* 2. SELECT IDS pour récupérer les IDs avec filtres/tris/pagination
|
|
234
|
+
* 3. SELECT FULL pour récupérer les données complètes avec LEFT JOIN uniquement sur les IDs trouvés
|
|
235
|
+
*
|
|
236
|
+
* @returns {PaginatedOptimizedQuery} Instance de PaginatedOptimizedQuery
|
|
237
|
+
*
|
|
238
|
+
* Exemple d'utilisation :
|
|
239
|
+
*
|
|
240
|
+
* const result = await Folder.paginatedOptimized()
|
|
241
|
+
* .where({ type: ['agp', 'avt'] })
|
|
242
|
+
* .filterJoin('applicant', { last_name: 'Dupont%' }) // Filtre via EXISTS
|
|
243
|
+
* .join('pme_folder') // LEFT JOIN dans phase FULL
|
|
244
|
+
* .order('folders.created_at DESC')
|
|
245
|
+
* .page(1, 50);
|
|
246
|
+
*
|
|
247
|
+
* Performance :
|
|
248
|
+
* - Pour des tables de millions de lignes avec 10 jointures : amélioration de 1000x à 10000x
|
|
249
|
+
* - Le COUNT passe de plusieurs secondes à quelques millisecondes
|
|
250
|
+
* - Le SELECT bénéficie d'une pagination efficace sur les IDs seulement
|
|
251
|
+
*/
|
|
252
|
+
static paginatedOptimized() {
|
|
253
|
+
return new PaginatedOptimizedQuery(this);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
Model.schema = new Schema(schema);
|
|
259
|
+
|
|
260
|
+
return Model;
|
|
261
|
+
};
|