@platformatic/sql-mapper 0.11.0 → 0.12.1
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/lib/entity.js +67 -8
- package/lib/queries/mysql-shared.js +2 -2
- package/lib/queries/pg.js +2 -2
- package/lib/queries/sqlite.js +21 -1
- package/mapper.js +2 -1
- package/package.json +7 -7
- package/test/entity.test.js +152 -0
- package/test/helper.js +5 -0
- package/test/mapper.test.js +0 -17
- package/test/no-primary-key.test.js +79 -0
- package/test/schema.test.js +62 -1
package/lib/entity.js
CHANGED
|
@@ -7,6 +7,12 @@ const {
|
|
|
7
7
|
tableName,
|
|
8
8
|
sanitizeLimit
|
|
9
9
|
} = require('./utils')
|
|
10
|
+
const { singularize } = require('inflected')
|
|
11
|
+
|
|
12
|
+
function lowerCaseFirst (str) {
|
|
13
|
+
str = str.toString()
|
|
14
|
+
return str.charAt(0).toLowerCase() + str.slice(1)
|
|
15
|
+
}
|
|
10
16
|
|
|
11
17
|
function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema, useSchemaInName, limitConfig) {
|
|
12
18
|
/* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite)
|
|
@@ -342,6 +348,17 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
|
|
|
342
348
|
if (autoTimestamp && (column.column_name === autoTimestamp.createdAt || column.column_name === autoTimestamp.updatedAt)) {
|
|
343
349
|
acc[column.column_name].autoTimestamp = true
|
|
344
350
|
}
|
|
351
|
+
|
|
352
|
+
// To get generated information
|
|
353
|
+
/* istanbul ignore next */
|
|
354
|
+
if (db.isPg) {
|
|
355
|
+
acc[column.column_name].isGenerated = column.is_generated !== 'NEVER'
|
|
356
|
+
} else if (db.isSQLite) {
|
|
357
|
+
acc[column.column_name].isGenerated = column.is_generated === 'YES'
|
|
358
|
+
} else {
|
|
359
|
+
acc[column.column_name].isGenerated = column.is_generated.includes('GENERATED')
|
|
360
|
+
}
|
|
361
|
+
|
|
345
362
|
return acc
|
|
346
363
|
}, {})
|
|
347
364
|
|
|
@@ -362,6 +379,17 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
|
|
|
362
379
|
const constraintsList = await queries.listConstraints(db, sql, table, schema)
|
|
363
380
|
const primaryKeys = new Set()
|
|
364
381
|
|
|
382
|
+
/* istanbul ignore next */
|
|
383
|
+
function checkSQLitePrimaryKey (constraint) {
|
|
384
|
+
if (db.isSQLite) {
|
|
385
|
+
const validTypes = ['integer', 'uuid', 'serial']
|
|
386
|
+
const pkType = fields[constraint.column_name].sqlType.toLowerCase()
|
|
387
|
+
if (!validTypes.includes(pkType)) {
|
|
388
|
+
throw new Error(`Invalid Primary Key type. Expected "integer", found "${pkType}"`)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
365
393
|
for (const constraint of constraintsList) {
|
|
366
394
|
const field = fields[constraint.column_name]
|
|
367
395
|
|
|
@@ -377,23 +405,54 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
|
|
|
377
405
|
if (constraint.constraint_type === 'PRIMARY KEY') {
|
|
378
406
|
primaryKeys.add(constraint.column_name)
|
|
379
407
|
// Check for SQLite typeless PK
|
|
380
|
-
|
|
381
|
-
if (db.isSQLite) {
|
|
382
|
-
const validTypes = ['integer', 'uuid', 'serial']
|
|
383
|
-
const pkType = fields[constraint.column_name].sqlType.toLowerCase()
|
|
384
|
-
if (!validTypes.includes(pkType)) {
|
|
385
|
-
throw new Error(`Invalid Primary Key type. Expected "integer", found "${pkType}"`)
|
|
386
|
-
}
|
|
387
|
-
}
|
|
408
|
+
checkSQLitePrimaryKey(constraint)
|
|
388
409
|
field.primaryKey = true
|
|
389
410
|
}
|
|
390
411
|
|
|
391
412
|
if (constraint.constraint_type === 'FOREIGN KEY') {
|
|
392
413
|
field.foreignKey = true
|
|
414
|
+
|
|
415
|
+
// we need to ignore for coverage here becasue cannot be covered with sqlite (no schema support)
|
|
416
|
+
// istanbul ignore next
|
|
417
|
+
const foreignEntityName = singularize(camelcase(useSchemaInName ? camelcase(`${constraint.foreign_table_schema} ${constraint.foreign_table_name}`) : constraint.foreign_table_name))
|
|
418
|
+
// istanbul ignore next
|
|
419
|
+
const entityName = singularize(camelcase(useSchemaInName ? camelcase(`${constraint.table_schema} ${constraint.table_name}`) : constraint.table_name))
|
|
420
|
+
// istanbul ignore next
|
|
421
|
+
const loweredTableWithSchemaName = lowerCaseFirst(useSchemaInName ? camelcase(`${constraint.table_schema} ${camelcase(constraint.table_name)}`) : camelcase(constraint.table_name))
|
|
422
|
+
constraint.loweredTableWithSchemaName = loweredTableWithSchemaName
|
|
423
|
+
constraint.foreignEntityName = foreignEntityName
|
|
424
|
+
constraint.entityName = entityName
|
|
393
425
|
currentRelations.push(constraint)
|
|
394
426
|
}
|
|
395
427
|
}
|
|
396
428
|
|
|
429
|
+
if (primaryKeys.size === 0) {
|
|
430
|
+
let found = false
|
|
431
|
+
for (const constraint of constraintsList) {
|
|
432
|
+
const field = fields[constraint.column_name]
|
|
433
|
+
|
|
434
|
+
/* istanbul ignore else */
|
|
435
|
+
if (constraint.constraint_type === 'UNIQUE') {
|
|
436
|
+
field.unique = true
|
|
437
|
+
|
|
438
|
+
/* istanbul ignore else */
|
|
439
|
+
if (!found) {
|
|
440
|
+
// Check for SQLite typeless PK
|
|
441
|
+
/* istanbul ignore next */
|
|
442
|
+
try {
|
|
443
|
+
checkSQLitePrimaryKey(constraint)
|
|
444
|
+
} catch {
|
|
445
|
+
continue
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
primaryKeys.add(constraint.column_name)
|
|
449
|
+
field.primaryKey = true
|
|
450
|
+
found = true
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
397
456
|
const entity = createMapper(db, sql, log, table, fields, primaryKeys, currentRelations, queries, autoTimestamp, schema, useSchemaInName, limitConfig)
|
|
398
457
|
entity.relations = currentRelations
|
|
399
458
|
|
|
@@ -23,7 +23,7 @@ async function listTables (db, sql, schemas) {
|
|
|
23
23
|
|
|
24
24
|
async function listColumns (db, sql, table, schema) {
|
|
25
25
|
const query = sql`
|
|
26
|
-
SELECT column_name as column_name, data_type as udt_name, is_nullable as is_nullable, column_type as column_type
|
|
26
|
+
SELECT column_name as column_name, data_type as udt_name, is_nullable as is_nullable, column_type as column_type, extra as is_generated
|
|
27
27
|
FROM information_schema.columns
|
|
28
28
|
WHERE table_name = ${table}
|
|
29
29
|
AND table_schema = ${schema}
|
|
@@ -33,7 +33,7 @@ async function listColumns (db, sql, table, schema) {
|
|
|
33
33
|
|
|
34
34
|
async function listConstraints (db, sql, table, schema) {
|
|
35
35
|
const query = sql`
|
|
36
|
-
SELECT TABLE_NAME as table_name, COLUMN_NAME as column_name, CONSTRAINT_TYPE as constraint_type, referenced_table_name AS foreign_table_name, referenced_column_name AS foreign_column_name
|
|
36
|
+
SELECT TABLE_NAME as table_name, TABLE_SCHEMA as table_schema, COLUMN_NAME as column_name, CONSTRAINT_TYPE as constraint_type, referenced_table_name AS foreign_table_name, referenced_table_schema AS foreign_table_schema, referenced_column_name AS foreign_column_name
|
|
37
37
|
FROM information_schema.table_constraints t
|
|
38
38
|
JOIN information_schema.key_column_usage k
|
|
39
39
|
USING (constraint_name, table_schema, table_name)
|
package/lib/queries/pg.js
CHANGED
|
@@ -44,7 +44,7 @@ module.exports.listTables = listTables
|
|
|
44
44
|
|
|
45
45
|
async function listColumns (db, sql, table, schema) {
|
|
46
46
|
return db.query(sql`
|
|
47
|
-
SELECT column_name, udt_name, is_nullable
|
|
47
|
+
SELECT column_name, udt_name, is_nullable, is_generated
|
|
48
48
|
FROM information_schema.columns
|
|
49
49
|
WHERE table_name = ${table}
|
|
50
50
|
AND table_schema = ${schema}
|
|
@@ -55,7 +55,7 @@ module.exports.listColumns = listColumns
|
|
|
55
55
|
|
|
56
56
|
async function listConstraints (db, sql, table, schema) {
|
|
57
57
|
const query = sql`
|
|
58
|
-
SELECT constraints.*, usage.*, usage2.table_name AS foreign_table_name, usage2.column_name AS foreign_column_name
|
|
58
|
+
SELECT constraints.*, usage.*, usage2.table_name AS foreign_table_name, usage2.column_name AS foreign_column_name, usage2.table_schema AS foreign_table_schema
|
|
59
59
|
FROM information_schema.table_constraints constraints
|
|
60
60
|
JOIN information_schema.key_column_usage usage
|
|
61
61
|
ON constraints.constraint_name = usage.constraint_name
|
package/lib/queries/sqlite.js
CHANGED
|
@@ -15,8 +15,10 @@ async function listTables (db, sql) {
|
|
|
15
15
|
module.exports.listTables = listTables
|
|
16
16
|
|
|
17
17
|
async function listColumns (db, sql, table) {
|
|
18
|
+
// pragma_table_info is not returning hidden column which tells if the column is generated or not
|
|
19
|
+
// therefore it is changed to pragma_table_xinfo
|
|
18
20
|
const columns = await db.query(sql`
|
|
19
|
-
SELECT * FROM
|
|
21
|
+
SELECT * FROM pragma_table_xinfo(${table})
|
|
20
22
|
`)
|
|
21
23
|
for (const column of columns) {
|
|
22
24
|
column.column_name = column.name
|
|
@@ -24,6 +26,8 @@ async function listColumns (db, sql, table) {
|
|
|
24
26
|
column.udt_name = column.type.replace(/^([^(]+).*/, '$1').toLowerCase()
|
|
25
27
|
// convert is_nullable
|
|
26
28
|
column.is_nullable = column.notnull === 0 && column.pk === 0 ? 'YES' : 'NO'
|
|
29
|
+
// convert hidden to is_generated
|
|
30
|
+
column.is_generated = (column.hidden === 2 || column.hidden === 3) ? 'YES' : 'NO'
|
|
27
31
|
}
|
|
28
32
|
return columns
|
|
29
33
|
}
|
|
@@ -45,6 +49,22 @@ async function listConstraints (db, sql, table) {
|
|
|
45
49
|
})
|
|
46
50
|
}
|
|
47
51
|
|
|
52
|
+
const indexes = await db.query(sql`
|
|
53
|
+
SELECT *
|
|
54
|
+
FROM pragma_index_list(${table}) as il
|
|
55
|
+
JOIN pragma_index_info(il.name) as ii
|
|
56
|
+
`)
|
|
57
|
+
|
|
58
|
+
for (const index of indexes) {
|
|
59
|
+
/* istanbul ignore else */
|
|
60
|
+
if (index.unique === 1) {
|
|
61
|
+
constraints.push({
|
|
62
|
+
column_name: index.name,
|
|
63
|
+
constraint_type: 'UNIQUE'
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
const foreignKeys = await db.query(sql`
|
|
49
69
|
SELECT *
|
|
50
70
|
FROM pragma_foreign_key_list(${table})
|
package/mapper.js
CHANGED
|
@@ -125,7 +125,8 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
|
|
|
125
125
|
const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit)
|
|
126
126
|
// Check for primary key of all entities
|
|
127
127
|
if (entity.primaryKeys.size === 0) {
|
|
128
|
-
|
|
128
|
+
log.warn({ table }, 'Cannot find any primary keys for table')
|
|
129
|
+
continue
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
entities[entity.singularName] = entity
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/sql-mapper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "A data mapper utility for SQL databases",
|
|
5
5
|
"main": "mapper.js",
|
|
6
6
|
"repository": {
|
|
@@ -14,19 +14,19 @@
|
|
|
14
14
|
},
|
|
15
15
|
"homepage": "https://github.com/platformatic/platformatic#readme",
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"fastify": "^4.
|
|
17
|
+
"fastify": "^4.10.2",
|
|
18
18
|
"snazzy": "^9.0.0",
|
|
19
19
|
"standard": "^17.0.0",
|
|
20
|
-
"tap": "^16.
|
|
20
|
+
"tap": "^16.3.2",
|
|
21
21
|
"tsd": "^0.25.0"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@databases/mysql": "^5.2.
|
|
25
|
-
"@databases/pg": "^5.
|
|
24
|
+
"@databases/mysql": "^5.2.1",
|
|
25
|
+
"@databases/pg": "^5.4.1",
|
|
26
26
|
"@databases/sql": "^3.2.0",
|
|
27
27
|
"@databases/sqlite": "^4.0.2",
|
|
28
|
-
"camelcase": "^6.
|
|
29
|
-
"fastify-plugin": "^4.
|
|
28
|
+
"camelcase": "^6.3.0",
|
|
29
|
+
"fastify-plugin": "^4.4.0",
|
|
30
30
|
"inflected": "^2.1.0"
|
|
31
31
|
},
|
|
32
32
|
"tsd": {
|
package/test/entity.test.js
CHANGED
|
@@ -717,3 +717,155 @@ test('JSON type', { skip: !(isPg || isMysql8) }, async ({ teardown, same, equal,
|
|
|
717
717
|
}
|
|
718
718
|
}), [{ id: 2, config: { foo: 'bar', bar: 'foo' } }])
|
|
719
719
|
})
|
|
720
|
+
|
|
721
|
+
test('stored and virtual generated columns should return for SQLite', { skip: !(isSQLite) }, async ({ teardown, same }) => {
|
|
722
|
+
async function onDatabaseLoad (db, sql) {
|
|
723
|
+
await clear(db, sql)
|
|
724
|
+
teardown(() => db.dispose())
|
|
725
|
+
|
|
726
|
+
await db.query(sql`CREATE TABLE generated_test (
|
|
727
|
+
id INTEGER PRIMARY KEY,
|
|
728
|
+
test INTEGER,
|
|
729
|
+
test_stored INTEGER GENERATED ALWAYS AS (test*2) STORED,
|
|
730
|
+
test_virtual INTEGER GENERATED ALWAYS AS (test*4) VIRTUAL
|
|
731
|
+
);`)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const mapper = await connect({
|
|
735
|
+
connectionString: connInfo.connectionString,
|
|
736
|
+
log: fakeLogger,
|
|
737
|
+
onDatabaseLoad,
|
|
738
|
+
ignore: {},
|
|
739
|
+
hooks: {}
|
|
740
|
+
})
|
|
741
|
+
|
|
742
|
+
const generatedTest = mapper.entities.generatedTest
|
|
743
|
+
|
|
744
|
+
// save - new record
|
|
745
|
+
same(await generatedTest.save({
|
|
746
|
+
input: { test: 1 }
|
|
747
|
+
}), { id: 1, test: 1, testStored: 2, testVirtual: 4 })
|
|
748
|
+
|
|
749
|
+
// save - update
|
|
750
|
+
same(await generatedTest.save({
|
|
751
|
+
input: { id: 1, test: 2 }
|
|
752
|
+
}), { id: 1, test: 2, testStored: 4, testVirtual: 8 })
|
|
753
|
+
|
|
754
|
+
// insert
|
|
755
|
+
same(await generatedTest.insert({
|
|
756
|
+
inputs: [{ test: 4 }]
|
|
757
|
+
}), [{ id: 2, test: 4, testStored: 8, testVirtual: 16 }])
|
|
758
|
+
|
|
759
|
+
// updateMany
|
|
760
|
+
same(await generatedTest.updateMany({
|
|
761
|
+
where: {
|
|
762
|
+
id: {
|
|
763
|
+
eq: 2
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
input: {
|
|
767
|
+
test: 8
|
|
768
|
+
}
|
|
769
|
+
}), [{ id: 2, test: 8, testStored: 16, testVirtual: 32 }])
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
test('stored generated columns should return for pg', { skip: !(isPg) }, async ({ teardown, same }) => {
|
|
773
|
+
async function onDatabaseLoad (db, sql) {
|
|
774
|
+
await clear(db, sql)
|
|
775
|
+
teardown(() => db.dispose())
|
|
776
|
+
|
|
777
|
+
await db.query(sql`CREATE TABLE generated_test (
|
|
778
|
+
id SERIAL PRIMARY KEY,
|
|
779
|
+
test INTEGER,
|
|
780
|
+
test_stored INTEGER GENERATED ALWAYS AS (test*2) STORED
|
|
781
|
+
);`)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const mapper = await connect({
|
|
785
|
+
connectionString: connInfo.connectionString,
|
|
786
|
+
log: fakeLogger,
|
|
787
|
+
onDatabaseLoad,
|
|
788
|
+
ignore: {},
|
|
789
|
+
hooks: {}
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
const generatedTest = mapper.entities.generatedTest
|
|
793
|
+
|
|
794
|
+
// save - new record
|
|
795
|
+
same(await generatedTest.save({
|
|
796
|
+
input: { test: 1 }
|
|
797
|
+
}), { id: 1, test: 1, testStored: 2 })
|
|
798
|
+
|
|
799
|
+
// save - update
|
|
800
|
+
same(await generatedTest.save({
|
|
801
|
+
input: { id: 1, test: 2 }
|
|
802
|
+
}), { id: 1, test: 2, testStored: 4 })
|
|
803
|
+
|
|
804
|
+
// insert
|
|
805
|
+
same(await generatedTest.insert({
|
|
806
|
+
inputs: [{ test: 4 }]
|
|
807
|
+
}), [{ id: 2, test: 4, testStored: 8 }])
|
|
808
|
+
|
|
809
|
+
// updateMany
|
|
810
|
+
same(await generatedTest.updateMany({
|
|
811
|
+
where: {
|
|
812
|
+
id: {
|
|
813
|
+
eq: 2
|
|
814
|
+
}
|
|
815
|
+
},
|
|
816
|
+
input: {
|
|
817
|
+
test: 8
|
|
818
|
+
}
|
|
819
|
+
}), [{ id: 2, test: 8, testStored: 16 }])
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
test('stored and virtual generated columns should return for pg', { skip: (isPg || isSQLite) }, async ({ teardown, same }) => {
|
|
823
|
+
async function onDatabaseLoad (db, sql) {
|
|
824
|
+
await clear(db, sql)
|
|
825
|
+
teardown(() => db.dispose())
|
|
826
|
+
|
|
827
|
+
await db.query(sql`CREATE TABLE generated_test (
|
|
828
|
+
id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
829
|
+
test INTEGER,
|
|
830
|
+
test_stored INTEGER GENERATED ALWAYS AS (test*2) STORED,
|
|
831
|
+
test_virtual INTEGER GENERATED ALWAYS AS (test*4) VIRTUAL
|
|
832
|
+
);`)
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const mapper = await connect({
|
|
836
|
+
connectionString: connInfo.connectionString,
|
|
837
|
+
log: fakeLogger,
|
|
838
|
+
onDatabaseLoad,
|
|
839
|
+
ignore: {},
|
|
840
|
+
hooks: {}
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
const generatedTest = mapper.entities.generatedTest
|
|
844
|
+
|
|
845
|
+
// save - new record
|
|
846
|
+
same(await generatedTest.save({
|
|
847
|
+
input: { test: 1 }
|
|
848
|
+
}), { id: 1, test: 1, testStored: 2, testVirtual: 4 })
|
|
849
|
+
|
|
850
|
+
// save - update
|
|
851
|
+
same(await generatedTest.save({
|
|
852
|
+
input: { id: 1, test: 2 }
|
|
853
|
+
}), { id: 1, test: 2, testStored: 4, testVirtual: 8 })
|
|
854
|
+
|
|
855
|
+
// insert
|
|
856
|
+
same(await generatedTest.insert({
|
|
857
|
+
inputs: [{ test: 4 }]
|
|
858
|
+
}), [{ id: 2, test: 4, testStored: 8, testVirtual: 16 }])
|
|
859
|
+
|
|
860
|
+
// updateMany
|
|
861
|
+
same(await generatedTest.updateMany({
|
|
862
|
+
where: {
|
|
863
|
+
id: {
|
|
864
|
+
eq: 2
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
input: {
|
|
868
|
+
test: 8
|
|
869
|
+
}
|
|
870
|
+
}), [{ id: 2, test: 8, testStored: 16, testVirtual: 32 }])
|
|
871
|
+
})
|
package/test/helper.js
CHANGED
package/test/mapper.test.js
CHANGED
|
@@ -193,20 +193,3 @@ test('missing connectionString', async ({ rejects }) => {
|
|
|
193
193
|
|
|
194
194
|
await rejects(app.ready(), /connectionString/)
|
|
195
195
|
})
|
|
196
|
-
|
|
197
|
-
test('throw if no primary keys', async ({ rejects, teardown }) => {
|
|
198
|
-
async function onDatabaseLoad (db, sql) {
|
|
199
|
-
await clear(db, sql)
|
|
200
|
-
|
|
201
|
-
await db.query(sql`CREATE TABLE pages (
|
|
202
|
-
title VARCHAR(255) NOT NULL
|
|
203
|
-
);`)
|
|
204
|
-
}
|
|
205
|
-
await rejects(connect({
|
|
206
|
-
connectionString: connInfo.connectionString,
|
|
207
|
-
log: fakeLogger,
|
|
208
|
-
onDatabaseLoad,
|
|
209
|
-
ignore: {},
|
|
210
|
-
hooks: {}
|
|
211
|
-
}))
|
|
212
|
-
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
|
|
5
|
+
const { clear, connInfo, isMysql8 } = require('./helper')
|
|
6
|
+
const { connect } = require('..')
|
|
7
|
+
const fakeLogger = {
|
|
8
|
+
trace: () => {},
|
|
9
|
+
warn: () => {},
|
|
10
|
+
error: () => {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test('unique key', async ({ equal, not, same, teardown }) => {
|
|
14
|
+
async function onDatabaseLoad (db, sql) {
|
|
15
|
+
await clear(db, sql)
|
|
16
|
+
teardown(() => db.dispose())
|
|
17
|
+
|
|
18
|
+
const table = sql`
|
|
19
|
+
CREATE TABLE pages (
|
|
20
|
+
xx INTEGER DEFAULT NULL UNIQUE,
|
|
21
|
+
name varchar(75) DEFAULT NULL UNIQUE
|
|
22
|
+
);
|
|
23
|
+
`
|
|
24
|
+
|
|
25
|
+
await db.query(table)
|
|
26
|
+
}
|
|
27
|
+
const mapper = await connect({
|
|
28
|
+
connectionString: connInfo.connectionString,
|
|
29
|
+
log: fakeLogger,
|
|
30
|
+
onDatabaseLoad,
|
|
31
|
+
ignore: {},
|
|
32
|
+
hooks: {}
|
|
33
|
+
})
|
|
34
|
+
const pageEntity = mapper.entities.page
|
|
35
|
+
not(pageEntity, undefined)
|
|
36
|
+
equal(pageEntity.name, 'Page')
|
|
37
|
+
equal(pageEntity.singularName, 'page')
|
|
38
|
+
equal(pageEntity.pluralName, 'pages')
|
|
39
|
+
if (isMysql8) {
|
|
40
|
+
same(pageEntity.primaryKeys, new Set(['name']))
|
|
41
|
+
equal(pageEntity.camelCasedFields.name.primaryKey, true)
|
|
42
|
+
} else {
|
|
43
|
+
same(pageEntity.primaryKeys, new Set(['xx']))
|
|
44
|
+
equal(pageEntity.camelCasedFields.xx.primaryKey, true)
|
|
45
|
+
}
|
|
46
|
+
equal(pageEntity.camelCasedFields.xx.unique, true)
|
|
47
|
+
equal(pageEntity.camelCasedFields.name.unique, true)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('no key', async ({ same, teardown, pass, equal, plan }) => {
|
|
51
|
+
plan(3)
|
|
52
|
+
async function onDatabaseLoad (db, sql) {
|
|
53
|
+
await clear(db, sql)
|
|
54
|
+
teardown(() => db.dispose())
|
|
55
|
+
|
|
56
|
+
const table = sql`
|
|
57
|
+
CREATE TABLE pages (
|
|
58
|
+
xx INTEGER DEFAULT NULL,
|
|
59
|
+
name varchar(75) DEFAULT NULL
|
|
60
|
+
);
|
|
61
|
+
`
|
|
62
|
+
|
|
63
|
+
await db.query(table)
|
|
64
|
+
}
|
|
65
|
+
const log = {
|
|
66
|
+
trace: () => {},
|
|
67
|
+
warn: (obj, str) => {
|
|
68
|
+
same(obj, { table: 'pages' })
|
|
69
|
+
equal(str, 'Cannot find any primary keys for table')
|
|
70
|
+
},
|
|
71
|
+
error: () => {}
|
|
72
|
+
}
|
|
73
|
+
const mapper = await connect({
|
|
74
|
+
connectionString: connInfo.connectionString,
|
|
75
|
+
log,
|
|
76
|
+
onDatabaseLoad
|
|
77
|
+
})
|
|
78
|
+
same(mapper.entities, {})
|
|
79
|
+
})
|
package/test/schema.test.js
CHANGED
|
@@ -174,7 +174,6 @@ test('[pg] if schema is empty array, should find entities only in default \'publ
|
|
|
174
174
|
hooks: {},
|
|
175
175
|
schema: []
|
|
176
176
|
})
|
|
177
|
-
|
|
178
177
|
equal(Object.keys(mapper.entities).length, 1)
|
|
179
178
|
const pageEntity = mapper.entities.page
|
|
180
179
|
equal(pageEntity.name, 'Page')
|
|
@@ -318,3 +317,65 @@ test('addEntityHooks in entities with schema', { skip: isSQLite }, async ({ pass
|
|
|
318
317
|
await entity.insert({ inputs: [{ title: 'hello' }, { title: 'world' }], fields: ['id', 'title'] })
|
|
319
318
|
end()
|
|
320
319
|
})
|
|
320
|
+
|
|
321
|
+
test('uses tables from different schemas with FK', { skip: isSQLite }, async ({ pass, teardown, equal }) => {
|
|
322
|
+
async function onDatabaseLoad (db, sql) {
|
|
323
|
+
await clear(db, sql)
|
|
324
|
+
teardown(() => db.dispose())
|
|
325
|
+
|
|
326
|
+
await db.query(sql`CREATE SCHEMA IF NOT EXISTS test1;`)
|
|
327
|
+
if (isMysql || isMysql8) {
|
|
328
|
+
await db.query(sql`CREATE TABLE IF NOT EXISTS \`test1\`.\`pages\` (
|
|
329
|
+
id SERIAL PRIMARY KEY,
|
|
330
|
+
title VARCHAR(255) NOT NULL
|
|
331
|
+
);`)
|
|
332
|
+
} else {
|
|
333
|
+
await db.query(sql`CREATE TABLE IF NOT EXISTS "test1"."pages" (
|
|
334
|
+
id SERIAL PRIMARY KEY,
|
|
335
|
+
title VARCHAR(255) NOT NULL
|
|
336
|
+
);`)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
await db.query(sql`CREATE SCHEMA IF NOT EXISTS test2;`)
|
|
340
|
+
|
|
341
|
+
if (isMysql || isMysql8) {
|
|
342
|
+
await db.query(sql`CREATE TABLE IF NOT EXISTS \`test2\`.\`users\` (
|
|
343
|
+
id SERIAL PRIMARY KEY,
|
|
344
|
+
username VARCHAR(255) NOT NULL,
|
|
345
|
+
page_id BIGINT UNSIGNED,
|
|
346
|
+
FOREIGN KEY(page_id) REFERENCES test1.pages(id) ON DELETE CASCADE
|
|
347
|
+
);`)
|
|
348
|
+
} else {
|
|
349
|
+
await db.query(sql`CREATE TABLE IF NOT EXISTS "test2"."users" (
|
|
350
|
+
id SERIAL PRIMARY KEY,
|
|
351
|
+
username VARCHAR(255) NOT NULL,
|
|
352
|
+
page_id integer REFERENCES test1.pages(id)
|
|
353
|
+
);`)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const mapper = await connect({
|
|
357
|
+
connectionString: connInfo.connectionString,
|
|
358
|
+
log: fakeLogger,
|
|
359
|
+
onDatabaseLoad,
|
|
360
|
+
ignore: {},
|
|
361
|
+
hooks: {},
|
|
362
|
+
schema: ['test1', 'test2']
|
|
363
|
+
})
|
|
364
|
+
const pageEntity = mapper.entities.test1Page
|
|
365
|
+
equal(pageEntity.name, 'Test1Page')
|
|
366
|
+
equal(pageEntity.singularName, 'test1Page')
|
|
367
|
+
equal(pageEntity.pluralName, 'test1Pages')
|
|
368
|
+
equal(pageEntity.schema, 'test1')
|
|
369
|
+
equal(pageEntity.relations.length, 0)
|
|
370
|
+
|
|
371
|
+
const userEntity = mapper.entities.test2User
|
|
372
|
+
equal(userEntity.name, 'Test2User')
|
|
373
|
+
equal(userEntity.singularName, 'test2User')
|
|
374
|
+
equal(userEntity.pluralName, 'test2Users')
|
|
375
|
+
equal(userEntity.schema, 'test2')
|
|
376
|
+
equal(userEntity.relations.length, 1)
|
|
377
|
+
const userRelation = userEntity.relations[0]
|
|
378
|
+
equal(userRelation.foreignEntityName, 'test1Page')
|
|
379
|
+
equal(userRelation.entityName, 'test2User')
|
|
380
|
+
pass()
|
|
381
|
+
})
|