Skip to content

Commit 6fa7fbb

Browse files
committed
feat: internal to mongodb conversion COMPASS-8701
1 parent 1249f8b commit 6fa7fbb

File tree

5 files changed

+312
-12
lines changed

5 files changed

+312
-12
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
".esm-wrapper.mjs"
3434
],
3535
"scripts": {
36-
"test": "nyc mocha --timeout 5000 --colors -r ts-node/register test/*.ts",
36+
"test": "nyc mocha --timeout 5000 --colors -r ts-node/register src/**/*.test.ts",
3737
"test-example-parse-from-file": "ts-node examples/parse-from-file.ts",
3838
"test-example-parse-schema": "ts-node examples/parse-schema.ts",
3939
"test-time": "ts-node ./test/time-testing.ts",

src/schema-convertors/bsontypes.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// function parseType(type: SchemaType, signal?: AbortSignal): StandardJSONSchema {
2+
// switch (type.bsonType) {
3+
// case 'Array': return {
4+
// type: 'array',
5+
// items: parseTypes((type as ArraySchemaType).types)
6+
// };
7+
// case 'Binary': return {
8+
// type: 'string'
9+
// // contentEncoding: // TODO: can we get this?
10+
// };
11+
// case 'Boolean': return {
12+
// type: 'boolean'
13+
// };
14+
// case 'Document': return {
15+
// type: 'object',
16+
// ...parseFields((type as DocumentSchemaType).fields, signal)
17+
// };
18+
// case 'Double': return {
19+
// type: 'number'
20+
// };
21+
// case 'Null': return {
22+
// type: 'null'
23+
// };
24+
// case 'ObjectId': return {
25+
// type: 'string',
26+
// contentEncoding: 'base64' // TODO: confirm
27+
// };
28+
// case 'String': return {
29+
// type: 'string'
30+
// };
31+
// case 'Timestamp': return {
32+
// type: 'string'
33+
// // TODO
34+
// };
35+
// default: throw new Error('Type unknown ' + type.bsonType); // TODO: unknown + telemetry?
36+
// }
37+
// }

src/schema-convertors.ts renamed to src/schema-convertors/index.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
1-
import { Schema as InternalSchema } from './schema-analyzer';
2-
import { ExtendedJSONSchema, MongodbJSONSchema, StandardJSONSchema } from './types';
3-
4-
function internalSchemaToStandard(
5-
internalSchema: InternalSchema,
6-
options: {
7-
signal?: AbortSignal
8-
}): StandardJSONSchema {
9-
// TODO: COMPASS-8700
10-
return {};
11-
}
1+
import internalSchemaToStandard from './internalToStandard';
2+
import { Schema as InternalSchema } from '../schema-analyzer';
3+
import { ExtendedJSONSchema, MongodbJSONSchema } from '../types';
124

135
function internalSchemaToMongodb(
146
internalSchema: InternalSchema,
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import assert from 'assert';
2+
import internalSchemaToStandard from './internalToMongodb';
3+
4+
describe.only('internalSchemaToStandard', function() {
5+
it('converts a document/object', function() {
6+
const internal = {
7+
count: 2,
8+
fields: [
9+
{
10+
name: 'author',
11+
path: [
12+
'author'
13+
],
14+
count: 1,
15+
type: [
16+
'Document',
17+
'Undefined'
18+
],
19+
probability: 1,
20+
hasDuplicates: false,
21+
types: [
22+
{
23+
name: 'Document',
24+
path: [
25+
'author'
26+
],
27+
count: 1,
28+
probability: 0.5,
29+
bsonType: 'Document',
30+
fields: [
31+
{
32+
name: 'name',
33+
path: [
34+
'author',
35+
'name'
36+
],
37+
count: 1,
38+
type: 'String',
39+
probability: 1,
40+
hasDuplicates: false,
41+
types: [
42+
{
43+
name: 'String',
44+
path: [
45+
'author',
46+
'name'
47+
],
48+
count: 1,
49+
probability: 1,
50+
unique: 1,
51+
hasDuplicates: false,
52+
values: [
53+
'Peter Sonder'
54+
],
55+
bsonType: 'String'
56+
}
57+
]
58+
},
59+
{
60+
name: 'rating',
61+
path: [
62+
'author',
63+
'rating'
64+
],
65+
count: 1,
66+
type: 'Double',
67+
probability: 1,
68+
hasDuplicates: false,
69+
types: [
70+
{
71+
name: 'Double',
72+
path: [
73+
'author',
74+
'rating'
75+
],
76+
count: 1,
77+
probability: 1,
78+
unique: 1,
79+
hasDuplicates: false,
80+
values: [
81+
1.3
82+
],
83+
bsonType: 'Double'
84+
}
85+
]
86+
}
87+
]
88+
},
89+
{
90+
name: 'Undefined',
91+
bsonType: 'Undefined',
92+
unique: 1,
93+
hasDuplicates: false,
94+
path: [
95+
'author'
96+
],
97+
count: 1,
98+
probability: 0.5
99+
}
100+
]
101+
}
102+
]
103+
};
104+
const standard = internalSchemaToStandard(internal);
105+
assert.deepStrictEqual(standard, {
106+
$jsonSchema: {
107+
bsonType: 'object',
108+
required: ['author'],
109+
properties: {
110+
author: {
111+
bsonType: 'object',
112+
required: ['name', 'rating'],
113+
properties: {
114+
name: {
115+
bsonType: 'string'
116+
},
117+
rating: {
118+
bsonType: 'double'
119+
}
120+
}
121+
}
122+
}
123+
}
124+
});
125+
});
126+
127+
it('converts an array', function() {
128+
const internal = {
129+
count: 2,
130+
fields: [
131+
{
132+
name: 'genres',
133+
path: [
134+
'genres'
135+
],
136+
count: 1,
137+
type: [
138+
'array',
139+
'Undefined'
140+
],
141+
probability: 0.5,
142+
hasDuplicates: false,
143+
types: [
144+
{
145+
name: 'array',
146+
path: [
147+
'genres'
148+
],
149+
count: 1,
150+
probability: 0.5,
151+
bsonType: 'Array',
152+
types: [
153+
{
154+
name: 'String',
155+
path: [
156+
'genres'
157+
],
158+
count: 2,
159+
probability: 1,
160+
unique: 2,
161+
hasDuplicates: false,
162+
values: [
163+
'crimi',
164+
'comedy'
165+
],
166+
bsonType: 'String'
167+
}
168+
],
169+
totalCount: 2,
170+
lengths: [
171+
2
172+
],
173+
averageLength: 2
174+
},
175+
{
176+
name: 'Undefined',
177+
bsonType: 'Undefined',
178+
unique: 1,
179+
hasDuplicates: false,
180+
path: [
181+
'genres'
182+
],
183+
count: 1,
184+
probability: 0.5
185+
}
186+
]
187+
}
188+
]
189+
};
190+
const standard = internalSchemaToStandard(internal);
191+
assert.deepStrictEqual(standard, {
192+
$jsonSchema: {
193+
bsonType: 'object',
194+
required: [],
195+
properties: {
196+
genres: {
197+
bsonType: 'array',
198+
items: {
199+
bsonType: 'string'
200+
}
201+
}
202+
}
203+
}
204+
});
205+
});
206+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { ArraySchemaType, DocumentSchemaType, Schema as InternalSchema, SchemaType } from '../schema-analyzer';
2+
import { StandardJSONSchema } from '../types';
3+
4+
const internalTypeToBsonType = (type: string) => type === 'Document' ? 'object' : type.toLowerCase();
5+
6+
function parseType(type: SchemaType, signal?: AbortSignal): StandardJSONSchema {
7+
if (signal?.aborted) throw new Error('Operation aborted');
8+
const schema: StandardJSONSchema = {
9+
bsonType: internalTypeToBsonType(type.bsonType)
10+
};
11+
switch (type.bsonType) {
12+
case 'Array':
13+
schema.items = parseTypes((type as ArraySchemaType).types);
14+
break;
15+
case 'Document':
16+
Object.assign(schema,
17+
parseFields((type as DocumentSchemaType).fields, signal)
18+
);
19+
break;
20+
}
21+
22+
return schema;
23+
}
24+
25+
function parseTypes(types: SchemaType[], signal?: AbortSignal): StandardJSONSchema {
26+
if (signal?.aborted) throw new Error('Operation aborted');
27+
const definedTypes = types.filter(type => type.bsonType.toLowerCase() !== 'undefined');
28+
const isSingleType = definedTypes.length === 1;
29+
if (isSingleType) {
30+
return parseType(definedTypes[0], signal);
31+
}
32+
// TODO: array of types for simple types
33+
return {
34+
anyOf: definedTypes.map(type => parseType(type, signal))
35+
};
36+
}
37+
38+
function parseFields(fields: DocumentSchemaType['fields'], signal?: AbortSignal): {
39+
required: StandardJSONSchema['required'],
40+
properties: StandardJSONSchema['properties'],
41+
} {
42+
const required = [];
43+
const properties: StandardJSONSchema['properties'] = {};
44+
for (const field of fields) {
45+
if (signal?.aborted) throw new Error('Operation aborted');
46+
if (field.probability === 1) required.push(field.name);
47+
properties[field.name] = parseTypes(field.types, signal);
48+
}
49+
50+
return { required, properties };
51+
}
52+
53+
export default function internalSchemaToMongodb(
54+
internalSchema: InternalSchema,
55+
options: {
56+
signal?: AbortSignal
57+
} = {}): StandardJSONSchema {
58+
const schema: StandardJSONSchema = {
59+
$jsonSchema: {
60+
bsonType: 'object',
61+
...parseFields(internalSchema.fields, options.signal)
62+
}
63+
};
64+
return schema;
65+
}

0 commit comments

Comments
 (0)