@platformatic/sql-mapper 0.22.0 → 0.23.0
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 +11 -18
- package/mapper.js +36 -11
- package/package.json +1 -1
- package/test/entity.test.js +21 -16
- package/test/no-primary-key.test.js +2 -2
- package/test/schema.test.js +94 -1
package/lib/entity.js
CHANGED
|
@@ -14,7 +14,7 @@ function lowerCaseFirst (str) {
|
|
|
14
14
|
return str.charAt(0).toLowerCase() + str.slice(1)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema, useSchemaInName, limitConfig) {
|
|
17
|
+
function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema, useSchemaInName, limitConfig, columns, constraintsList) {
|
|
18
18
|
/* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite)
|
|
19
19
|
const entityName = useSchemaInName ? toUpperFirst(`${schema}${toSingular(table)}`) : toSingular(table)
|
|
20
20
|
/* istanbul ignore next */
|
|
@@ -339,9 +339,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
|
|
342
|
+
function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSchemaInName, ignore, limitConfig, schemaList, columns, constraintsList) {
|
|
343
343
|
// Compute the columns
|
|
344
|
-
|
|
344
|
+
columns = columns.filter((c) => !ignore[c.column_name])
|
|
345
345
|
const fields = columns.reduce((acc, column) => {
|
|
346
346
|
acc[column.column_name] = {
|
|
347
347
|
sqlType: column.udt_name,
|
|
@@ -368,33 +368,26 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
|
|
|
368
368
|
acc[column.column_name].isGenerated = column.is_generated.includes('GENERATED')
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
+
// To get enum values in pg
|
|
372
|
+
/* istanbul ignore next */
|
|
373
|
+
if (column.enum) {
|
|
374
|
+
acc[column.column_name].enum = column.enum
|
|
375
|
+
}
|
|
376
|
+
|
|
371
377
|
return acc
|
|
372
378
|
}, {})
|
|
373
379
|
|
|
374
|
-
// To get enum values in pg
|
|
375
|
-
/* istanbul ignore next */
|
|
376
|
-
if (db.isPg) {
|
|
377
|
-
const enums = await queries.listEnumValues(db, sql, table, schema)
|
|
378
|
-
for (const enumValue of enums) {
|
|
379
|
-
if (!fields[enumValue.column_name].enum) {
|
|
380
|
-
fields[enumValue.column_name].enum = [enumValue.enumlabel]
|
|
381
|
-
} else {
|
|
382
|
-
fields[enumValue.column_name].enum.push(enumValue.enumlabel)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
380
|
const currentRelations = []
|
|
387
381
|
|
|
388
|
-
const constraintsList = await queries.listConstraints(db, sql, table, schema)
|
|
389
382
|
const primaryKeys = new Set()
|
|
390
383
|
|
|
391
384
|
/* istanbul ignore next */
|
|
392
385
|
function checkSQLitePrimaryKey (constraint) {
|
|
393
386
|
if (db.isSQLite) {
|
|
394
|
-
const validTypes = ['integer', 'uuid', 'serial']
|
|
387
|
+
const validTypes = ['varchar', 'integer', 'uuid', 'serial']
|
|
395
388
|
const pkType = fields[constraint.column_name].sqlType.toLowerCase()
|
|
396
389
|
if (!validTypes.includes(pkType)) {
|
|
397
|
-
throw new Error(`Invalid Primary Key type
|
|
390
|
+
throw new Error(`Invalid Primary Key type: "${pkType}". We support the following: ${validTypes.join(', ')}.`)
|
|
398
391
|
}
|
|
399
392
|
}
|
|
400
393
|
}
|
package/mapper.js
CHANGED
|
@@ -46,7 +46,7 @@ const defaultAutoTimestampFields = {
|
|
|
46
46
|
updatedAt: 'updated_at'
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {}, schema, limit = {} }) {
|
|
49
|
+
async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {}, schema, limit = {}, dbschema }) {
|
|
50
50
|
if (typeof autoTimestamp === 'boolean' && autoTimestamp === true) {
|
|
51
51
|
autoTimestamp = defaultAutoTimestampFields
|
|
52
52
|
}
|
|
@@ -110,9 +110,33 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
|
|
|
110
110
|
await onDatabaseLoad(db, sql)
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
if (!dbschema) {
|
|
114
|
+
dbschema = await queries.listTables(db, sql, schemaList)
|
|
115
|
+
|
|
116
|
+
// TODO make this parallel or a single query
|
|
117
|
+
for (const wrap of dbschema) {
|
|
118
|
+
const { table, schema } = wrap
|
|
119
|
+
const columns = await queries.listColumns(db, sql, table, schema)
|
|
120
|
+
wrap.constraints = await queries.listConstraints(db, sql, table, schema)
|
|
121
|
+
wrap.columns = columns
|
|
122
|
+
|
|
123
|
+
// To get enum values in pg
|
|
124
|
+
/* istanbul ignore next */
|
|
125
|
+
if (db.isPg) {
|
|
126
|
+
const enums = await queries.listEnumValues(db, sql, table, schema)
|
|
127
|
+
for (const enumValue of enums) {
|
|
128
|
+
const column = columns.find(column => column.column_name === enumValue.column_name)
|
|
129
|
+
if (!column.enum) {
|
|
130
|
+
column.enum = [enumValue.enumlabel]
|
|
131
|
+
} else {
|
|
132
|
+
column.enum.push(enumValue.enumlabel)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
114
138
|
|
|
115
|
-
for (const { table, schema } of
|
|
139
|
+
for (const { table, schema, columns, constraints } of dbschema) {
|
|
116
140
|
// The following line is a safety net when developing this module,
|
|
117
141
|
// it should never happen.
|
|
118
142
|
/* istanbul ignore next */
|
|
@@ -122,7 +146,7 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
|
|
|
122
146
|
if (ignore[table] === true) {
|
|
123
147
|
continue
|
|
124
148
|
}
|
|
125
|
-
const entity =
|
|
149
|
+
const entity = buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit, schemaList, columns, constraints)
|
|
126
150
|
// Check for primary key of all entities
|
|
127
151
|
if (entity.primaryKeys.size === 0) {
|
|
128
152
|
log.warn({ table }, 'Cannot find any primary keys for table')
|
|
@@ -137,18 +161,19 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
|
|
|
137
161
|
addEntityHooks(entity.singularName, hooks[entity.singularName])
|
|
138
162
|
}
|
|
139
163
|
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
db,
|
|
167
|
+
sql,
|
|
168
|
+
entities,
|
|
169
|
+
addEntityHooks,
|
|
170
|
+
dbschema
|
|
171
|
+
}
|
|
140
172
|
} catch (err) /* istanbul ignore next */ {
|
|
141
173
|
db.dispose()
|
|
142
174
|
throw err
|
|
143
175
|
}
|
|
144
176
|
|
|
145
|
-
return {
|
|
146
|
-
db,
|
|
147
|
-
sql,
|
|
148
|
-
entities,
|
|
149
|
-
addEntityHooks
|
|
150
|
-
}
|
|
151
|
-
|
|
152
177
|
function addEntityHooks (entityName, hooks) {
|
|
153
178
|
const entity = entities[entityName]
|
|
154
179
|
if (!entity) {
|
package/package.json
CHANGED
package/test/entity.test.js
CHANGED
|
@@ -346,27 +346,32 @@ test('[SQLite] - UUID', { skip: !isSQLite }, async ({ pass, teardown, same, equa
|
|
|
346
346
|
}
|
|
347
347
|
})
|
|
348
348
|
|
|
349
|
-
test('[SQLite]
|
|
349
|
+
test('[SQLite] allows to have VARCHAR PK', { skip: !isSQLite }, async ({ same, teardown }) => {
|
|
350
350
|
async function onDatabaseLoad (db, sql) {
|
|
351
351
|
await clear(db, sql)
|
|
352
|
+
teardown(() => db.dispose())
|
|
353
|
+
|
|
352
354
|
await db.query(sql`CREATE TABLE pages (
|
|
353
|
-
id
|
|
354
|
-
title varchar(255) NOT NULL
|
|
355
|
-
content text NOT NULL
|
|
355
|
+
id varchar(255) PRIMARY KEY,
|
|
356
|
+
title varchar(255) NOT NULL
|
|
356
357
|
);`)
|
|
357
358
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
359
|
+
const mapper = await connect({
|
|
360
|
+
connectionString: connInfo.connectionString,
|
|
361
|
+
log: fakeLogger,
|
|
362
|
+
onDatabaseLoad,
|
|
363
|
+
ignore: {},
|
|
364
|
+
hooks: {}
|
|
365
|
+
})
|
|
366
|
+
const pageEntity = mapper.entities.page
|
|
367
|
+
const [newPage] = await pageEntity.insert({
|
|
368
|
+
fields: ['id', 'title'],
|
|
369
|
+
inputs: [{ id: 'varchar_id', title: '13th page with explicit id equal to 13' }]
|
|
370
|
+
})
|
|
371
|
+
same(newPage, {
|
|
372
|
+
id: 'varchar_id',
|
|
373
|
+
title: '13th page with explicit id equal to 13'
|
|
374
|
+
})
|
|
370
375
|
})
|
|
371
376
|
|
|
372
377
|
test('mixing snake and camel case', async ({ pass, teardown, same, equal }) => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { test } = require('tap')
|
|
4
4
|
|
|
5
|
-
const { clear, connInfo, isMysql8 } = require('./helper')
|
|
5
|
+
const { clear, connInfo, isMysql8, isSQLite } = require('./helper')
|
|
6
6
|
const { connect } = require('..')
|
|
7
7
|
const fakeLogger = {
|
|
8
8
|
trace: () => {},
|
|
@@ -36,7 +36,7 @@ test('unique key', async ({ equal, not, same, teardown }) => {
|
|
|
36
36
|
equal(pageEntity.name, 'Page')
|
|
37
37
|
equal(pageEntity.singularName, 'page')
|
|
38
38
|
equal(pageEntity.pluralName, 'pages')
|
|
39
|
-
if (isMysql8) {
|
|
39
|
+
if (isMysql8 || isSQLite) {
|
|
40
40
|
same(pageEntity.primaryKeys, new Set(['name']))
|
|
41
41
|
equal(pageEntity.camelCasedFields.name.primaryKey, true)
|
|
42
42
|
} else {
|
package/test/schema.test.js
CHANGED
|
@@ -61,7 +61,7 @@ test('uses tables from different schemas', { skip: isSQLite }, async ({ pass, te
|
|
|
61
61
|
pass()
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
-
test('find enums correctly using schemas', { skip: isSQLite }, async ({ pass, teardown, equal }) => {
|
|
64
|
+
test('find enums correctly using schemas', { skip: isSQLite }, async ({ pass, teardown, equal, match }) => {
|
|
65
65
|
async function onDatabaseLoad (db, sql) {
|
|
66
66
|
await clear(db, sql)
|
|
67
67
|
teardown(() => db.dispose())
|
|
@@ -102,6 +102,31 @@ test('find enums correctly using schemas', { skip: isSQLite }, async ({ pass, te
|
|
|
102
102
|
equal(pageEntity.name, 'Test1Page')
|
|
103
103
|
equal(pageEntity.singularName, 'test1Page')
|
|
104
104
|
equal(pageEntity.pluralName, 'test1Pages')
|
|
105
|
+
match(mapper.dbschema, [
|
|
106
|
+
{
|
|
107
|
+
schema: 'test1',
|
|
108
|
+
table: 'pages',
|
|
109
|
+
constraints: [
|
|
110
|
+
{
|
|
111
|
+
constraint_type: isMysql8 ? 'UNIQUE' : 'PRIMARY KEY'
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
columns: [
|
|
115
|
+
{
|
|
116
|
+
column_name: 'id',
|
|
117
|
+
is_nullable: 'NO'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
column_name: 'title',
|
|
121
|
+
is_nullable: 'NO'
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
column_name: 'type',
|
|
125
|
+
is_nullable: 'YES'
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
])
|
|
105
130
|
pass()
|
|
106
131
|
})
|
|
107
132
|
|
|
@@ -379,3 +404,71 @@ test('uses tables from different schemas with FK', { skip: isSQLite }, async ({
|
|
|
379
404
|
equal(userRelation.entityName, 'test2User')
|
|
380
405
|
pass()
|
|
381
406
|
})
|
|
407
|
+
|
|
408
|
+
test('recreate mapper from schema', async ({ pass, teardown, equal, match, fail }) => {
|
|
409
|
+
async function onDatabaseLoad (db, sql) {
|
|
410
|
+
await clear(db, sql)
|
|
411
|
+
teardown(() => db.dispose())
|
|
412
|
+
|
|
413
|
+
if (isMysql || isMysql8) {
|
|
414
|
+
await db.query(sql`
|
|
415
|
+
CREATE TABLE IF NOT EXISTS \`pages\` (
|
|
416
|
+
id SERIAL PRIMARY KEY,
|
|
417
|
+
title VARCHAR(255) NOT NULL
|
|
418
|
+
);`)
|
|
419
|
+
} else if (isPg) {
|
|
420
|
+
await db.query(sql`
|
|
421
|
+
CREATE TABLE IF NOT EXISTS "pages" (
|
|
422
|
+
id SERIAL PRIMARY KEY,
|
|
423
|
+
title VARCHAR(255) NOT NULL
|
|
424
|
+
);`)
|
|
425
|
+
} else if (isSQLite) {
|
|
426
|
+
await db.query(sql`
|
|
427
|
+
CREATE TABLE IF NOT EXISTS "pages" (
|
|
428
|
+
id INTEGER PRIMARY KEY,
|
|
429
|
+
title VARCHAR(255) NOT NULL
|
|
430
|
+
);`)
|
|
431
|
+
} else {
|
|
432
|
+
await db.query(sql`CREATE TABLE IF NOT EXISTS "pages" (
|
|
433
|
+
id SERIAL PRIMARY KEY,
|
|
434
|
+
title VARCHAR(255) NOT NULL,
|
|
435
|
+
);`)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const mapper = await connect({
|
|
439
|
+
connectionString: connInfo.connectionString,
|
|
440
|
+
log: fakeLogger,
|
|
441
|
+
onDatabaseLoad,
|
|
442
|
+
ignore: {},
|
|
443
|
+
hooks: {}
|
|
444
|
+
})
|
|
445
|
+
const dbschema = mapper.dbschema
|
|
446
|
+
const knownQueries = [
|
|
447
|
+
'SELECT VERSION()'
|
|
448
|
+
]
|
|
449
|
+
const mapper2 = await connect({
|
|
450
|
+
connectionString: connInfo.connectionString,
|
|
451
|
+
log: {
|
|
452
|
+
trace (msg) {
|
|
453
|
+
if (knownQueries.indexOf(msg.query?.text) < 0) {
|
|
454
|
+
console.log(msg)
|
|
455
|
+
fail('no trace')
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
error (...msg) {
|
|
459
|
+
console.log(...msg)
|
|
460
|
+
fail('no error')
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
dbschema,
|
|
464
|
+
ignore: {},
|
|
465
|
+
hooks: {}
|
|
466
|
+
})
|
|
467
|
+
teardown(() => mapper2.db.dispose())
|
|
468
|
+
|
|
469
|
+
const pageEntity = mapper2.entities.page
|
|
470
|
+
equal(pageEntity.name, 'Page')
|
|
471
|
+
equal(pageEntity.singularName, 'page')
|
|
472
|
+
equal(pageEntity.pluralName, 'pages')
|
|
473
|
+
pass()
|
|
474
|
+
})
|