mythix-orm 1.4.3 → 1.4.6

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/docs/Home.md CHANGED
@@ -1 +1,51 @@
1
- Welcome to the mythix-orm wiki!
1
+ # Welcome to the Mythix ORM wiki
2
+
3
+ ## Getting started
4
+
5
+ Here you will not only find documentation for the Mythix ORM API, but also articles (in the "Pages" section) that explain in detail the inner workings of Mythix ORM.
6
+
7
+ To start with, we highly recommend reading the following articles:
8
+
9
+ 1. [Query Engine](./QueryEngine)
10
+ 2. [Associations](./Associations)
11
+
12
+ These should give you a basic idea on how to work with Mythix ORM.
13
+
14
+ After you have read these articles and understand what you are working with, take a look at the API documentation to understand _what_ you have available to you, and how you can use it.
15
+
16
+ ## What Mythix ORM is (and is not)
17
+
18
+ Mythix ORM was designed and created to replace all the terrible other options that currently exist for Node. I mean, have you ever looked at the source code for some of the other popular ORM libraries out there? It is the stuff of nightmares!
19
+
20
+ I was tired of daily having to deal with malarky spewed by the other ORM libraries and their very poorly designed interfaces, their bloat, and their difficulty to setup and use (to say it mildly).
21
+
22
+ Though I firmly believe ORMs are--in nature--a poor and terrible fix for the plethora if crap us developers must go through daily trying to work with outdated and non-standard databases, they do still have their place in our world (until the database "situation" is resolved). So, while ORMs are still needed, why not at least use the best one available?
23
+
24
+ Meet Mythix ORM. It was designed from the ground-up to replace *all* other existing ORMs, forever. As part of the design, a few key decisions were made to make Mythix ORM a far better solution than the other "solutions" out there.
25
+
26
+ Let's go over those key design decisions real quick so we are all on the same page:
27
+
28
+ 1. Mythix ORM will never be bloated. It has a specific purpose, and it will stick to that purpose. It is an abstraction layer between different database types. It doesn't intend or pretend to be anything else. Mythix ORM is *bare bones*, just what you need, and nothing more. It is the intent and hope of the authors that Mythix ORM will grow a thriving community, and it was a deliberate design decision that the community would build extra libraries to add features to Mythix ORM. The motto over here in Mythix land is "take what you need, and nothing more".
29
+ 2. Mythix ORM is very extensible. Nearly every part of Mythix ORM can be overloaded, hijacked, or replaced entirely. Again, the intent is that the community will provide many cool features through extra libraries that *add* to Mythix ORM's feature set. It was also the intent of the authors who designed Mythix ORM to have it so developers could work with it *the way they like to*, without any of the mandates, and the "world **should** work this way" shenanigans. Mythix ORM is very powerful, and absolutely *will* allow you to shoot yourself in the foot--if you, as the developer, decide that is what you want to do.
30
+ 3. The primary author of this library--Wyatt Greenway--doesn't like the "black box" mentality. Encapsulation is absolutely a must for clean and stable code, but "black box"? Never. For this reason Mythix ORM was designed with no private variables, and nearly every method exists on a class, with the deliberate intent that methods could (and should) be overloaded to provide extra functionality. Please *do* overload methods, and change the behavior of Mythix ORM to fit your team needs. But *please* be a good citizen, and read and understand what you are doing and why before you do it. Mythix ORM will naturally change its interfaces over time (with appropriate versioning applied to each release), and it is up to the downstream developers to update and manage their custom code.
31
+ 4. Mythix ORM deliberately tries to abstract everything it can away from the database. Because of this, you will often find cases where you might need to do things differently then you are used to, or you might find some of our design decisions a little strange. ORMs are *supposed* to be an abstraction layer, so when I see other ORMs suggesting database specific code in their documentation it makes me shudder. Obviously there are times where this can not be avoided, and Mythix ORM does its best to handle these cases in an abstract way. However, there may be times where you just need to make a direct query to do something with your database, or use a custom literal, and that is okay. One area that you will immediately notice this abstraction is the field types. There is a shockingly small number of field types available in Mythix ORM, and this is deliberate. Mythix ORM will *never* supply database specific field types... if you need those, you can use literals, or you can define your own field types to suit your needs. The situation also isn't as bad as you might initially think. Take the `INTEGER` type for example. It is designed such that it can receive as an optional parameter the "number of bytes" needed to store a certain integer type--and then it is up to the specific connection you are using to decide how to implement said type. For example, Mythix ORM does not supply the MySQL specific types like `TINYINT`, `SMALLINT`, or `MEDIUMINT`. Instead, you always simply use `INTEGER`, and specify the number of bytes you need, and the MySQL connection will take care of the rest for you. Field types are just one example. Mythix ORM will abstract away everything it can by deliberate design.
32
+ 5. Mythix ORM and its API were deliberately designed to be as simple as possible, with all the complexity required tucked neatly away in the modular connection drivers. Deliberate intent and lots of thinking went into making the interface simple, yet flexible and powerful. For these reasons Mythix ORM is broken into many pieces, so the developer can pick and choose only what they want. Mythix ORM was also deliberately designed so that it could be run inside a browser. "Why in the heck would I ever do that?" I hear you asking. Well, Mythix ORM has more to offer than just being a layer between databases. Its model system, and especially its query system, are slick, and useful outside the context of server-only. I hope to one day see "browser based" connections that allow developers to use the same powerful query interface built into Mythix ORM inside the browser. I mean, how cool would it be to use the powerful built in query-engine inside the browser? Maybe to generate GraphQL? Or a custom query interface over HTTP? Let's think big and outside the box.
33
+ 6. SQL/NoSQL, who cares? From the ground up Mythix ORM was designed to support SQL and NoSQL (or even completely custom) databases. Connection drivers are painless to create, and the horizon is the limit. Currently Mythix ORM officially only has drivers for `PostgreSQL` and `SQLite`, but in the future it plans (and intends) to support every database on the planet, including file storage systems, and in-memory databases. If you don't see the database driver you are looking for, be patient, or better yet, help out! Before Mythix ORM is considered "done" it will have drivers for `MySQL`, `Microsoft SQL`, `Mongo`, `Snowflake`, `SOLR`, `ElasticSearch`, and many more.
34
+
35
+ Now let's take a little moment to discuss what Mythix ORM **is not**:
36
+
37
+ 1. Mythix ORM **is not** bloated, and it never intends to be. We will continue with the modular architecture, giving developers the ability to choose what they want to include (and what they don't want to include). This also means that you, as the developer, need to pay a little more attention. You can't just install Mythix ORM and away you go. You also need to select which database driver you need, and install it manually beside Mythix ORM.
38
+ 2. Mythix ORM **is not** "magical". In fact, we despise "magic" and won't have any of it. Everything must be clearly defined, and must visibly exist somewhere. Mythix ORM will not automatically create fields for you (i.e. `created_at` and `updated_at`), not even for relationships, polymorphic or not. The tedium you may come across was also considered, and accounted for. For example, if you *want* everyone of your models to have a `created_at`, and `updated_at` field, then simply create a base model that all of your other models inherit from, and on the base model itself you can define these "common" fields. The intent is to give the developer full control, while keeping all "magic" off the table.
39
+ 3. Mythix ORM **is not** opinionated. It is actually the opposite. It is the intent of the authors that developers will use and modify this library *exactly how they want to*. We don't make glaring statements like "We won't include a `toSQL` method because no one *should* ever use such a thing!", or silly statements like "You *should* do things our way... because we obviously know best". Ha! Whatever! No, we don't know what is best, and we openly admit it. You, **the developer**, know what is best, and we support you. Do want you want to do, and do it well. Make your mommy proud! Just remember that when you shoot yourself in the foot, it is *your foot*, not ours.
40
+ 4. Mythix ORM **is not** a swiss-army knife, batteries included, does everything under the sun type of library. It can do everything you need it to do however, and it does so in a modular, community supported way. If you want extra features, build them, or rely on what the community builds. Mythix ORM will deliberately always remain small, slim, sleek, and do exactly what it does the best it can, and nothing more.
41
+ 5. We--the authoring team of Mythix ORM--do **not** always know what is best. We know this, and are humble enough to admit it. This is one of the prime reasons behind deciding to lean heavily on the community for support and extra features. When the community builds something that thousands of people are using regularly, then--and only then--will we consider adding it as a core feature to Mythix ORM. In short, we want the community to drive the bus. We will take a back seat and see where it goes, cheering and supporting the entire trip.
42
+
43
+ ## Mythix certifications
44
+
45
+ Mythix, as a community of libraries, has a certification program. We will certify third party libraries, and we recommend that you select certified libraries first. Why are we doing this? Part of the mission Mythix has is to reengage engineers to actually be engineers, instead of StackOverflow monkeys. We can not even begin to tell you how many times we have wept over the stank we have seen in open source libraries. We would prefer that Mythix, as a community, not be involved in the stank of the rest of the world.
46
+
47
+ For this reason we will maintain a list of "certified" libraries, whose code bases have been groomed for cleanliness and correctness. We will also maintain a "black list" of libraries that use Mythix technologies, but that have severe stank. We welcome all developers, and hope to build a thriving community around Mythix technologies... but be warned, if you are a poor engineer and poop out poor code, then your project might end up on a blacklist...
48
+
49
+ The above statements are intended to inspire all developers to become better versions of themselves. Our world is a train wreck of smelly broken code (that is often popular, unfortunately). Mythix hopes to take a hand in changing the landscape, by lifting developers up and supporting good engineers, helping them standout as pillars of hope against the background noise.
50
+
51
+ We won't be rude, unfair, or bigoted. We will be precise, keen-eyed, care about what we build, and drive engineers to be better engineers. We like the word "accountable", and would like to hold ourselves and our community of engineers to a higher standard. Let's build great things together, not smelly things that everyone hates. Our certification and blacklists will simply serve as a means to try and prod engineers to do a better job, and to aspire to a higher standard.
@@ -383,8 +383,10 @@ Connection interface methods are methods which hand off the query to a connectio
383
383
  12. `NOT.LT(...)` (greater than or equal to `>=`)
384
384
  13. `LTE(...)` (less than or equal to `<=`)
385
385
  14. `NOT.LTE(...)` (greater than `>`)
386
- 15. `LIKE` **coming soon!**
387
- 16. `NOT.LIKE` **coming soon!**
386
+ 15. `LIKE(...)` (pattern match `LIKE '%something%'`)
387
+ 16. `LIKE(..., { caseSensitive: true })` (pattern match `LIKE '%something%'`) [only supported in PostgreSQL -- default is `ILIKE`]
388
+ 17. `NOT.LIKE(...)` (not matching pattern `NOT LIKE '%something%'`)
389
+ 18. `NOT.LIKE(..., { caseSensitive: true })` (not matching pattern `NOT LIKE '%something%'`) [only supported in PostgreSQL -- default is `NOT ILIKE`]
388
390
 
389
391
  ### Control
390
392
 
@@ -13,6 +13,10 @@ const Types = require('../types');
13
13
 
14
14
  const LiteralBase = Literals.LiteralBase;
15
15
 
16
+ /// ConnectionBase is the base class that
17
+ /// all connection classes should inherit from.
18
+ ///
19
+ /// Alias: Connection
16
20
  class ConnectionBase extends EventEmitter {
17
21
  static dialect = 'none';
18
22
 
@@ -226,10 +230,10 @@ class ConnectionBase extends EventEmitter {
226
230
  return SqlString.escape(value);
227
231
  }
228
232
 
229
- escape(field, _value) {
233
+ escape(field, _value, options) {
230
234
  var value = _value;
231
235
  if (LiteralBase.isLiteral(value))
232
- return value.toString(this);
236
+ return value.toString(this, options);
233
237
 
234
238
  value = field.type.serialize(value, this);
235
239
 
@@ -255,14 +259,14 @@ class ConnectionBase extends EventEmitter {
255
259
  return parts.map((part) => SqlString.escapeId(part).replace(/^`/, '"').replace(/`$/, '"')).join('.');
256
260
  }
257
261
 
258
- escapeID(value) {
262
+ escapeID(value, options) {
259
263
  if (LiteralBase.isLiteral(value))
260
- return value.toString(this.connection);
264
+ return value.toString(this.connection, options);
261
265
 
262
266
  return this._escapeID(value);
263
267
  }
264
268
 
265
- _averageLiteralToString(literal) {
269
+ _averageLiteralToString(literal, options) {
266
270
  if (!literal || !LiteralBase.isLiteral(literal))
267
271
  return;
268
272
 
@@ -270,10 +274,10 @@ class ConnectionBase extends EventEmitter {
270
274
  if (!queryGenerator)
271
275
  return;
272
276
 
273
- return queryGenerator._averageLiteralToString(literal);
277
+ return queryGenerator._averageLiteralToString(literal, options);
274
278
  }
275
279
 
276
- _countLiteralToString(literal) {
280
+ _countLiteralToString(literal, options) {
277
281
  if (!literal || !LiteralBase.isLiteral(literal))
278
282
  return;
279
283
 
@@ -281,10 +285,10 @@ class ConnectionBase extends EventEmitter {
281
285
  if (!queryGenerator)
282
286
  return;
283
287
 
284
- return queryGenerator._countLiteralToString(literal);
288
+ return queryGenerator._countLiteralToString(literal, options);
285
289
  }
286
290
 
287
- _distinctLiteralToString(literal) {
291
+ _distinctLiteralToString(literal, options) {
288
292
  if (!literal || !LiteralBase.isLiteral(literal))
289
293
  return;
290
294
 
@@ -292,10 +296,10 @@ class ConnectionBase extends EventEmitter {
292
296
  if (!queryGenerator)
293
297
  return;
294
298
 
295
- return queryGenerator._distinctLiteralToString(literal);
299
+ return queryGenerator._distinctLiteralToString(literal, options);
296
300
  }
297
301
 
298
- _maxLiteralToString(literal) {
302
+ _maxLiteralToString(literal, options) {
299
303
  if (!literal || !LiteralBase.isLiteral(literal))
300
304
  return;
301
305
 
@@ -303,10 +307,10 @@ class ConnectionBase extends EventEmitter {
303
307
  if (!queryGenerator)
304
308
  return;
305
309
 
306
- return queryGenerator._maxLiteralToString(literal);
310
+ return queryGenerator._maxLiteralToString(literal, options);
307
311
  }
308
312
 
309
- _minLiteralToString(literal) {
313
+ _minLiteralToString(literal, options) {
310
314
  if (!literal || !LiteralBase.isLiteral(literal))
311
315
  return;
312
316
 
@@ -314,10 +318,10 @@ class ConnectionBase extends EventEmitter {
314
318
  if (!queryGenerator)
315
319
  return;
316
320
 
317
- return queryGenerator._minLiteralToString(literal);
321
+ return queryGenerator._minLiteralToString(literal, options);
318
322
  }
319
323
 
320
- _sumLiteralToString(literal) {
324
+ _sumLiteralToString(literal, options) {
321
325
  if (!literal || !LiteralBase.isLiteral(literal))
322
326
  return;
323
327
 
@@ -325,24 +329,24 @@ class ConnectionBase extends EventEmitter {
325
329
  if (!queryGenerator)
326
330
  return;
327
331
 
328
- return queryGenerator._sumLiteralToString(literal);
332
+ return queryGenerator._sumLiteralToString(literal, options);
329
333
  }
330
334
 
331
- literalToString(literal) {
335
+ literalToString(literal, options) {
332
336
  if (Literals.AverageLiteral.isLiteralType(literal))
333
- return this._averageLiteralToString(literal);
337
+ return this._averageLiteralToString(literal, options);
334
338
  else if (Literals.CountLiteral.isLiteralType(literal))
335
- return this._countLiteralToString(literal);
339
+ return this._countLiteralToString(literal, options);
336
340
  else if (Literals.DistinctLiteral.isLiteralType(literal))
337
- return this._distinctLiteralToString(literal);
341
+ return this._distinctLiteralToString(literal, options);
338
342
  else if (Literals.MaxLiteral.isLiteralType(literal))
339
- return this._maxLiteralToString(literal);
343
+ return this._maxLiteralToString(literal, options);
340
344
  else if (Literals.MinLiteral.isLiteralType(literal))
341
- return this._minLiteralToString(literal);
345
+ return this._minLiteralToString(literal, options);
342
346
  else if (Literals.SumLiteral.isLiteralType(literal))
343
- return this._sumLiteralToString(literal);
347
+ return this._sumLiteralToString(literal, options);
344
348
  else if (Literals.Literal.isLiteralType(literal))
345
- return literal.toString(this);
349
+ return literal.toString(this, options);
346
350
 
347
351
  throw new Error(`${this.constructor.name}::literalToString: Unsupported literal ${literal}.`);
348
352
  }
@@ -3,11 +3,11 @@
3
3
  const LiteralFieldBase = require('./literal-field-base');
4
4
 
5
5
  class AverageLiteral extends LiteralFieldBase {
6
- toString(connection) {
6
+ toString(connection, options) {
7
7
  if (!connection)
8
8
  return `${this.constructor.name} {}`;
9
9
 
10
- return connection.literalToString(this);
10
+ return connection.literalToString(this, options);
11
11
  }
12
12
  }
13
13
 
@@ -7,11 +7,11 @@ class CountLiteral extends LiteralFieldBase {
7
7
  return false;
8
8
  }
9
9
 
10
- toString(connection) {
10
+ toString(connection, options) {
11
11
  if (!connection)
12
12
  return `${this.constructor.name} {}`;
13
13
 
14
- return connection.literalToString(this);
14
+ return connection.literalToString(this, options);
15
15
  }
16
16
  }
17
17
 
@@ -3,11 +3,11 @@
3
3
  const LiteralFieldBase = require('./literal-field-base');
4
4
 
5
5
  class DistinctLiteral extends LiteralFieldBase {
6
- toString(connection) {
6
+ toString(connection, options) {
7
7
  if (!connection)
8
8
  return `${this.constructor.name} {}`;
9
9
 
10
- return connection.literalToString(this);
10
+ return connection.literalToString(this, options);
11
11
  }
12
12
  }
13
13
 
@@ -40,6 +40,13 @@ class LiteralFieldBase extends LiteralBase {
40
40
 
41
41
  return `${definition.modelName}:${definition.fieldNames[0]}`;
42
42
  }
43
+
44
+ getField(connection) {
45
+ if (!this.definition)
46
+ return null;
47
+
48
+ return this.definitionToField(connection, this.definition);
49
+ }
43
50
  }
44
51
 
45
52
  module.exports = LiteralFieldBase;
@@ -3,11 +3,11 @@
3
3
  const LiteralFieldBase = require('./literal-field-base');
4
4
 
5
5
  class MaxLiteral extends LiteralFieldBase {
6
- toString(connection) {
6
+ toString(connection, options) {
7
7
  if (!connection)
8
8
  return `${this.constructor.name} {}`;
9
9
 
10
- return connection.literalToString(this);
10
+ return connection.literalToString(this, options);
11
11
  }
12
12
  }
13
13
 
@@ -3,11 +3,11 @@
3
3
  const LiteralFieldBase = require('./literal-field-base');
4
4
 
5
5
  class MinLiteral extends LiteralFieldBase {
6
- toString(connection) {
6
+ toString(connection, options) {
7
7
  if (!connection)
8
8
  return `${this.constructor.name} {}`;
9
9
 
10
- return connection.literalToString(this);
10
+ return connection.literalToString(this, options);
11
11
  }
12
12
  }
13
13
 
@@ -3,11 +3,11 @@
3
3
  const LiteralFieldBase = require('./literal-field-base');
4
4
 
5
5
  class SumLiteral extends LiteralFieldBase {
6
- toString(connection) {
6
+ toString(connection, options) {
7
7
  if (!connection)
8
8
  return `${this.constructor.name} {}`;
9
9
 
10
- return connection.literalToString(this);
10
+ return connection.literalToString(this, options);
11
11
  }
12
12
  }
13
13
 
@@ -20,10 +20,12 @@ class QueryGeneratorBase {
20
20
  });
21
21
  }
22
22
 
23
- stackAssign(obj, ...args) {
23
+ stackAssign(obj, ..._args) {
24
24
  let newObj = Object.create(obj || {});
25
+ let args = _args.filter(Boolean);
25
26
 
26
- Object.assign(newObj, ...args);
27
+ if (args.length > 0)
28
+ Object.assign(newObj, ...args);
27
29
 
28
30
  return newObj;
29
31
  }
@@ -159,8 +161,14 @@ class QueryGeneratorBase {
159
161
  continue;
160
162
 
161
163
  if (LiteralBase.isLiteral(field)) {
162
- let result = field.toString(this.connection);
163
- let projectionField = this.parseFieldProjection(result, true);
164
+ let result = field.toString(this.connection, options);
165
+ let projectionField;
166
+
167
+ if (typeof field.getField === 'function')
168
+ projectionField = field.getField(this.connection);
169
+ else
170
+ projectionField = this.parseFieldProjection(result, true);
171
+
164
172
  if (projectionField === result) {
165
173
  // not able to parse projection
166
174
  continue;
@@ -188,7 +196,7 @@ class QueryGeneratorBase {
188
196
  return orderFieldMap;
189
197
  }
190
198
 
191
- sortedProjectedFields(projectedFields) {
199
+ sortedProjectedFields(projectedFields, options) {
192
200
  return projectedFields.sort((a, b) => {
193
201
  // If either value is a distinct literal
194
202
  // then make certain it comes first in
@@ -197,8 +205,8 @@ class QueryGeneratorBase {
197
205
  const distinctSortOrder = (a, b) => {
198
206
  let x = LiteralBase.isLiteral(a);
199
207
  let y = LiteralBase.isLiteral(b);
200
- let xStr = (x) ? a.toString(this.connection) : a;
201
- let yStr = (y) ? b.toString(this.connection) : b;
208
+ let xStr = (x) ? a.toString(this.connection, options) : a;
209
+ let yStr = (y) ? b.toString(this.connection, options) : b;
202
210
  let xIsDistinct = (typeof xStr === 'string' && LITERAL_IS_DISTINCT_RE.test(xStr));
203
211
  let yIsDistinct = (typeof yStr === 'string' && LITERAL_IS_DISTINCT_RE.test(yStr));
204
212
 
@@ -353,34 +361,37 @@ class QueryGeneratorBase {
353
361
  let projectedFields = _projectedFields;
354
362
  let result;
355
363
 
356
- // If we are sub-selecting then only the last
357
- // field in the projection should be the projection
358
364
  if (options && options.isSubQuery) {
359
- result = result = Array.from(projectedFields.values());
365
+ // If we are sub-selecting then only one
366
+ // field in the projection is allowed
360
367
 
361
- let lastField = result[result.length - 1];
368
+ result = Array.from(projectedFields.values());
369
+ if (result.length !== 1)
370
+ throw new Error(`${this.constructor.name}::getProjectionFromQueryEngine: Only one field is allowed in the projection of a sub-query.`);
362
371
 
363
- if (LiteralBase.isLiteral(lastField))
364
- result = [ lastField ];
365
- else if (typeof lastField === 'string')
366
- result = [ lastField ];
372
+ let subQueryField = result[0];
373
+
374
+ if (LiteralBase.isLiteral(subQueryField))
375
+ result = [ subQueryField ];
376
+ else if (typeof subQueryField === 'string')
377
+ result = [ subQueryField ];
367
378
  else
368
- result = [ new Literals.DistinctLiteral(lastField.fullFieldName) ];
379
+ result = [ new Literals.DistinctLiteral(subQueryField.fullFieldName) ];
369
380
  } else {
381
+ projectedFields = addRequiredFieldsToProjection(projectedFields);
382
+
370
383
  // If distinct specifies a field, then
371
384
  // make sure that field isn't specified twice
372
385
  if (hasDistinct) {
373
386
  let distinctFieldName = hasDistinct.getFullyQualifiedFieldName();
374
387
  if (distinctFieldName)
375
388
  projectedFields.delete(distinctFieldName);
376
- } else {
377
- projectedFields = addRequiredFieldsToProjection(projectedFields);
378
389
  }
379
390
 
380
391
  result = Array.from(projectedFields.values());
381
392
 
382
393
  // Convert projection fields to array and sort
383
- result = this.sortedProjectedFields(result);
394
+ result = this.sortedProjectedFields(result, options);
384
395
 
385
396
  // Now prefix the projection fields with the distinct
386
397
  // literal if one exists on the query
@@ -439,7 +450,7 @@ class QueryGeneratorBase {
439
450
  if (hasDistinct && Literals.DistinctLiteral.isLiteralType(projectionValue))
440
451
  continue;
441
452
 
442
- let key = projectionValue.toString(this.connection);
453
+ let key = projectionValue.toString(this.connection, options);
443
454
  if (isAdding)
444
455
  projectedFields.set(key, projectionValue);
445
456
  else
@@ -553,8 +564,13 @@ class QueryGeneratorBase {
553
564
  return result;
554
565
  }
555
566
 
567
+ isFieldIdentifier(str) {
568
+ return (/^"[^"]+"."[^"]+"|"\w+:[\w.]+"/i).test(str);
569
+ }
570
+
556
571
  getProjectedFields(queryEngine, _options, asMap) {
557
- let queryProjection = this.getProjectionFromQueryEngine(queryEngine, _options);
572
+ let options = _options || {};
573
+ let queryProjection = this.getProjectionFromQueryEngine(queryEngine, options);
558
574
  let allProjectionFields = new Map();
559
575
 
560
576
  for (let i = 0, il = queryProjection.length; i < il; i++) {
@@ -563,15 +579,20 @@ class QueryGeneratorBase {
563
579
  continue;
564
580
 
565
581
  if (LiteralBase.isLiteral(projectionField)) {
566
- let result = projectionField.toString(this.connection);
567
- let fullFieldName = this.parseFieldProjection(result);
582
+ let result = projectionField.toString(this.connection, options);
583
+ let fullFieldName;
584
+
585
+ if (typeof projectionField.getFullyQualifiedFieldName === 'function')
586
+ fullFieldName = projectionField.getFullyQualifiedFieldName();
587
+ else
588
+ fullFieldName = this.parseFieldProjection(result);
589
+
568
590
  if (!fullFieldName)
569
591
  fullFieldName = result;
570
592
 
571
593
  if (fullFieldName && result)
572
594
  allProjectionFields.set(fullFieldName, result);
573
-
574
- if (result && !this.isFieldProjection(result))
595
+ else
575
596
  allProjectionFields.set(result, result);
576
597
 
577
598
  continue;
@@ -770,33 +791,33 @@ class QueryGeneratorBase {
770
791
  return queryRoot.slice(index);
771
792
  }
772
793
 
773
- _averageLiteralToString(literal) {
794
+ _averageLiteralToString(literal, options) {
774
795
  if (!literal || !LiteralBase.isLiteral(literal))
775
796
  return;
776
797
 
777
- let field = literal.definitionToField(this.connection, literal.definition);
798
+ let field = literal.getField(this.connection);
778
799
  let escapedFieldName;
779
800
 
780
801
  if (LiteralBase.isLiteral(field))
781
- escapedFieldName = field.toString(this.connection);
802
+ escapedFieldName = field.toString(this.connection, options);
782
803
  else
783
- escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
804
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
784
805
 
785
806
  return `AVG(${escapedFieldName})`;
786
807
  }
787
808
 
788
- _countLiteralToString(literal) {
809
+ _countLiteralToString(literal, options) {
789
810
  if (!literal || !LiteralBase.isLiteral(literal))
790
811
  return;
791
812
 
792
- let field = (literal.definition) ? literal.definitionToField(this.connection, literal.definition) : null;
813
+ let field = literal.getField(this.connection);
793
814
  let escapedFieldName;
794
815
 
795
816
  if (field) {
796
817
  if (LiteralBase.isLiteral(field))
797
- escapedFieldName = field.toString(this.connection);
818
+ escapedFieldName = field.toString(this.connection, options);
798
819
  else
799
- escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
820
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
800
821
  } else {
801
822
  escapedFieldName = '*';
802
823
  }
@@ -804,58 +825,58 @@ class QueryGeneratorBase {
804
825
  return `COUNT(${escapedFieldName})`;
805
826
  }
806
827
 
807
- _distinctLiteralToString(literal) {
828
+ _distinctLiteralToString(literal, options) {
808
829
  if (!literal || !LiteralBase.isLiteral(literal))
809
830
  return;
810
831
 
811
- let field = literal.definitionToField(this.connection, literal.definition);
832
+ let field = literal.getField(this.connection);
812
833
  if (LiteralBase.isLiteral(field))
813
- return `DISTINCT ${field.toString(this.connection)}`;
834
+ return `DISTINCT ${field.toString(this.connection, options)}`;
814
835
 
815
- return `DISTINCT ${this.getEscapedProjectionName(field.Model, field, literal.options)}`;
836
+ return `DISTINCT ${this.getEscapedProjectionName(field.Model, field, this.stackAssign(options, literal.options))}`;
816
837
  }
817
838
 
818
- _maxLiteralToString(literal) {
839
+ _maxLiteralToString(literal, options) {
819
840
  if (!literal || !LiteralBase.isLiteral(literal))
820
841
  return;
821
842
 
822
- let field = literal.definitionToField(this.connection, literal.definition);
843
+ let field = literal.getField(this.connection);
823
844
  let escapedFieldName;
824
845
 
825
846
  if (LiteralBase.isLiteral(field))
826
- escapedFieldName = field.toString(this.connection);
847
+ escapedFieldName = field.toString(this.connection, options);
827
848
  else
828
- escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
849
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
829
850
 
830
851
  return `MAX(${escapedFieldName})`;
831
852
  }
832
853
 
833
- _minLiteralToString(literal) {
854
+ _minLiteralToString(literal, options) {
834
855
  if (!literal || !LiteralBase.isLiteral(literal))
835
856
  return;
836
857
 
837
- let field = literal.definitionToField(this.connection, literal.definition);
858
+ let field = literal.getField(this.connection);
838
859
  let escapedFieldName;
839
860
 
840
861
  if (LiteralBase.isLiteral(field))
841
- escapedFieldName = field.toString(this.connection);
862
+ escapedFieldName = field.toString(this.connection, options);
842
863
  else
843
- escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
864
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
844
865
 
845
866
  return `MIN(${escapedFieldName})`;
846
867
  }
847
868
 
848
- _sumLiteralToString(literal) {
869
+ _sumLiteralToString(literal, options) {
849
870
  if (!literal || !LiteralBase.isLiteral(literal))
850
871
  return;
851
872
 
852
- let field = literal.definitionToField(this.connection, literal.definition);
873
+ let field = literal.getField(this.connection);
853
874
  let escapedFieldName;
854
875
 
855
876
  if (LiteralBase.isLiteral(field))
856
- escapedFieldName = field.toString(this.connection);
877
+ escapedFieldName = field.toString(this.connection, options);
857
878
  else
858
- escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
879
+ escapedFieldName = this.getEscapedColumnName(field.Model, field, this.stackAssign(options, literal.options));
859
880
 
860
881
  return `SUM(${escapedFieldName})`;
861
882
  }
package/lib/model.js CHANGED
@@ -6,12 +6,32 @@ const Type = require('./types/type');
6
6
  const DefaultHelpers = require('./types/helpers/default-helpers');
7
7
  const Field = require('./field');
8
8
 
9
- // Used as a unique key for cache
9
+ /// Used as a unique key for cache.
10
+ /// Caches will generally be stored in
11
+ /// a Map, and since a Map's keys can
12
+ /// be any instance, we use a "CacheKey"
13
+ /// object on Models that will change
14
+ /// when they are dirty. This will allow
15
+ /// downstream libraries to use this "dirty"
16
+ /// key as a cache key.
10
17
  class CacheKey {
18
+ /// A "number" argument is provided here
19
+ /// as a way to get an "id" from a CacheKey.
20
+ /// Another way to view this "number" is
21
+ /// a cache version. This number is incremented
22
+ /// every time a model's fields get dirty.
23
+ ///
24
+ /// Arguments:
25
+ /// number: number
26
+ /// An incrementing number, or "version" for the CacheKey
11
27
  constructor(number) {
12
28
  this.number = (number == null) ? 0 : (number.valueOf() + 1);
13
29
  }
14
30
 
31
+ /// Return the "number" or "version"
32
+ /// of this CacheKey.
33
+ ///
34
+ /// Return: number
15
35
  valueOf() {
16
36
  return this.number;
17
37
  }
@@ -85,9 +105,28 @@ function bindStaticWhereToModelClass(ModelClass) {
85
105
  });
86
106
  }
87
107
 
108
+ /// The base Model class for Mythix ORM.
109
+ /// Every Mythix ORM model should inherit
110
+ /// from this class. This class provides
111
+ /// support for all model operations in
112
+ /// Mythix ORM.
88
113
  class Model {
114
+ /// This property assists with type checking
115
+ ///
116
+ /// Type: boolean
89
117
  static _isMythixModel = true;
90
118
 
119
+ /// Use this method to check if a class
120
+ /// is a Mythix ORM model. It will return
121
+ /// `true` if the provided value is a class
122
+ /// that inherits from <see>Model</see>, or
123
+ /// if the provided value has an attribute
124
+ /// named `_isMythixModel` that is truthy.
125
+ ///
126
+ /// Return: boolean
127
+ /// Arguments:
128
+ /// value: Function
129
+ /// Value to check.
91
130
  static isModelClass(value) {
92
131
  if (!value)
93
132
  return false;
@@ -101,6 +140,20 @@ class Model {
101
140
  return false;
102
141
  }
103
142
 
143
+ /// Check to see if the provided value is
144
+ /// an *instance* of a Mythix ORM <see>Model</see>.
145
+ /// Unlike <see>Model.isModelClass</see>, which
146
+ /// checks if a *class* is a <see>Model</see>, this will check
147
+ /// to see if an *instance* is an instance of a
148
+ /// Mythix ORM <see>Model</see>. It will return
149
+ /// `true` if the provided value is an `instanceof`
150
+ /// <see>Model</see>, or if the values `constructor`
151
+ /// property has a truthy `_isMythixModel` property.
152
+ ///
153
+ /// Return: boolean
154
+ /// Arguments:
155
+ /// value: any
156
+ /// Value to check
104
157
  static isModel(value) {
105
158
  if (!value)
106
159
  return false;
@@ -114,6 +167,18 @@ class Model {
114
167
  return false;
115
168
  }
116
169
 
170
+ /// Like everywhere in Javascript, we can
171
+ /// call `.toString()` to a get a string
172
+ /// representation of our <see>Model</see>
173
+ /// *class*. The optional `showFields` argument,
174
+ /// if true, will list the models fields as well.
175
+ /// Without the `showFields` argument, the model
176
+ /// name alone will be returned as a string.
177
+ ///
178
+ /// Return: string
179
+ /// Arguments:
180
+ /// showFields?: boolean
181
+ /// If `true`, then list the models fields.
117
182
  static toString(showFields) {
118
183
  let fieldNames = [];
119
184
 
@@ -134,6 +199,30 @@ class Model {
134
199
  return this.toString((depth !== 0));
135
200
  }
136
201
 
202
+ /// Get the underlying connection bound to
203
+ /// the model's class. Connection binding is
204
+ /// optional, so this method can also be provided
205
+ /// a connection. If a connection is provided, then
206
+ /// it will simply be returned. Because Mythix ORM
207
+ /// has no globals, this is a design pattern to
208
+ /// supply a connection if you have one available,
209
+ /// or fallback to a "bound" connection (if one can
210
+ /// be found).
211
+ ///
212
+ /// Return: <see>Connection</see>
213
+ /// Arguments:
214
+ /// connection?: <see>Connection</see>
215
+ /// An optional connection that can be provided.
216
+ /// If provided, it will simply be returned.
217
+ /// Otherwise, if not provided, or a falsy value,
218
+ /// then attempt to fallback to the bound connection,
219
+ /// if any is available.
220
+ /// Note:
221
+ /// An underscore prefix on a method in Mythix ORM
222
+ /// implies that this method should not be overloaded
223
+ /// unless you know exactly what you are doing.
224
+ /// It also implies that there is another method
225
+ /// that can and should be overloaded instead.
137
226
  static _getConnection(_connection) {
138
227
  return _connection || this._mythixBoundConnection;
139
228
  }
@@ -4,16 +4,21 @@ const Nife = require('nife');
4
4
  const ProxyClass = require('../proxy-class');
5
5
  const QueryEngineBase = require('./query-engine-base');
6
6
 
7
- function addOperatorToQuery(name, inverseName, value) {
7
+ function addOperatorToQuery(name, inverseName, value, extraOptions) {
8
8
  if (Nife.instanceOf(value, 'object'))
9
9
  throw new Error(`QueryEngine::addOperatorToQuery: ${name}(${value}) makes no sense...`);
10
10
 
11
- this._addToQuery({
11
+ let conditionalParams = {
12
12
  condition: true,
13
13
  operator: name,
14
14
  inverseOperator: inverseName,
15
15
  value: this._fetchOperatorValue(value),
16
- });
16
+ };
17
+
18
+ if (extraOptions)
19
+ conditionalParams = Object.assign(conditionalParams, extraOptions);
20
+
21
+ this._addToQuery(conditionalParams);
17
22
 
18
23
  return this._fetchScope('model');
19
24
  }
@@ -83,12 +88,14 @@ class FieldScope extends QueryEngineBase {
83
88
  return addOperatorToQuery.call(this, 'LTE', 'GT', value);
84
89
  }
85
90
 
86
- LIKE(value) {
87
- return addOperatorToQuery.call(this, 'LIKE', 'NOT_LIKE', value);
91
+ LIKE(value, options) {
92
+ let caseSensitive = ((options && options.caseSensitive) === true);
93
+ return addOperatorToQuery.call(this, 'LIKE', 'NOT_LIKE', value, { caseSensitive });
88
94
  }
89
95
 
90
- NOT_LIKE(value) {
91
- return addOperatorToQuery.call(this, 'NOT_LIKE', 'LIKE', value);
96
+ NOT_LIKE(value, options) {
97
+ let caseSensitive = ((options && options.caseSensitive) === true);
98
+ return addOperatorToQuery.call(this, 'NOT_LIKE', 'LIKE', value, { caseSensitive });
92
99
  }
93
100
 
94
101
  [ProxyClass.MISSING](target, prop) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix-orm",
3
- "version": "1.4.3",
3
+ "version": "1.4.6",
4
4
  "description": "ORM for Mythix framework",
5
5
  "main": "lib/index.js",
6
6
  "type": "commonjs",