Skip to content

Commit 56b9b74

Browse files
MXPOLIdokah
authored andcommitted
Mongo to V3 (#397)
* feat: new capabilities file * feat: new capabilities property in the schema list * fix: fixed the types in mongo_schema_provider * feat: upsert insert implementation * feat: aggregate implementation * feat: enable query nested fields * feat: new supported ops and test * test: enable mongo tests on github cli * fix: updated mssql supported ops * refactor: some changes according to the review * test: new supported operations * test: spanner supported ops update
1 parent 2aba97d commit 56b9b74

File tree

26 files changed

+284
-84
lines changed

26 files changed

+284
-84
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ jobs:
4141
"postgres", "postgres13", "postgres12", "postgres11", "postgres10", "postgres9",
4242
"mysql", "mysql5",
4343
"mssql", "mssql17",
44-
spanner
44+
spanner,
45+
"mongo", "mongo4",
4546
]
4647

4748
env:

apps/velo-external-db/src/storage/factory.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ export const engineConnectorFor = async(_type: string, config: any): Promise<Dat
2323
const { mySqlFactory } = require('@wix-velo/external-db-mysql')
2424
return await mySqlFactory(config)
2525
}
26-
// case 'mongo': {
27-
// const { mongoFactory } = require('@wix-velo/external-db-mongo')
28-
// return await mongoFactory(config)
29-
// }
26+
case 'mongo': {
27+
const { mongoFactory } = require('@wix-velo/external-db-mongo')
28+
return await mongoFactory(config)
29+
}
3030
// case 'google-sheet': {
3131
// const { googleSheetFactory } = require('@wix-velo/external-db-google-sheets')
3232
// return await googleSheetFactory(config)

apps/velo-external-db/test/e2e/app_data.e2e.spec.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import axios from 'axios'
22
import each from 'jest-each'
3-
import Chance = require('chance')
3+
import * as Chance from 'chance'
44
import { Uninitialized, gen as genCommon, testIfSupportedOperationsIncludes, streamToArray } from '@wix-velo/test-commons'
5-
import { SchemaOperations } from '@wix-velo/velo-external-db-types'
5+
import { InputField, SchemaOperations, Item } from '@wix-velo/velo-external-db-types'
66
import { dataSpi } from '@wix-velo/velo-external-db-core'
77
import { authAdmin, authOwner, authVisitor } from '@wix-velo/external-db-testkit'
88
import * as gen from '../gen'
@@ -11,7 +11,7 @@ import * as matchers from '../drivers/schema_api_rest_matchers'
1111
import * as data from '../drivers/data_api_rest_test_support'
1212
import * as authorization from '../drivers/authorization_test_support'
1313
import { initApp, teardownApp, dbTeardown, setupDb, currentDbImplementationName, supportedOperations } from '../resources/e2e_resources'
14-
const { UpdateImmediately, DeleteImmediately, Truncate, Aggregate, FindWithSort, Projection, FilterByEveryField, QueryNestedFields } = SchemaOperations
14+
const { UpdateImmediately, DeleteImmediately, Truncate, Aggregate, FindWithSort, Projection, FilterByEveryField, QueryNestedFields, NonAtomicBulkInsert, AtomicBulkInsert } = SchemaOperations
1515

1616
const chance = Chance()
1717

@@ -80,7 +80,7 @@ describe(`Velo External DB Data REST API: ${currentDbImplementationName()}`, ()
8080
)
8181
})
8282

83-
test('insert api should fail if item already exists', async() => {
83+
testIfSupportedOperationsIncludes(supportedOperations, [AtomicBulkInsert])('insert api should fail if item already exists', async() => {
8484
await schema.givenCollection(ctx.collectionName, [ctx.column], authOwner)
8585
await data.givenItems([ ctx.items[1] ], ctx.collectionName, authAdmin)
8686

@@ -98,6 +98,23 @@ describe(`Velo External DB Data REST API: ${currentDbImplementationName()}`, ()
9898
)
9999
})
100100

101+
testIfSupportedOperationsIncludes(supportedOperations, [NonAtomicBulkInsert])('insert api should throw 409 error if item already exists and continue inserting the rest', async() => {
102+
await schema.givenCollection(ctx.collectionName, [ctx.column], authOwner)
103+
await data.givenItems([ ctx.items[1] ], ctx.collectionName, authAdmin)
104+
105+
const response = axiosInstance.post('/data/insert', data.insertRequest(ctx.collectionName, ctx.items, false), { responseType: 'stream', ...authAdmin })
106+
107+
const expectedItems = ctx.items.map(i => dataSpi.QueryResponsePart.item(i))
108+
109+
await expect(response).rejects.toThrow('409')
110+
await expect(data.queryCollectionAsArray(ctx.collectionName, [], undefined, authOwner)).resolves.toEqual(expect.toIncludeAllMembers(
111+
[
112+
...expectedItems,
113+
data.pagingMetadata(expectedItems.length, expectedItems.length)
114+
])
115+
)
116+
})
117+
101118
test('insert api should succeed if item already exists and overwriteExisting is on', async() => {
102119
await schema.givenCollection(ctx.collectionName, [ctx.column], authOwner)
103120
await data.givenItems([ ctx.item ], ctx.collectionName, authAdmin)
@@ -347,8 +364,24 @@ describe(`Velo External DB Data REST API: ${currentDbImplementationName()}`, ()
347364
})
348365
})
349366

367+
interface Ctx {
368+
collectionName: string
369+
column: InputField
370+
numberColumns: InputField[]
371+
objectColumn: InputField
372+
item: Item
373+
items: Item[]
374+
modifiedItem: Item
375+
modifiedItems: Item[]
376+
anotherItem: Item
377+
numberItem: Item
378+
anotherNumberItem: Item
379+
objectItem: Item
380+
nestedFieldName: string
381+
pastVeloDate: { $date: string; }
382+
}
350383

351-
const ctx = {
384+
const ctx: Ctx = {
352385
collectionName: Uninitialized,
353386
column: Uninitialized,
354387
numberColumns: Uninitialized,

apps/velo-external-db/test/env/env.db.setup.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { testResources: mysql } = require ('@wix-velo/external-db-mysql')
88
const { testResources: spanner } = require ('@wix-velo/external-db-spanner')
99
// const { testResources: firestore } = require ('@wix-velo/external-db-firestore')
1010
const { testResources: mssql } = require ('@wix-velo/external-db-mssql')
11-
// const { testResources: mongo } = require ('@wix-velo/external-db-mongo')
11+
const { testResources: mongo } = require ('@wix-velo/external-db-mongo')
1212
// const { testResources: googleSheet } = require('@wix-velo/external-db-google-sheets')
1313
// const { testResources: airtable } = require('@wix-velo/external-db-airtable')
1414
// const { testResources: dynamoDb } = require('@wix-velo/external-db-dynamodb')
@@ -39,9 +39,9 @@ const initEnv = async(testEngine) => {
3939
await mssql.initEnv()
4040
break
4141

42-
// case 'mongo':
43-
// await mongo.initEnv()
44-
// break
42+
case 'mongo':
43+
await mongo.initEnv()
44+
break
4545
// case 'google-sheet':
4646
// await googleSheet.initEnv()
4747
// break
@@ -86,9 +86,9 @@ const cleanup = async(testEngine) => {
8686
// await googleSheet.cleanup()
8787
// break
8888

89-
// case 'mongo':
90-
// await mongo.cleanup()
91-
// break
89+
case 'mongo':
90+
await mongo.cleanup()
91+
break
9292

9393
// case 'dynamodb':
9494
// await dynamoDb.cleanup()

apps/velo-external-db/test/env/env.db.teardown.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const { testResources: mysql } = require ('@wix-velo/external-db-mysql')
33
const { testResources: spanner } = require ('@wix-velo/external-db-spanner')
44
// const { testResources: firestore } = require ('@wix-velo/external-db-firestore')
55
const { testResources: mssql } = require ('@wix-velo/external-db-mssql')
6-
// const { testResources: mongo } = require ('@wix-velo/external-db-mongo')
6+
const { testResources: mongo } = require ('@wix-velo/external-db-mongo')
77
// const { testResources: googleSheet } = require('@wix-velo/external-db-google-sheets')
88
// const { testResources: airtable } = require('@wix-velo/external-db-airtable')
99
// const { testResources: dynamo } = require('@wix-velo/external-db-dynamodb')
@@ -45,9 +45,9 @@ const shutdownEnv = async(testEngine) => {
4545
// await dynamo.shutdownEnv()
4646
// break
4747

48-
// case 'mongo':
49-
// await mongo.shutdownEnv()
50-
// break
48+
case 'mongo':
49+
await mongo.shutdownEnv()
50+
break
5151

5252
// case 'bigquery':
5353
// await bigquery.shutdownEnv()

apps/velo-external-db/test/resources/e2e_resources.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const testSuits = {
3333
spanner: new E2EResources(spanner, createAppWithWixDataBaseUrl),
3434
firestore: new E2EResources(firestore, createApp),
3535
mssql: new E2EResources(mssql, createAppWithWixDataBaseUrl),
36-
mongo: new E2EResources(mongo, createApp),
36+
mongo: new E2EResources(mongo, createAppWithWixDataBaseUrl),
3737
'google-sheet': new E2EResources(googleSheet, createApp),
3838
airtable: new E2EResources(airtable, createApp),
3939
dynamodb: new E2EResources(dynamo, createApp),

apps/velo-external-db/test/resources/provider_resources.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const testSuits = {
7474
spanner: suiteDef('Spanner', spannerTestEnvInit, spanner.testResources),
7575
firestore: suiteDef('Firestore', firestoreTestEnvInit, firestore.testResources.supportedOperations),
7676
mssql: suiteDef('Sql Server', mssqlTestEnvInit, mssql.testResources),
77-
mongo: suiteDef('Mongo', mongoTestEnvInit, mongo.testResources.supportedOperations),
77+
mongo: suiteDef('Mongo', mongoTestEnvInit, mongo.testResources),
7878
airtable: suiteDef('Airtable', airTableTestEnvInit, airtable.testResources.supportedOperations),
7979
dynamodb: suiteDef('DynamoDb', dynamoTestEnvInit, dynamo.testResources.supportedOperations),
8080
bigquery: suiteDef('BigQuery', bigqueryTestEnvInit, bigquery.testResources.supportedOperations),
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { errors } from '@wix-velo/velo-external-db-commons'
22
const { ItemAlreadyExists } = errors
33

4-
const notThrowingTranslateErrorCodes = (err: any) => {
4+
const extractItemIdFromError = (err: any) => err.message.split('"')[1]
5+
6+
const notThrowingTranslateErrorCodes = (err: any, collectionName: string) => {
57
switch (err.code) {
6-
case 11000:
7-
return new ItemAlreadyExists(`Item already exists: ${err.message}`)
8+
case 11000:
9+
return new ItemAlreadyExists(`Item already exists: ${err.message}`, collectionName, extractItemIdFromError(err))
810
default:
911
return new Error (`default ${err.message}`)
1012
}
1113
}
1214

13-
export const translateErrorCodes = (err: any) => {
14-
throw notThrowingTranslateErrorCodes(err)
15+
export const translateErrorCodes = (err: any, collectionName: string) => {
16+
throw notThrowingTranslateErrorCodes(err, collectionName)
1517
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { AdapterOperators } from '@wix-velo/velo-external-db-commons'
2+
import { CollectionOperation, DataOperation, FieldType } from '@wix-velo/velo-external-db-types'
3+
4+
const { query, count, queryReferenced, aggregate, } = DataOperation
5+
const { eq, ne, string_contains, string_begins, string_ends, gt, gte, lt, lte, include } = AdapterOperators
6+
7+
export const ReadWriteOperations = Object.values(DataOperation)
8+
export const ReadOnlyOperations = [query, count, queryReferenced, aggregate]
9+
export const FieldTypes = Object.values(FieldType)
10+
export const CollectionOperations = Object.values(CollectionOperation)
11+
export const ColumnsCapabilities = {
12+
text: { sortable: true, columnQueryOperators: [eq, ne, string_contains, string_begins, string_ends, include, gt, gte, lt, lte] },
13+
url: { sortable: true, columnQueryOperators: [eq, ne, string_contains, string_begins, string_ends, include, gt, gte, lt, lte] },
14+
number: { sortable: true, columnQueryOperators: [eq, ne, gt, gte, lt, lte, include] },
15+
boolean: { sortable: true, columnQueryOperators: [eq] },
16+
image: { sortable: false, columnQueryOperators: [] },
17+
object: { sortable: false, columnQueryOperators: [] },
18+
datetime: { sortable: true, columnQueryOperators: [eq, ne, gt, gte, lt, lte] },
19+
}

libs/external-db-mongo/src/mongo_data_provider.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { translateErrorCodes } from './exception_translator'
2-
import { unpackIdFieldForItem, updateExpressionFor, validateTable } from './mongo_utils'
3-
import { IDataProvider, AdapterFilter as Filter, AdapterAggregation as Aggregation, Item } from '@wix-velo/velo-external-db-types'
2+
import { insertExpressionFor, isEmptyObject, unpackIdFieldForItem, updateExpressionFor, validateTable } from './mongo_utils'
3+
import { IDataProvider, AdapterFilter as Filter, AdapterAggregation as Aggregation, Item, Sort, } from '@wix-velo/velo-external-db-types'
44
import FilterParser from './sql_filter_transformer'
55
import { MongoClient } from 'mongodb'
66

@@ -34,22 +34,22 @@ export default class DataProvider implements IDataProvider {
3434
.count(filterExpr)
3535
}
3636

37-
async insert(collectionName: string, items: Item[] ): Promise<number> {
37+
async insert(collectionName: string, items: Item[], _fields: any[], upsert = false): Promise<number> {
3838
validateTable(collectionName)
39-
const result = await this.client.db()
40-
.collection(collectionName)
41-
//@ts-ignore - Type 'string' is not assignable to type 'ObjectId', objectId Can be a 24 character hex string, 12 byte binary Buffer, or a number. and we cant assume that on the _id input
42-
.insertMany(items)
43-
.catch(translateErrorCodes)
44-
return result.insertedCount
39+
const { insertedCount, upsertedCount } = await this.client.db()
40+
.collection(collectionName)
41+
.bulkWrite(insertExpressionFor(items, upsert), { ordered: false })
42+
.catch(e => translateErrorCodes(e, collectionName))
43+
44+
return insertedCount + upsertedCount
4545
}
4646

4747
async update(collectionName: string, items: Item[]): Promise<number> {
4848
validateTable(collectionName)
4949
const result = await this.client.db()
5050
.collection(collectionName)
5151
.bulkWrite( updateExpressionFor(items) )
52-
return result.nModified
52+
return result.nModified
5353
}
5454

5555
async delete(collectionName: string, itemIds: string[]): Promise<number> {
@@ -67,17 +67,26 @@ export default class DataProvider implements IDataProvider {
6767
.deleteMany({})
6868
}
6969

70-
async aggregate(collectionName: string, filter: Filter, aggregation: Aggregation): Promise<Item[]> {
70+
async aggregate(collectionName: string, filter: Filter, aggregation: Aggregation, sort: Sort[], skip: number, limit: number): Promise<Item[]> {
7171
validateTable(collectionName)
72+
const additionalAggregationStages = []
7273
const { fieldsStatement, havingFilter } = this.filterParser.parseAggregation(aggregation)
7374
const { filterExpr } = this.filterParser.transform(filter)
75+
const sortExpr = this.filterParser.orderAggregationBy(sort)
76+
77+
!isEmptyObject(sortExpr.$sort)? additionalAggregationStages.push(sortExpr) : null
78+
skip? additionalAggregationStages.push({ $skip: skip }) : null
79+
limit? additionalAggregationStages.push({ $limit: limit }) : null
80+
7481
const result = await this.client.db()
75-
.collection(collectionName)
76-
.aggregate( [ { $match: filterExpr },
77-
fieldsStatement,
78-
havingFilter
79-
] )
80-
.toArray()
82+
.collection(collectionName)
83+
.aggregate([
84+
{ $match: filterExpr },
85+
fieldsStatement,
86+
havingFilter,
87+
...additionalAggregationStages
88+
])
89+
.toArray()
8190

8291
return result.map( unpackIdFieldForItem )
8392
}

0 commit comments

Comments
 (0)