meadow-connection-postgresql 1.0.0 → 1.0.2
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/README.md +133 -0
- package/docker-compose.yml +20 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +96 -0
- package/docs/_cover.md +17 -0
- package/docs/_sidebar.md +33 -0
- package/docs/_topbar.md +5 -0
- package/docs/api/connect.md +100 -0
- package/docs/api/connectAsync.md +92 -0
- package/docs/api/createTable.md +116 -0
- package/docs/api/createTables.md +128 -0
- package/docs/api/generateCreateTableStatement.md +136 -0
- package/docs/api/generateDropTableStatement.md +71 -0
- package/docs/api/pool.md +171 -0
- package/docs/api/reference.md +112 -0
- package/docs/api.md +3 -0
- package/docs/architecture.md +168 -0
- package/docs/css/docuserve.css +73 -0
- package/docs/index.html +39 -0
- package/docs/quickstart.md +181 -0
- package/docs/retold-catalog.json +62 -0
- package/docs/retold-keyword-index.json +4964 -0
- package/docs/schema.md +148 -0
- package/package.json +5 -2
- package/source/Meadow-Connection-PostgreSQL.js +79 -99
- package/source/Meadow-Schema-PostgreSQL.js +1048 -0
- package/start-postgresql.sh +21 -0
- package/stop-postgresql.sh +9 -0
- package/test/PostgreSQL_tests.js +865 -1
- package/test/docker-init/01-chinook-schema.sql +177 -0
package/test/PostgreSQL_tests.js
CHANGED
|
@@ -11,6 +11,7 @@ const Expect = Chai.expect;
|
|
|
11
11
|
|
|
12
12
|
const libFable = require('fable');
|
|
13
13
|
const libMeadowConnectionPostgreSQL = require('../source/Meadow-Connection-PostgreSQL.js');
|
|
14
|
+
const libMeadowSchemaPostgreSQL = require('../source/Meadow-Schema-PostgreSQL.js');
|
|
14
15
|
|
|
15
16
|
const _FableConfig = (
|
|
16
17
|
{
|
|
@@ -32,7 +33,7 @@ const _FableConfig = (
|
|
|
32
33
|
"PostgreSQL":
|
|
33
34
|
{
|
|
34
35
|
"Server": "127.0.0.1",
|
|
35
|
-
"Port":
|
|
36
|
+
"Port": 25432,
|
|
36
37
|
"User": "postgres",
|
|
37
38
|
"Password": "testpassword",
|
|
38
39
|
"Database": "testdb",
|
|
@@ -40,6 +41,92 @@ const _FableConfig = (
|
|
|
40
41
|
}
|
|
41
42
|
});
|
|
42
43
|
|
|
44
|
+
const _AnimalTableSchema =
|
|
45
|
+
{
|
|
46
|
+
TableName: 'Animal',
|
|
47
|
+
Columns:
|
|
48
|
+
[
|
|
49
|
+
{ Column: 'IDAnimal', DataType: 'ID' },
|
|
50
|
+
{ Column: 'GUIDAnimal', DataType: 'GUID', Size: 36 },
|
|
51
|
+
{ Column: 'Name', DataType: 'String', Size: 128 },
|
|
52
|
+
{ Column: 'Age', DataType: 'Numeric' },
|
|
53
|
+
{ Column: 'IDFarm', DataType: 'ForeignKey' }
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const _AnimalTableSchemaWithColumnIndexed =
|
|
58
|
+
{
|
|
59
|
+
TableName: 'Animal',
|
|
60
|
+
Columns:
|
|
61
|
+
[
|
|
62
|
+
{ Column: 'IDAnimal', DataType: 'ID' },
|
|
63
|
+
{ Column: 'GUIDAnimal', DataType: 'GUID', Size: 36 },
|
|
64
|
+
{ Column: 'Name', DataType: 'String', Size: 128, Indexed: true },
|
|
65
|
+
{ Column: 'TagNumber', DataType: 'String', Size: 64, Indexed: 'unique' },
|
|
66
|
+
{ Column: 'IDFarm', DataType: 'ForeignKey' }
|
|
67
|
+
]
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const _AnimalTableSchemaWithIndexName =
|
|
71
|
+
{
|
|
72
|
+
TableName: 'AnimalCustomIdx',
|
|
73
|
+
Columns:
|
|
74
|
+
[
|
|
75
|
+
{ Column: 'IDAnimalCustomIdx', DataType: 'ID' },
|
|
76
|
+
{ Column: 'GUIDAnimalCustomIdx', DataType: 'GUID', Size: 36 },
|
|
77
|
+
{ Column: 'Name', DataType: 'String', Size: 128, Indexed: true, IndexName: 'IX_Custom_Name' },
|
|
78
|
+
{ Column: 'TagNumber', DataType: 'String', Size: 64, Indexed: 'unique', IndexName: 'UQ_Animal_Tag' },
|
|
79
|
+
{ Column: 'Weight', DataType: 'Decimal', Size: '10,2', Indexed: true },
|
|
80
|
+
{ Column: 'IDFarm', DataType: 'ForeignKey' }
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Schemas specifically for introspection testing (unique table names to avoid conflicts)
|
|
85
|
+
const _IntrospectAnimalSchema =
|
|
86
|
+
{
|
|
87
|
+
TableName: 'IntrospAnimal',
|
|
88
|
+
Columns:
|
|
89
|
+
[
|
|
90
|
+
{ Column: 'IDIntrospAnimal', DataType: 'ID' },
|
|
91
|
+
{ Column: 'GUIDIntrospAnimal', DataType: 'GUID', Size: 36 },
|
|
92
|
+
{ Column: 'Name', DataType: 'String', Size: 128 },
|
|
93
|
+
{ Column: 'Description', DataType: 'Text' },
|
|
94
|
+
{ Column: 'Cost', DataType: 'Decimal', Size: '10,2' },
|
|
95
|
+
{ Column: 'Age', DataType: 'Numeric' },
|
|
96
|
+
{ Column: 'Birthday', DataType: 'DateTime' },
|
|
97
|
+
{ Column: 'Active', DataType: 'Boolean' },
|
|
98
|
+
{ Column: 'IDFarm', DataType: 'ForeignKey' }
|
|
99
|
+
]
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const _IntrospectAnimalIndexedSchema =
|
|
103
|
+
{
|
|
104
|
+
TableName: 'IntrospAnimalIdx',
|
|
105
|
+
Columns:
|
|
106
|
+
[
|
|
107
|
+
{ Column: 'IDIntrospAnimalIdx', DataType: 'ID' },
|
|
108
|
+
{ Column: 'GUIDIntrospAnimalIdx', DataType: 'GUID', Size: 36 },
|
|
109
|
+
{ Column: 'Name', DataType: 'String', Size: 128, Indexed: true },
|
|
110
|
+
{ Column: 'Description', DataType: 'Text' },
|
|
111
|
+
{ Column: 'TagNumber', DataType: 'String', Size: 64, Indexed: 'unique' },
|
|
112
|
+
{ Column: 'IDOwner', DataType: 'ForeignKey' }
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const _IntrospectAnimalCustomIdxSchema =
|
|
117
|
+
{
|
|
118
|
+
TableName: 'IntrospAnimalCustIdx',
|
|
119
|
+
Columns:
|
|
120
|
+
[
|
|
121
|
+
{ Column: 'IDIntrospAnimalCustIdx', DataType: 'ID' },
|
|
122
|
+
{ Column: 'GUIDIntrospAnimalCustIdx', DataType: 'GUID', Size: 36 },
|
|
123
|
+
{ Column: 'Name', DataType: 'String', Size: 128, Indexed: true, IndexName: 'IX_Custom_Name' },
|
|
124
|
+
{ Column: 'TagNumber', DataType: 'String', Size: 64, Indexed: 'unique', IndexName: 'UQ_IntrospAnimalCustIdx_Tag' },
|
|
125
|
+
{ Column: 'Weight', DataType: 'Decimal', Size: '10,2', Indexed: true },
|
|
126
|
+
{ Column: 'IDTrainer', DataType: 'ForeignKey' }
|
|
127
|
+
]
|
|
128
|
+
};
|
|
129
|
+
|
|
43
130
|
suite
|
|
44
131
|
(
|
|
45
132
|
'Meadow-Connection-PostgreSQL',
|
|
@@ -144,5 +231,782 @@ suite
|
|
|
144
231
|
);
|
|
145
232
|
}
|
|
146
233
|
);
|
|
234
|
+
|
|
235
|
+
suite
|
|
236
|
+
(
|
|
237
|
+
'Index Generation',
|
|
238
|
+
() =>
|
|
239
|
+
{
|
|
240
|
+
let libSchemaPostgreSQL = null;
|
|
241
|
+
|
|
242
|
+
setup(
|
|
243
|
+
() =>
|
|
244
|
+
{
|
|
245
|
+
let _Fable = new libFable(_FableConfig);
|
|
246
|
+
libSchemaPostgreSQL = _Fable.serviceManager.addServiceType('MeadowSchemaPostgreSQL', libMeadowSchemaPostgreSQL);
|
|
247
|
+
libSchemaPostgreSQL = _Fable.serviceManager.instantiateServiceProvider('MeadowSchemaPostgreSQL');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test
|
|
251
|
+
(
|
|
252
|
+
'auto-detect GUID and ForeignKey indices',
|
|
253
|
+
() =>
|
|
254
|
+
{
|
|
255
|
+
let tmpIndices = libSchemaPostgreSQL.getIndexDefinitionsFromSchema(_AnimalTableSchema);
|
|
256
|
+
Expect(tmpIndices).to.be.an('array');
|
|
257
|
+
Expect(tmpIndices.length).to.equal(2);
|
|
258
|
+
Expect(tmpIndices[0].Name).to.equal('AK_M_GUIDAnimal');
|
|
259
|
+
Expect(tmpIndices[0].Unique).to.equal(true);
|
|
260
|
+
Expect(tmpIndices[1].Name).to.equal('IX_M_IDFarm');
|
|
261
|
+
Expect(tmpIndices[1].Unique).to.equal(false);
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
test
|
|
266
|
+
(
|
|
267
|
+
'include explicit indices alongside auto-detected ones',
|
|
268
|
+
() =>
|
|
269
|
+
{
|
|
270
|
+
let tmpSchemaWithExplicit = JSON.parse(JSON.stringify(_AnimalTableSchema));
|
|
271
|
+
tmpSchemaWithExplicit.Indices = [
|
|
272
|
+
{ Name: 'IX_Animal_NameAge', Columns: ['Name', 'Age'], Unique: false }
|
|
273
|
+
];
|
|
274
|
+
let tmpIndices = libSchemaPostgreSQL.getIndexDefinitionsFromSchema(tmpSchemaWithExplicit);
|
|
275
|
+
Expect(tmpIndices.length).to.equal(3);
|
|
276
|
+
Expect(tmpIndices[2].Name).to.equal('IX_Animal_NameAge');
|
|
277
|
+
Expect(tmpIndices[2].Columns).to.deep.equal(['Name', 'Age']);
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
test
|
|
282
|
+
(
|
|
283
|
+
'column-level Indexed property generates consistently named indices',
|
|
284
|
+
() =>
|
|
285
|
+
{
|
|
286
|
+
let tmpIndices = libSchemaPostgreSQL.getIndexDefinitionsFromSchema(_AnimalTableSchemaWithColumnIndexed);
|
|
287
|
+
Expect(tmpIndices).to.be.an('array');
|
|
288
|
+
Expect(tmpIndices.length).to.equal(4);
|
|
289
|
+
Expect(tmpIndices[0].Name).to.equal('AK_M_GUIDAnimal');
|
|
290
|
+
Expect(tmpIndices[1].Name).to.equal('IX_M_T_Animal_C_Name');
|
|
291
|
+
Expect(tmpIndices[1].Unique).to.equal(false);
|
|
292
|
+
Expect(tmpIndices[2].Name).to.equal('AK_M_T_Animal_C_TagNumber');
|
|
293
|
+
Expect(tmpIndices[2].Unique).to.equal(true);
|
|
294
|
+
Expect(tmpIndices[3].Name).to.equal('IX_M_IDFarm');
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
test
|
|
299
|
+
(
|
|
300
|
+
'generate script with column-level Indexed property',
|
|
301
|
+
() =>
|
|
302
|
+
{
|
|
303
|
+
let tmpScript = libSchemaPostgreSQL.generateCreateIndexScript(_AnimalTableSchemaWithColumnIndexed);
|
|
304
|
+
Expect(tmpScript).to.contain('IX_M_T_Animal_C_Name');
|
|
305
|
+
Expect(tmpScript).to.contain('AK_M_T_Animal_C_TagNumber');
|
|
306
|
+
Expect(tmpScript).to.contain('IF NOT EXISTS');
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
test
|
|
311
|
+
(
|
|
312
|
+
'generate idempotent index script with IF NOT EXISTS',
|
|
313
|
+
() =>
|
|
314
|
+
{
|
|
315
|
+
let tmpScript = libSchemaPostgreSQL.generateCreateIndexScript(_AnimalTableSchema);
|
|
316
|
+
Expect(tmpScript).to.contain('CREATE UNIQUE INDEX IF NOT EXISTS');
|
|
317
|
+
Expect(tmpScript).to.contain('CREATE INDEX IF NOT EXISTS');
|
|
318
|
+
Expect(tmpScript).to.contain('"AK_M_GUIDAnimal"');
|
|
319
|
+
Expect(tmpScript).to.contain('"IX_M_IDFarm"');
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
test
|
|
324
|
+
(
|
|
325
|
+
'generate script with strategy clause',
|
|
326
|
+
() =>
|
|
327
|
+
{
|
|
328
|
+
let tmpSchemaWithStrategy = JSON.parse(JSON.stringify(_AnimalTableSchema));
|
|
329
|
+
tmpSchemaWithStrategy.Indices = [
|
|
330
|
+
{ Name: 'IX_Animal_Name_Hash', Columns: ['Name'], Unique: false, Strategy: 'hash' }
|
|
331
|
+
];
|
|
332
|
+
let tmpScript = libSchemaPostgreSQL.generateCreateIndexScript(tmpSchemaWithStrategy);
|
|
333
|
+
Expect(tmpScript).to.contain('USING hash');
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
test
|
|
338
|
+
(
|
|
339
|
+
'generate individual index statements with pg_indexes check',
|
|
340
|
+
() =>
|
|
341
|
+
{
|
|
342
|
+
let tmpStatements = libSchemaPostgreSQL.generateCreateIndexStatements(_AnimalTableSchema);
|
|
343
|
+
Expect(tmpStatements).to.be.an('array');
|
|
344
|
+
Expect(tmpStatements.length).to.equal(2);
|
|
345
|
+
Expect(tmpStatements[0].Name).to.equal('AK_M_GUIDAnimal');
|
|
346
|
+
Expect(tmpStatements[0].Statement).to.contain('CREATE UNIQUE INDEX');
|
|
347
|
+
Expect(tmpStatements[0].CheckStatement).to.contain('pg_indexes');
|
|
348
|
+
}
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
test
|
|
352
|
+
(
|
|
353
|
+
'IndexName property overrides auto-generated index name',
|
|
354
|
+
() =>
|
|
355
|
+
{
|
|
356
|
+
let tmpIndices = libSchemaPostgreSQL.getIndexDefinitionsFromSchema(_AnimalTableSchemaWithIndexName);
|
|
357
|
+
Expect(tmpIndices).to.be.an('array');
|
|
358
|
+
Expect(tmpIndices.length).to.equal(5);
|
|
359
|
+
Expect(tmpIndices[0].Name).to.equal('AK_M_GUIDAnimalCustomIdx');
|
|
360
|
+
Expect(tmpIndices[1].Name).to.equal('IX_Custom_Name');
|
|
361
|
+
Expect(tmpIndices[1].Unique).to.equal(false);
|
|
362
|
+
Expect(tmpIndices[2].Name).to.equal('UQ_Animal_Tag');
|
|
363
|
+
Expect(tmpIndices[2].Unique).to.equal(true);
|
|
364
|
+
Expect(tmpIndices[3].Name).to.equal('IX_M_T_AnimalCustomIdx_C_Weight');
|
|
365
|
+
Expect(tmpIndices[3].Unique).to.equal(false);
|
|
366
|
+
Expect(tmpIndices[4].Name).to.equal('IX_M_IDFarm');
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
test
|
|
371
|
+
(
|
|
372
|
+
'generate script with IndexName uses custom names in SQL',
|
|
373
|
+
() =>
|
|
374
|
+
{
|
|
375
|
+
let tmpScript = libSchemaPostgreSQL.generateCreateIndexScript(_AnimalTableSchemaWithIndexName);
|
|
376
|
+
Expect(tmpScript).to.contain('IX_Custom_Name');
|
|
377
|
+
Expect(tmpScript).to.contain('UQ_Animal_Tag');
|
|
378
|
+
Expect(tmpScript).to.contain('IX_M_T_AnimalCustomIdx_C_Weight');
|
|
379
|
+
Expect(tmpScript).to.not.contain('IX_M_T_AnimalCustomIdx_C_Name');
|
|
380
|
+
Expect(tmpScript).to.not.contain('AK_M_T_AnimalCustomIdx_C_TagNumber');
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
test
|
|
385
|
+
(
|
|
386
|
+
'schema provider is accessible from connection provider',
|
|
387
|
+
() =>
|
|
388
|
+
{
|
|
389
|
+
let _Fable = new libFable(_FableConfig);
|
|
390
|
+
_Fable.serviceManager.addServiceType('MeadowPostgreSQLProvider', libMeadowConnectionPostgreSQL);
|
|
391
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowPostgreSQLProvider');
|
|
392
|
+
Expect(_Fable.MeadowPostgreSQLProvider.schemaProvider).to.be.an('object');
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
suite
|
|
399
|
+
(
|
|
400
|
+
'Database Introspection',
|
|
401
|
+
()=>
|
|
402
|
+
{
|
|
403
|
+
let _Fable = null;
|
|
404
|
+
let libSchemaPostgreSQL = null;
|
|
405
|
+
|
|
406
|
+
setup(
|
|
407
|
+
(fDone) =>
|
|
408
|
+
{
|
|
409
|
+
_Fable = new libFable(_FableConfig);
|
|
410
|
+
_Fable.serviceManager.addServiceType('MeadowSchemaPostgreSQL', libMeadowSchemaPostgreSQL);
|
|
411
|
+
libSchemaPostgreSQL = _Fable.serviceManager.instantiateServiceProvider('MeadowSchemaPostgreSQL');
|
|
412
|
+
_Fable.serviceManager.addServiceType('MeadowPostgreSQLProvider', libMeadowConnectionPostgreSQL);
|
|
413
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowPostgreSQLProvider');
|
|
414
|
+
|
|
415
|
+
_Fable.MeadowPostgreSQLProvider.connectAsync(
|
|
416
|
+
(pError) =>
|
|
417
|
+
{
|
|
418
|
+
if (pError) return fDone(pError);
|
|
419
|
+
libSchemaPostgreSQL.setConnectionPool(_Fable.MeadowPostgreSQLProvider.pool);
|
|
420
|
+
|
|
421
|
+
// Drop test tables first (clean slate)
|
|
422
|
+
_Fable.MeadowPostgreSQLProvider.pool.query('DROP TABLE IF EXISTS "IntrospAnimal", "IntrospAnimalIdx", "IntrospAnimalCustIdx"',
|
|
423
|
+
(pDropError) =>
|
|
424
|
+
{
|
|
425
|
+
if (pDropError) return fDone(pDropError);
|
|
426
|
+
|
|
427
|
+
let tmpSchema = { Tables: [_IntrospectAnimalSchema, _IntrospectAnimalIndexedSchema, _IntrospectAnimalCustomIdxSchema] };
|
|
428
|
+
libSchemaPostgreSQL.createTables(tmpSchema,
|
|
429
|
+
(pCreateError) =>
|
|
430
|
+
{
|
|
431
|
+
if (pCreateError) return fDone(pCreateError);
|
|
432
|
+
libSchemaPostgreSQL.createAllIndices(tmpSchema,
|
|
433
|
+
(pIdxError) =>
|
|
434
|
+
{
|
|
435
|
+
return fDone(pIdxError);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test
|
|
443
|
+
(
|
|
444
|
+
'listTables returns tables including introspection test tables',
|
|
445
|
+
(fDone) =>
|
|
446
|
+
{
|
|
447
|
+
libSchemaPostgreSQL.listTables(
|
|
448
|
+
(pError, pTables) =>
|
|
449
|
+
{
|
|
450
|
+
Expect(pError).to.not.exist;
|
|
451
|
+
Expect(pTables).to.be.an('array');
|
|
452
|
+
Expect(pTables).to.include('IntrospAnimal');
|
|
453
|
+
Expect(pTables).to.include('IntrospAnimalIdx');
|
|
454
|
+
Expect(pTables).to.include('IntrospAnimalCustIdx');
|
|
455
|
+
return fDone();
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
test
|
|
461
|
+
(
|
|
462
|
+
'introspectTableColumns returns column definitions for IntrospAnimal',
|
|
463
|
+
(fDone) =>
|
|
464
|
+
{
|
|
465
|
+
libSchemaPostgreSQL.introspectTableColumns('IntrospAnimal',
|
|
466
|
+
(pError, pColumns) =>
|
|
467
|
+
{
|
|
468
|
+
Expect(pError).to.not.exist;
|
|
469
|
+
Expect(pColumns).to.be.an('array');
|
|
470
|
+
Expect(pColumns.length).to.equal(9);
|
|
471
|
+
|
|
472
|
+
// ID column (SERIAL)
|
|
473
|
+
Expect(pColumns[0].Column).to.equal('IDIntrospAnimal');
|
|
474
|
+
Expect(pColumns[0].DataType).to.equal('ID');
|
|
475
|
+
|
|
476
|
+
// GUID column (VARCHAR with GUID in name)
|
|
477
|
+
Expect(pColumns[1].Column).to.equal('GUIDIntrospAnimal');
|
|
478
|
+
Expect(pColumns[1].DataType).to.equal('GUID');
|
|
479
|
+
|
|
480
|
+
// String column (VARCHAR)
|
|
481
|
+
Expect(pColumns[2].Column).to.equal('Name');
|
|
482
|
+
Expect(pColumns[2].DataType).to.equal('String');
|
|
483
|
+
|
|
484
|
+
// Text column
|
|
485
|
+
Expect(pColumns[3].Column).to.equal('Description');
|
|
486
|
+
Expect(pColumns[3].DataType).to.equal('Text');
|
|
487
|
+
|
|
488
|
+
// Decimal column
|
|
489
|
+
Expect(pColumns[4].Column).to.equal('Cost');
|
|
490
|
+
Expect(pColumns[4].DataType).to.equal('Decimal');
|
|
491
|
+
|
|
492
|
+
// Numeric column (INTEGER)
|
|
493
|
+
Expect(pColumns[5].Column).to.equal('Age');
|
|
494
|
+
Expect(pColumns[5].DataType).to.equal('Numeric');
|
|
495
|
+
|
|
496
|
+
// DateTime column (TIMESTAMP)
|
|
497
|
+
Expect(pColumns[6].Column).to.equal('Birthday');
|
|
498
|
+
Expect(pColumns[6].DataType).to.equal('DateTime');
|
|
499
|
+
|
|
500
|
+
// Boolean column (native BOOLEAN)
|
|
501
|
+
Expect(pColumns[7].Column).to.equal('Active');
|
|
502
|
+
Expect(pColumns[7].DataType).to.equal('Boolean');
|
|
503
|
+
|
|
504
|
+
// ForeignKey column (no actual FK constraint, detected as Numeric)
|
|
505
|
+
Expect(pColumns[8].Column).to.equal('IDFarm');
|
|
506
|
+
Expect(pColumns[8].DataType).to.equal('Numeric');
|
|
507
|
+
|
|
508
|
+
return fDone();
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
test
|
|
514
|
+
(
|
|
515
|
+
'introspectTableIndices returns index definitions for IntrospAnimal',
|
|
516
|
+
(fDone) =>
|
|
517
|
+
{
|
|
518
|
+
libSchemaPostgreSQL.introspectTableIndices('IntrospAnimal',
|
|
519
|
+
(pError, pIndices) =>
|
|
520
|
+
{
|
|
521
|
+
Expect(pError).to.not.exist;
|
|
522
|
+
Expect(pIndices).to.be.an('array');
|
|
523
|
+
Expect(pIndices.length).to.equal(2);
|
|
524
|
+
|
|
525
|
+
let tmpNames = pIndices.map((pIdx) => { return pIdx.Name; });
|
|
526
|
+
Expect(tmpNames).to.include('AK_M_GUIDIntrospAnimal');
|
|
527
|
+
Expect(tmpNames).to.include('IX_M_IDFarm');
|
|
528
|
+
|
|
529
|
+
let tmpGUIDIndex = pIndices.find((pIdx) => { return pIdx.Name === 'AK_M_GUIDIntrospAnimal'; });
|
|
530
|
+
Expect(tmpGUIDIndex.Unique).to.equal(true);
|
|
531
|
+
Expect(tmpGUIDIndex.Columns).to.deep.equal(['GUIDIntrospAnimal']);
|
|
532
|
+
|
|
533
|
+
return fDone();
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
test
|
|
539
|
+
(
|
|
540
|
+
'introspectTableForeignKeys returns empty for table without FK constraints',
|
|
541
|
+
(fDone) =>
|
|
542
|
+
{
|
|
543
|
+
libSchemaPostgreSQL.introspectTableForeignKeys('IntrospAnimal',
|
|
544
|
+
(pError, pFKs) =>
|
|
545
|
+
{
|
|
546
|
+
Expect(pError).to.not.exist;
|
|
547
|
+
Expect(pFKs).to.be.an('array');
|
|
548
|
+
Expect(pFKs.length).to.equal(0);
|
|
549
|
+
return fDone();
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
test
|
|
555
|
+
(
|
|
556
|
+
'introspectTableSchema combines columns and indices for IntrospAnimalIdx',
|
|
557
|
+
(fDone) =>
|
|
558
|
+
{
|
|
559
|
+
libSchemaPostgreSQL.introspectTableSchema('IntrospAnimalIdx',
|
|
560
|
+
(pError, pSchema) =>
|
|
561
|
+
{
|
|
562
|
+
Expect(pError).to.not.exist;
|
|
563
|
+
Expect(pSchema).to.be.an('object');
|
|
564
|
+
Expect(pSchema.TableName).to.equal('IntrospAnimalIdx');
|
|
565
|
+
Expect(pSchema.Columns).to.be.an('array');
|
|
566
|
+
|
|
567
|
+
// Check that column-level Indexed properties are folded in
|
|
568
|
+
let tmpNameCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'Name'; });
|
|
569
|
+
Expect(tmpNameCol.Indexed).to.equal(true);
|
|
570
|
+
Expect(tmpNameCol).to.not.have.property('IndexName');
|
|
571
|
+
|
|
572
|
+
let tmpTagCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'TagNumber'; });
|
|
573
|
+
Expect(tmpTagCol.Indexed).to.equal('unique');
|
|
574
|
+
Expect(tmpTagCol).to.not.have.property('IndexName');
|
|
575
|
+
|
|
576
|
+
return fDone();
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
test
|
|
582
|
+
(
|
|
583
|
+
'introspectTableSchema preserves IndexName for custom-named indices',
|
|
584
|
+
(fDone) =>
|
|
585
|
+
{
|
|
586
|
+
libSchemaPostgreSQL.introspectTableSchema('IntrospAnimalCustIdx',
|
|
587
|
+
(pError, pSchema) =>
|
|
588
|
+
{
|
|
589
|
+
Expect(pError).to.not.exist;
|
|
590
|
+
Expect(pSchema.TableName).to.equal('IntrospAnimalCustIdx');
|
|
591
|
+
|
|
592
|
+
// Name has custom IndexName IX_Custom_Name
|
|
593
|
+
let tmpNameCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'Name'; });
|
|
594
|
+
Expect(tmpNameCol.Indexed).to.equal(true);
|
|
595
|
+
Expect(tmpNameCol.IndexName).to.equal('IX_Custom_Name');
|
|
596
|
+
|
|
597
|
+
// TagNumber has custom IndexName UQ_IntrospAnimalCustIdx_Tag
|
|
598
|
+
let tmpTagCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'TagNumber'; });
|
|
599
|
+
Expect(tmpTagCol.Indexed).to.equal('unique');
|
|
600
|
+
Expect(tmpTagCol.IndexName).to.equal('UQ_IntrospAnimalCustIdx_Tag');
|
|
601
|
+
|
|
602
|
+
// Weight has auto-generated name - no IndexName
|
|
603
|
+
let tmpWeightCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'Weight'; });
|
|
604
|
+
Expect(tmpWeightCol.Indexed).to.equal(true);
|
|
605
|
+
Expect(tmpWeightCol).to.not.have.property('IndexName');
|
|
606
|
+
|
|
607
|
+
return fDone();
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
test
|
|
613
|
+
(
|
|
614
|
+
'introspectDatabaseSchema returns schemas for all tables',
|
|
615
|
+
(fDone) =>
|
|
616
|
+
{
|
|
617
|
+
libSchemaPostgreSQL.introspectDatabaseSchema(
|
|
618
|
+
(pError, pSchema) =>
|
|
619
|
+
{
|
|
620
|
+
Expect(pError).to.not.exist;
|
|
621
|
+
Expect(pSchema).to.be.an('object');
|
|
622
|
+
Expect(pSchema.Tables).to.be.an('array');
|
|
623
|
+
Expect(pSchema.Tables.length).to.be.greaterThan(0);
|
|
624
|
+
|
|
625
|
+
let tmpTableNames = pSchema.Tables.map((pT) => { return pT.TableName; });
|
|
626
|
+
Expect(tmpTableNames).to.include('IntrospAnimal');
|
|
627
|
+
Expect(tmpTableNames).to.include('IntrospAnimalIdx');
|
|
628
|
+
Expect(tmpTableNames).to.include('IntrospAnimalCustIdx');
|
|
629
|
+
|
|
630
|
+
return fDone();
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
test
|
|
636
|
+
(
|
|
637
|
+
'generateMeadowPackageFromTable produces Meadow package JSON',
|
|
638
|
+
(fDone) =>
|
|
639
|
+
{
|
|
640
|
+
libSchemaPostgreSQL.generateMeadowPackageFromTable('IntrospAnimal',
|
|
641
|
+
(pError, pPackage) =>
|
|
642
|
+
{
|
|
643
|
+
Expect(pError).to.not.exist;
|
|
644
|
+
Expect(pPackage).to.be.an('object');
|
|
645
|
+
Expect(pPackage.Scope).to.equal('IntrospAnimal');
|
|
646
|
+
Expect(pPackage.DefaultIdentifier).to.equal('IDIntrospAnimal');
|
|
647
|
+
Expect(pPackage.Schema).to.be.an('array');
|
|
648
|
+
Expect(pPackage.DefaultObject).to.be.an('object');
|
|
649
|
+
|
|
650
|
+
// Verify schema entries
|
|
651
|
+
let tmpIDEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'IDIntrospAnimal'; });
|
|
652
|
+
Expect(tmpIDEntry.Type).to.equal('AutoIdentity');
|
|
653
|
+
|
|
654
|
+
let tmpGUIDEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'GUIDIntrospAnimal'; });
|
|
655
|
+
Expect(tmpGUIDEntry.Type).to.equal('AutoGUID');
|
|
656
|
+
|
|
657
|
+
let tmpNameEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'Name'; });
|
|
658
|
+
Expect(tmpNameEntry.Type).to.equal('String');
|
|
659
|
+
|
|
660
|
+
// Verify default object
|
|
661
|
+
Expect(pPackage.DefaultObject.IDIntrospAnimal).to.equal(0);
|
|
662
|
+
Expect(pPackage.DefaultObject.GUIDIntrospAnimal).to.equal('');
|
|
663
|
+
Expect(pPackage.DefaultObject.Name).to.equal('');
|
|
664
|
+
|
|
665
|
+
return fDone();
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
test
|
|
671
|
+
(
|
|
672
|
+
'round-trip: introspect IntrospAnimalIdx and regenerate matching indices',
|
|
673
|
+
(fDone) =>
|
|
674
|
+
{
|
|
675
|
+
libSchemaPostgreSQL.introspectTableSchema('IntrospAnimalIdx',
|
|
676
|
+
(pError, pSchema) =>
|
|
677
|
+
{
|
|
678
|
+
Expect(pError).to.not.exist;
|
|
679
|
+
|
|
680
|
+
// Use the introspected schema to generate index definitions
|
|
681
|
+
let tmpIndices = libSchemaPostgreSQL.getIndexDefinitionsFromSchema(pSchema);
|
|
682
|
+
|
|
683
|
+
// The original IntrospAnimalIdx had:
|
|
684
|
+
// AK_M_GUIDIntrospAnimalIdx (GUID auto)
|
|
685
|
+
// IX_M_T_IntrospAnimalIdx_C_Name (Indexed: true)
|
|
686
|
+
// AK_M_T_IntrospAnimalIdx_C_TagNumber (Indexed: 'unique')
|
|
687
|
+
// IX_M_IDOwner (FK auto)
|
|
688
|
+
let tmpNames = tmpIndices.map((pIdx) => { return pIdx.Name; });
|
|
689
|
+
Expect(tmpNames).to.include('AK_M_GUIDIntrospAnimalIdx');
|
|
690
|
+
Expect(tmpNames).to.include('IX_M_T_IntrospAnimalIdx_C_Name');
|
|
691
|
+
Expect(tmpNames).to.include('AK_M_T_IntrospAnimalIdx_C_TagNumber');
|
|
692
|
+
Expect(tmpNames).to.include('IX_M_IDOwner');
|
|
693
|
+
|
|
694
|
+
return fDone();
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
test
|
|
700
|
+
(
|
|
701
|
+
'round-trip: introspect IntrospAnimalCustIdx and regenerate matching index names',
|
|
702
|
+
(fDone) =>
|
|
703
|
+
{
|
|
704
|
+
libSchemaPostgreSQL.introspectTableSchema('IntrospAnimalCustIdx',
|
|
705
|
+
(pError, pSchema) =>
|
|
706
|
+
{
|
|
707
|
+
Expect(pError).to.not.exist;
|
|
708
|
+
|
|
709
|
+
// Use the introspected schema to generate index definitions
|
|
710
|
+
let tmpIndices = libSchemaPostgreSQL.getIndexDefinitionsFromSchema(pSchema);
|
|
711
|
+
|
|
712
|
+
// The original IntrospAnimalCustIdx had:
|
|
713
|
+
// AK_M_GUIDIntrospAnimalCustIdx (GUID auto)
|
|
714
|
+
// IX_Custom_Name (IndexName override)
|
|
715
|
+
// UQ_IntrospAnimalCustIdx_Tag (IndexName override, unique)
|
|
716
|
+
// IX_M_T_IntrospAnimalCustIdx_C_Weight (auto)
|
|
717
|
+
// IX_M_IDTrainer (FK auto)
|
|
718
|
+
let tmpNames = tmpIndices.map((pIdx) => { return pIdx.Name; });
|
|
719
|
+
Expect(tmpNames).to.include('AK_M_GUIDIntrospAnimalCustIdx');
|
|
720
|
+
Expect(tmpNames).to.include('IX_Custom_Name');
|
|
721
|
+
Expect(tmpNames).to.include('UQ_IntrospAnimalCustIdx_Tag');
|
|
722
|
+
Expect(tmpNames).to.include('IX_M_T_IntrospAnimalCustIdx_C_Weight');
|
|
723
|
+
Expect(tmpNames).to.include('IX_M_IDTrainer');
|
|
724
|
+
|
|
725
|
+
return fDone();
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
suite
|
|
733
|
+
(
|
|
734
|
+
'Chinook Database Introspection',
|
|
735
|
+
()=>
|
|
736
|
+
{
|
|
737
|
+
let _Fable = null;
|
|
738
|
+
let libSchemaPostgreSQL = null;
|
|
739
|
+
|
|
740
|
+
setup(
|
|
741
|
+
(fDone) =>
|
|
742
|
+
{
|
|
743
|
+
_Fable = new libFable(_FableConfig);
|
|
744
|
+
_Fable.serviceManager.addServiceType('MeadowSchemaPostgreSQL', libMeadowSchemaPostgreSQL);
|
|
745
|
+
libSchemaPostgreSQL = _Fable.serviceManager.instantiateServiceProvider('MeadowSchemaPostgreSQL');
|
|
746
|
+
_Fable.serviceManager.addServiceType('MeadowPostgreSQLProvider', libMeadowConnectionPostgreSQL);
|
|
747
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowPostgreSQLProvider');
|
|
748
|
+
_Fable.MeadowPostgreSQLProvider.connectAsync(
|
|
749
|
+
(pError) =>
|
|
750
|
+
{
|
|
751
|
+
if (pError) return fDone(pError);
|
|
752
|
+
libSchemaPostgreSQL.setConnectionPool(_Fable.MeadowPostgreSQLProvider.pool);
|
|
753
|
+
return fDone();
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
test
|
|
758
|
+
(
|
|
759
|
+
'listTables includes all 11 Chinook tables',
|
|
760
|
+
(fDone) =>
|
|
761
|
+
{
|
|
762
|
+
libSchemaPostgreSQL.listTables(
|
|
763
|
+
(pError, pTables) =>
|
|
764
|
+
{
|
|
765
|
+
Expect(pError).to.not.exist;
|
|
766
|
+
Expect(pTables).to.be.an('array');
|
|
767
|
+
|
|
768
|
+
let tmpChinookTables = ['album', 'artist', 'customer', 'employee',
|
|
769
|
+
'genre', 'invoice', 'invoice_line', 'media_type',
|
|
770
|
+
'playlist', 'playlist_track', 'track'];
|
|
771
|
+
|
|
772
|
+
tmpChinookTables.forEach(
|
|
773
|
+
(pTableName) =>
|
|
774
|
+
{
|
|
775
|
+
Expect(pTables).to.include(pTableName);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
return fDone();
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
test
|
|
784
|
+
(
|
|
785
|
+
'introspectTableColumns on track detects all 9 columns with correct types',
|
|
786
|
+
(fDone) =>
|
|
787
|
+
{
|
|
788
|
+
libSchemaPostgreSQL.introspectTableColumns('track',
|
|
789
|
+
(pError, pColumns) =>
|
|
790
|
+
{
|
|
791
|
+
Expect(pError).to.not.exist;
|
|
792
|
+
Expect(pColumns).to.be.an('array');
|
|
793
|
+
Expect(pColumns.length).to.equal(9);
|
|
794
|
+
|
|
795
|
+
let tmpTrackId = pColumns.find((pCol) => { return pCol.Column === 'track_id'; });
|
|
796
|
+
Expect(tmpTrackId.DataType).to.equal('ID');
|
|
797
|
+
|
|
798
|
+
let tmpName = pColumns.find((pCol) => { return pCol.Column === 'name'; });
|
|
799
|
+
Expect(tmpName.DataType).to.equal('String');
|
|
800
|
+
|
|
801
|
+
let tmpUnitPrice = pColumns.find((pCol) => { return pCol.Column === 'unit_price'; });
|
|
802
|
+
Expect(tmpUnitPrice.DataType).to.equal('Decimal');
|
|
803
|
+
|
|
804
|
+
let tmpMilliseconds = pColumns.find((pCol) => { return pCol.Column === 'milliseconds'; });
|
|
805
|
+
Expect(tmpMilliseconds.DataType).to.equal('Numeric');
|
|
806
|
+
|
|
807
|
+
return fDone();
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
test
|
|
813
|
+
(
|
|
814
|
+
'introspectTableColumns on employee detects 15 columns',
|
|
815
|
+
(fDone) =>
|
|
816
|
+
{
|
|
817
|
+
libSchemaPostgreSQL.introspectTableColumns('employee',
|
|
818
|
+
(pError, pColumns) =>
|
|
819
|
+
{
|
|
820
|
+
Expect(pError).to.not.exist;
|
|
821
|
+
Expect(pColumns.length).to.equal(15);
|
|
822
|
+
|
|
823
|
+
let tmpEmployeeId = pColumns.find((pCol) => { return pCol.Column === 'employee_id'; });
|
|
824
|
+
Expect(tmpEmployeeId.DataType).to.equal('ID');
|
|
825
|
+
|
|
826
|
+
let tmpBirthDate = pColumns.find((pCol) => { return pCol.Column === 'birth_date'; });
|
|
827
|
+
Expect(tmpBirthDate.DataType).to.equal('DateTime');
|
|
828
|
+
|
|
829
|
+
return fDone();
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
test
|
|
835
|
+
(
|
|
836
|
+
'introspectTableForeignKeys on track detects 3 FK relationships',
|
|
837
|
+
(fDone) =>
|
|
838
|
+
{
|
|
839
|
+
libSchemaPostgreSQL.introspectTableForeignKeys('track',
|
|
840
|
+
(pError, pFKs) =>
|
|
841
|
+
{
|
|
842
|
+
Expect(pError).to.not.exist;
|
|
843
|
+
Expect(pFKs).to.be.an('array');
|
|
844
|
+
Expect(pFKs.length).to.equal(3);
|
|
845
|
+
|
|
846
|
+
let tmpAlbumFK = pFKs.find((pFK) => { return pFK.Column === 'album_id'; });
|
|
847
|
+
Expect(tmpAlbumFK).to.exist;
|
|
848
|
+
Expect(tmpAlbumFK.ReferencedTable).to.equal('album');
|
|
849
|
+
Expect(tmpAlbumFK.ReferencedColumn).to.equal('album_id');
|
|
850
|
+
|
|
851
|
+
let tmpMediaTypeFK = pFKs.find((pFK) => { return pFK.Column === 'media_type_id'; });
|
|
852
|
+
Expect(tmpMediaTypeFK).to.exist;
|
|
853
|
+
Expect(tmpMediaTypeFK.ReferencedTable).to.equal('media_type');
|
|
854
|
+
|
|
855
|
+
let tmpGenreFK = pFKs.find((pFK) => { return pFK.Column === 'genre_id'; });
|
|
856
|
+
Expect(tmpGenreFK).to.exist;
|
|
857
|
+
Expect(tmpGenreFK.ReferencedTable).to.equal('genre');
|
|
858
|
+
|
|
859
|
+
return fDone();
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
test
|
|
865
|
+
(
|
|
866
|
+
'introspectTableForeignKeys on employee detects self-referential FK',
|
|
867
|
+
(fDone) =>
|
|
868
|
+
{
|
|
869
|
+
libSchemaPostgreSQL.introspectTableForeignKeys('employee',
|
|
870
|
+
(pError, pFKs) =>
|
|
871
|
+
{
|
|
872
|
+
Expect(pError).to.not.exist;
|
|
873
|
+
Expect(pFKs).to.be.an('array');
|
|
874
|
+
Expect(pFKs.length).to.equal(1);
|
|
875
|
+
|
|
876
|
+
Expect(pFKs[0].Column).to.equal('reports_to');
|
|
877
|
+
Expect(pFKs[0].ReferencedTable).to.equal('employee');
|
|
878
|
+
Expect(pFKs[0].ReferencedColumn).to.equal('employee_id');
|
|
879
|
+
|
|
880
|
+
return fDone();
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
test
|
|
886
|
+
(
|
|
887
|
+
'introspectTableForeignKeys on playlist_track detects 2 FKs',
|
|
888
|
+
(fDone) =>
|
|
889
|
+
{
|
|
890
|
+
libSchemaPostgreSQL.introspectTableForeignKeys('playlist_track',
|
|
891
|
+
(pError, pFKs) =>
|
|
892
|
+
{
|
|
893
|
+
Expect(pError).to.not.exist;
|
|
894
|
+
Expect(pFKs).to.be.an('array');
|
|
895
|
+
Expect(pFKs.length).to.equal(2);
|
|
896
|
+
|
|
897
|
+
let tmpPlaylistFK = pFKs.find((pFK) => { return pFK.Column === 'playlist_id'; });
|
|
898
|
+
Expect(tmpPlaylistFK).to.exist;
|
|
899
|
+
Expect(tmpPlaylistFK.ReferencedTable).to.equal('playlist');
|
|
900
|
+
|
|
901
|
+
let tmpTrackFK = pFKs.find((pFK) => { return pFK.Column === 'track_id'; });
|
|
902
|
+
Expect(tmpTrackFK).to.exist;
|
|
903
|
+
Expect(tmpTrackFK.ReferencedTable).to.equal('track');
|
|
904
|
+
|
|
905
|
+
return fDone();
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
test
|
|
911
|
+
(
|
|
912
|
+
'introspectTableSchema on track combines columns with FK detection',
|
|
913
|
+
(fDone) =>
|
|
914
|
+
{
|
|
915
|
+
libSchemaPostgreSQL.introspectTableSchema('track',
|
|
916
|
+
(pError, pSchema) =>
|
|
917
|
+
{
|
|
918
|
+
Expect(pError).to.not.exist;
|
|
919
|
+
Expect(pSchema.TableName).to.equal('track');
|
|
920
|
+
Expect(pSchema.ForeignKeys.length).to.equal(3);
|
|
921
|
+
|
|
922
|
+
let tmpAlbumIdCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'album_id'; });
|
|
923
|
+
Expect(tmpAlbumIdCol.DataType).to.equal('ForeignKey');
|
|
924
|
+
|
|
925
|
+
let tmpMediaTypeIdCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'media_type_id'; });
|
|
926
|
+
Expect(tmpMediaTypeIdCol.DataType).to.equal('ForeignKey');
|
|
927
|
+
|
|
928
|
+
let tmpGenreIdCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'genre_id'; });
|
|
929
|
+
Expect(tmpGenreIdCol.DataType).to.equal('ForeignKey');
|
|
930
|
+
|
|
931
|
+
return fDone();
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
test
|
|
937
|
+
(
|
|
938
|
+
'introspectDatabaseSchema includes all Chinook tables',
|
|
939
|
+
(fDone) =>
|
|
940
|
+
{
|
|
941
|
+
libSchemaPostgreSQL.introspectDatabaseSchema(
|
|
942
|
+
(pError, pSchema) =>
|
|
943
|
+
{
|
|
944
|
+
Expect(pError).to.not.exist;
|
|
945
|
+
Expect(pSchema.Tables).to.be.an('array');
|
|
946
|
+
|
|
947
|
+
let tmpTableNames = pSchema.Tables.map((pT) => { return pT.TableName; });
|
|
948
|
+
Expect(tmpTableNames).to.include('track');
|
|
949
|
+
Expect(tmpTableNames).to.include('album');
|
|
950
|
+
Expect(tmpTableNames).to.include('artist');
|
|
951
|
+
Expect(tmpTableNames).to.include('employee');
|
|
952
|
+
Expect(tmpTableNames).to.include('customer');
|
|
953
|
+
Expect(tmpTableNames).to.include('invoice');
|
|
954
|
+
Expect(tmpTableNames).to.include('invoice_line');
|
|
955
|
+
Expect(tmpTableNames).to.include('playlist_track');
|
|
956
|
+
|
|
957
|
+
let tmpTrack = pSchema.Tables.find((pT) => { return pT.TableName === 'track'; });
|
|
958
|
+
Expect(tmpTrack.ForeignKeys.length).to.equal(3);
|
|
959
|
+
|
|
960
|
+
return fDone();
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
);
|
|
964
|
+
|
|
965
|
+
test
|
|
966
|
+
(
|
|
967
|
+
'generateMeadowPackageFromTable on album produces valid package',
|
|
968
|
+
(fDone) =>
|
|
969
|
+
{
|
|
970
|
+
libSchemaPostgreSQL.generateMeadowPackageFromTable('album',
|
|
971
|
+
(pError, pPackage) =>
|
|
972
|
+
{
|
|
973
|
+
Expect(pError).to.not.exist;
|
|
974
|
+
Expect(pPackage.Scope).to.equal('album');
|
|
975
|
+
Expect(pPackage.DefaultIdentifier).to.equal('album_id');
|
|
976
|
+
Expect(pPackage.Schema).to.be.an('array');
|
|
977
|
+
Expect(pPackage.DefaultObject).to.be.an('object');
|
|
978
|
+
|
|
979
|
+
let tmpIDEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'album_id'; });
|
|
980
|
+
Expect(tmpIDEntry.Type).to.equal('AutoIdentity');
|
|
981
|
+
|
|
982
|
+
let tmpTitleEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'title'; });
|
|
983
|
+
Expect(tmpTitleEntry.Type).to.equal('String');
|
|
984
|
+
|
|
985
|
+
return fDone();
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
);
|
|
989
|
+
|
|
990
|
+
test
|
|
991
|
+
(
|
|
992
|
+
'generateMeadowPackageFromTable on track handles FKs and Decimal',
|
|
993
|
+
(fDone) =>
|
|
994
|
+
{
|
|
995
|
+
libSchemaPostgreSQL.generateMeadowPackageFromTable('track',
|
|
996
|
+
(pError, pPackage) =>
|
|
997
|
+
{
|
|
998
|
+
Expect(pError).to.not.exist;
|
|
999
|
+
Expect(pPackage.Scope).to.equal('track');
|
|
1000
|
+
Expect(pPackage.DefaultIdentifier).to.equal('track_id');
|
|
1001
|
+
|
|
1002
|
+
let tmpUnitPriceEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'unit_price'; });
|
|
1003
|
+
Expect(tmpUnitPriceEntry).to.exist;
|
|
1004
|
+
|
|
1005
|
+
return fDone();
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
);
|
|
147
1011
|
}
|
|
148
1012
|
);
|