diff --git a/src/packages/database/model/index.js b/src/packages/database/model/index.js index d07a8017..546951d9 100644 --- a/src/packages/database/model/index.js +++ b/src/packages/database/model/index.js @@ -1342,6 +1342,16 @@ class Model { return new Query(this).where(conditions); } + static whereBetween(conditions: Object): Query> { + return new Query(this).whereBetween(conditions); + } + + static whereRaw( + query: string, + bindings: Array = []): Query> { + return new Query(this).whereRaw(query, bindings); + } + static not(conditions: Object): Query> { return new Query(this).not(conditions); } diff --git a/src/packages/database/query/index.js b/src/packages/database/query/index.js index 2851468a..be860e2e 100644 --- a/src/packages/database/query/index.js +++ b/src/packages/database/query/index.js @@ -196,13 +196,21 @@ class Query<+T: any> extends Promise { if (Array.isArray(value)) { if (value.length > 1) { - this.snapshots.push([not ? 'whereNotIn' : 'whereIn', [key, value]]); + this.snapshots.push([ + not ? 'whereNotIn' : 'whereIn', + [key, value] + ]); } else { return { ...obj, [key]: value[0] }; } + } else if (value === null) { + this.snapshots.push([ + not ? 'whereNotNull' : 'whereNull', + [key] + ]); } else { return { ...obj, @@ -221,6 +229,38 @@ class Query<+T: any> extends Promise { return this; } + whereBetween(conditions: Object, not: boolean = false): this { + const { + model: { + tableName + } + } = this; + + entries(conditions).forEach((condition) => { + let [key] = condition; + const [, value] = condition; + const columnName = this.model.columnNameFor(key); + + if (columnName) { + key = `${tableName}.${columnName}`; + + if (Array.isArray(value)) { + this.snapshots.push([ + `where${not ? 'NotBetween' : 'Between'}`, + [key, value] + ]); + } + } + }); + + return this; + } + + whereRaw(query: string, bindings: Array = []): this { + this.snapshots.push(['whereRaw', [query, bindings]]); + return this; + } + first(): this { if (!this.shouldCount) { const willSort = this.snapshots.some( diff --git a/src/packages/database/test/model.test.js b/src/packages/database/test/model.test.js index bc48658a..7d5bb7b0 100644 --- a/src/packages/database/test/model.test.js +++ b/src/packages/database/test/model.test.js @@ -562,6 +562,47 @@ describe('module "database/model"', () => { }); }); + describe('.whereBetween()', () => { + class Subject extends Model { + static tableName = 'posts'; + } + + before(async () => { + await Subject.initialize(store, () => { + return store.connection(Subject.tableName); + }); + }); + + it('returns an instance of `Query`', () => { + const result = Subject.whereBetween({ + userId: [1, 10] + }); + + expect(result).to.be.an.instanceof(Query); + }); + }); + + describe('.whereRaw()', () => { + class Subject extends Model { + static tableName = 'posts'; + } + + before(async () => { + await Subject.initialize(store, () => { + return store.connection(Subject.tableName); + }); + }); + + it('returns an instance of `Query`', () => { + const result = Subject.whereRaw( + `"title" LIKE ?`, + [`%Test%`] + ); + + expect(result).to.be.an.instanceof(Query); + }); + }); + describe('.not()', () => { class Subject extends Model { static tableName = 'posts'; diff --git a/src/packages/database/test/query.test.js b/src/packages/database/test/query.test.js index bdcbf41f..c271824b 100644 --- a/src/packages/database/test/query.test.js +++ b/src/packages/database/test/query.test.js @@ -407,6 +407,16 @@ describe('module "database/query"', () => { ]); }); + it('properly handles null conditions', () => { + const result = subject.where({ + isPublic: null + }); + + expect(result.snapshots).to.deep.equal([ + ['whereNull', ['posts.is_public']] + ]); + }); + it('resolves with the correct array of `Model` instances', async () => { const result = await subject.where({ isPublic: true @@ -423,6 +433,93 @@ describe('module "database/query"', () => { }); }); + describe('#whereBetween()', () => { + let subject; + + beforeEach(() => { + subject = new Query(TestModel); + }); + + it('returns `this`', () => { + const result = subject.whereBetween({ + userId: [1, 10] + }); + + expect(result).to.equal(subject); + }); + + it('properly modifies #snapshots', () => { + const result = subject.whereBetween({ + userId: [1, 10] + }); + + expect(result.snapshots).to.deep.equal([ + ['whereBetween', ['posts.user_id', [1, 10]]] + ]); + }); + + it('resolves with the correct array of `Model` instances', async () => { + const result = await subject.whereBetween({ + userId: [1, 10] + }); + + expect(result).to.be.an('array'); + + if (Array.isArray(result)) { + result.forEach(item => { + assertItem(item); + expect(item.userId).to.be.above(0).and.below(11); + + }); + } + }); + }); + + describe('#whereRaw()', () => { + let subject; + + beforeEach(() => { + subject = new Query(TestModel); + }); + + it('returns `this`', () => { + const result = subject.whereRaw( + `"title" LIKE ?`, + [`%Test%`] + ); + + expect(result).to.equal(subject); + }); + + it('properly modifies #snapshots', () => { + const result = subject.whereRaw( + `"title" LIKE ?`, + [`%Test%`] + ); + + expect(result.snapshots).to.deep.equal([ + ['whereRaw', [`"title" LIKE ?`, [`%Test%`]]] + ]); + }); + + it('resolves with the correct array of `Model` instances', async () => { + const result = await subject.whereRaw( + `"title" LIKE ?`, + [`%Test%`] + ); + + expect(result).to.be.an('array'); + + if (Array.isArray(result)) { + result.forEach(item => { + assertItem(item); + expect(item.title).to.match(/Test/); + + }); + } + }); + }); + describe('#first()', () => { let subject; diff --git a/test/test-app/db/seed.js b/test/test-app/db/seed.js index a1eb6e80..1503ef88 100644 --- a/test/test-app/db/seed.js +++ b/test/test-app/db/seed.js @@ -48,7 +48,7 @@ export default async function seed(trx) { Array.from(range(1, 100)).map(() => ( Post.transacting(trx).create({ body: lorem.paragraphs(), - title: lorem.sentence(), + title: `${arguments[1] === 0 ? 'Test ' : ''} ${lorem.sentence()}`, userId: randomize([...range(1, 100)]), isPublic: random.boolean() })