Commit e6dbea00 authored by Daniel Caillibaud's avatar Daniel Caillibaud

retag 2.4.11 | isNull autorisé sur les index sparse, ajout de tests (et doc)

parent 5be6f74c
{
"name": "lassi",
"version": "2.4.10",
"version": "2.4.11",
"private": true,
"license": "GPL-3.0",
"main": "./source/Lassi",
......
......@@ -166,6 +166,7 @@ class Entity {
// => si la propriété n'existe pas dans l'objet elle sera indexée quand même avec une valeur null
// => si la propriété vaut undefined elle sera indexée avec null
// - sparse : seulement les documents ayant la propriété (même null|undefined) sont indexés (undefined indexé avec la valeur null)
//
// Afin d'avoir un comportement homogène, buildIndexes va harmoniser les 3 cas
// - prop absente
// - prop avec valeur undefined
......@@ -175,7 +176,7 @@ class Entity {
// dans les 3 cas isNull remontera l'entity.
// 2) si index sparse, on supprime l'index pour null|undefined
// => dans les 3 cas le doc mongo n'est pas indexé
// => isNull ne remontera jamais rien, il throw pour s'assurer qu'on ne l'utilise jamais dans ce cas
// => isNull remonte les 3
return
}
......@@ -250,7 +251,7 @@ class Entity {
/**
* Retourne la valeur de l'attribut au dernier chargement depuis la base
* @throws si y'a pas eu de EntityDefinitiontrackAttribute(attribute) sur cette Entity
* @throws si y'a pas eu de EntityDefinition.trackAttribute(attribute) sur cette Entity pour cet attribut
* @param {string} attribute
* @return {*} null si l'entity ne sort pas de la db
*/
......
......@@ -114,7 +114,7 @@ class EntityDefinition {
/**
* Finalisation de l'objet Entité, appelé en fin de définition, avant initialize
* @param {Entities} entities le conteneur d'entités.
* @return {Entity} l'entité (chaînable)
* @return {EntityDefinition} pour chaînage éventuel
* @private
*/
_bless (entities) {
......@@ -402,7 +402,7 @@ class EntityDefinition {
* toutes les propriétés de values seront affectée à l'entité !
* @todo virer ce comportement et ajouter dans les constructeurs qui l'utilisaient un `Object.assign(this, values)`
* @param {Object=} values Des valeurs à injecter dans l'objet.
* @return {Entity} Une instance d'entité
* @return {Entity} Une instance de l'entité
*/
create (values) {
var instance = new this.entityConstructor() // eslint-disable-line new-cap
......@@ -449,12 +449,19 @@ class EntityDefinition {
* });
* ```
*
* @param {String} indexName Nom du champ à indexer ou de l'index virtuel
* @param {String} fieldType (optionnel) Type du champ à indexer ('integer', 'string', 'date')
* @param {String} indexName Nom du champ à indexer (ou de l'index virtuel si on passe une callback)
* @param {String} [fieldType] Type du champ à indexer ('integer', 'string', 'date')
* ce qui va entrainer du cast à l'indexation et à la query (cf. castToType)
* @param {Object} indexOptions (optionnel) Options d'index mongo ex: {unique: true, sparse: true}
* @param {Function} callback (optionnel) Cette fonction permet de définir virtuellement la valeur d'un index.
* @return {Entity} l'entité (chaînable)
* @param {Object} [indexOptions] Options d'index mongo ex: {unique: true, sparse: true}
* @param {boolean} [indexOptions.sparse=false] si true l'index n'est pas sauvegardé
* @param {boolean} [indexOptions.unique=false] si true le store plantera si la valeur existe déjà en base
* @param {function} [indexOptions.normalizer] une fonction à appliquer sur la valeur de l'index avant stockage
* (mais après callback éventuelle)
* @param {function} [callback] Pour définir la valeur de l'index, appelé avec un .call(entity).
* L'index est alors "virtuel" car il ne dépend pas d'un seul champ,
* mieux vaut ne pas lui donner le même nom qu'un champ
* si on fait trop de transformations
* @return {EntityDefinition} pour chaînage
*/
defineIndex (indexName, ...params) {
if (this.indexes[indexName]) throw Error(`L’index ${indexName} a déjà été défini`)
......@@ -463,7 +470,7 @@ class EntityDefinition {
let indexOptions = {}
let fieldType
// On récupère les paramètres optionnels: fieldType, indexOptions, callback
// en partant de la fin. Heureusement ils ont des types différents !
// en partant de la fin. Heureusement ils ont tous des types différents !
let param = params.pop()
if (typeof param === 'function') {
callback = param
......
......@@ -698,7 +698,6 @@ class EntityQuery {
isNull () {
const lastMatch = getLastMatch(this)
if (!lastMatch) throw Error('isNull appelé sans match précédent')
if (lastMatch.index.indexOptions.sparse) throw Error('isNull() ne peut pas être appelé sur un index sparse')
return alterLastMatch(this, {operator: 'ISNULL'})
}
......
......@@ -35,10 +35,10 @@ describe('Test entities-queries', function () {
// Pour visualiser les index rapidement
// console.log('index de la collection', indexes)
// nos indexes + _id_ toujours mis par mongo + __deletedAt ajouté par lassi
expect(indexes).to.have.lengthOf(18)
expect(indexes).to.have.lengthOf(19)
// on ne check que les notres
const testIndexes = indexes.filter(i => !['_id_', '__deletedAt'].includes(i.name))
expect(testIndexes).to.have.lengthOf(16)
expect(testIndexes).to.have.lengthOf(17)
this(null, testIndexes)
}).seqEach(function (index) {
expect(index.name).to.match(/^entity_index_/)
......@@ -279,11 +279,19 @@ describe('Test entities-queries', function () {
})
describe('unique and sparse', () => {
after('purge', function (done) {
afterEach('purge', function (done) {
TestEntity.match().purge(done)
})
it('empêche d’avoir deux fois la même valeur mais accepte plusieurs null|undefined', (done) => {
// liste de valeurs pour les props sparse et s
const values = [
[null, 'a'],
['b', 'b'],
[undefined, 'c'],
['d', 'd']
]
it('unique+sparse empêche d’avoir deux fois la même valeur mais accepte plusieurs null|undefined', (done) => {
flow().seq(function () {
TestEntity.create({uniqueSparseString: null}).store(this)
}).seq(function () {
......@@ -303,8 +311,76 @@ describe('Test entities-queries', function () {
}).catch(done)
})
it('throws exception avec isNull', () => {
expect(() => TestEntity.match('uniqueSparseString').isNull()).to.throw('isNull() ne peut pas être appelé sur un index sparse')
it('isNull remonte les index unique+sparse inexistants', (done) => {
flow(values).seqEach(function ([uniqueSparseString, s]) {
TestEntity.create({uniqueSparseString, s}).store(this)
}).seq(function () {
TestEntity.match('uniqueSparseString').isNull().grab(this)
}).seq(function (entities) {
expect(entities).to.have.length(2)
// on est pas sur de l'ordre (ac ou ca)
const both = entities[0].s + entities[1].s
if (both === 'ac') return done()
expect(both).to.equals('ca')
this()
}).seq(function () {
// on teste aussi que isNull remonte les props absentes
TestEntity.match('sparseString').isNull().grab(this)
}).seq(function (entities) {
expect(entities).to.have.length(values.length)
done()
}).catch(done)
})
it('isNull remonte les index sparse inexistants', (done) => {
flow(values).seqEach(function ([sparseString, s]) {
TestEntity.create({sparseString, s}).store(this)
}).seq(function () {
TestEntity.match('sparseString').isNull().grab(this)
}).seq(function (entities) {
expect(entities).to.have.length(2)
// on est pas sur de l'ordre (ac ou ca)
const both = entities[0].s + entities[1].s
if (both === 'ac') return done()
expect(both).to.equals('ca')
this()
}).seq(function () {
// on teste aussi que isNull remonte les props absentes
TestEntity.match('uniqueSparseString').isNull().grab(this)
}).seq(function (entities) {
expect(entities).to.have.length(values.length)
done()
}).catch(done)
})
it('isNotNull remonte les index unique+sparse existants', (done) => {
flow(values).seqEach(function ([uniqueSparseString, s]) {
TestEntity.create({uniqueSparseString, s}).store(this)
}).seq(function () {
TestEntity.match('uniqueSparseString').isNotNull().grab(this)
}).seq(function (entities) {
expect(entities).to.have.length(2)
// on est pas sur de l'ordre (bd ou db)
const both = entities[0].s + entities[1].s
if (both === 'bd') return done()
expect(both).to.equals('db')
done()
}).catch(done)
})
it('isNotNull remonte les index sparse existants', (done) => {
flow(values).seqEach(function ([sparseString, s]) {
TestEntity.create({sparseString, s}).store(this)
}).seq(function () {
TestEntity.match('sparseString').isNotNull().grab(this)
}).seq(function (entities) {
expect(entities).to.have.length(2)
// on est pas sur de l'ordre (bd ou db)
const both = entities[0].s + entities[1].s
if (both === 'bd') return done()
expect(both).to.equals('db')
done()
}).catch(done)
})
})
......@@ -360,7 +436,9 @@ describe('Test entities-queries', function () {
{whatever: false},
{whatever: 0},
{whatever: null},
{whatever: undefined}
{whatever: undefined},
{whatever: []},
{whatever: [42]}
].map((item, index) => {
item.i = index
return item
......@@ -388,7 +466,8 @@ describe('Test entities-queries', function () {
datas.forEach(({whatever: original, i}, index) => {
expect(index).to.equals(i)
const expected = (original === null) ? undefined : original
expect(entities[index].whatever).to.equals(expected, `Pb sur le n° ${i}`)
if (Array.isArray(expected)) expect(entities[index].whatever).to.deep.equals(expected, `Pb sur le n° ${i}`)
else expect(entities[index].whatever).to.equals(expected, `Pb sur le n° ${i}`)
})
done()
}).catch(done)
......
......@@ -170,6 +170,7 @@ function initEntities (next) {
TestEntity.defineIndex('t', 'string')
TestEntity.defineIndex('uniqueString', 'string', {unique: true})
TestEntity.defineIndex('uniqueSparseString', 'string', {sparse: true, unique: true})
TestEntity.defineIndex('sparseString', 'string', {sparse: true})
TestEntity.defineIndex('iPair', 'integer', function () {
if (typeof this.i === 'number') return this.i % 2
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment