@platformatic/sql-mapper
Advanced tools
Comparing version 0.22.0 to 0.23.0
@@ -17,3 +17,3 @@ 'use strict' | ||
function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema, useSchemaInName, limitConfig) { | ||
function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema, useSchemaInName, limitConfig, columns, constraintsList) { | ||
/* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite) | ||
@@ -343,5 +343,5 @@ const entityName = useSchemaInName ? toUpperFirst(`${schema}${toSingular(table)}`) : toSingular(table) | ||
async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSchemaInName, ignore, limitConfig, schemaList) { | ||
function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSchemaInName, ignore, limitConfig, schemaList, columns, constraintsList) { | ||
// Compute the columns | ||
const columns = (await queries.listColumns(db, sql, table, schema)).filter((c) => !ignore[c.column_name]) | ||
columns = columns.filter((c) => !ignore[c.column_name]) | ||
const fields = columns.reduce((acc, column) => { | ||
@@ -373,20 +373,13 @@ acc[column.column_name] = { | ||
// To get enum values in pg | ||
/* istanbul ignore next */ | ||
if (column.enum) { | ||
acc[column.column_name].enum = column.enum | ||
} | ||
return acc | ||
}, {}) | ||
// To get enum values in pg | ||
/* istanbul ignore next */ | ||
if (db.isPg) { | ||
const enums = await queries.listEnumValues(db, sql, table, schema) | ||
for (const enumValue of enums) { | ||
if (!fields[enumValue.column_name].enum) { | ||
fields[enumValue.column_name].enum = [enumValue.enumlabel] | ||
} else { | ||
fields[enumValue.column_name].enum.push(enumValue.enumlabel) | ||
} | ||
} | ||
} | ||
const currentRelations = [] | ||
const constraintsList = await queries.listConstraints(db, sql, table, schema) | ||
const primaryKeys = new Set() | ||
@@ -397,6 +390,6 @@ | ||
if (db.isSQLite) { | ||
const validTypes = ['integer', 'uuid', 'serial'] | ||
const validTypes = ['varchar', 'integer', 'uuid', 'serial'] | ||
const pkType = fields[constraint.column_name].sqlType.toLowerCase() | ||
if (!validTypes.includes(pkType)) { | ||
throw new Error(`Invalid Primary Key type. Expected "integer", found "${pkType}"`) | ||
throw new Error(`Invalid Primary Key type: "${pkType}". We support the following: ${validTypes.join(', ')}.`) | ||
} | ||
@@ -403,0 +396,0 @@ } |
@@ -49,3 +49,3 @@ 'use strict' | ||
async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {}, schema, limit = {} }) { | ||
async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {}, schema, limit = {}, dbschema }) { | ||
if (typeof autoTimestamp === 'boolean' && autoTimestamp === true) { | ||
@@ -114,5 +114,29 @@ autoTimestamp = defaultAutoTimestampFields | ||
const tablesWithSchema = await queries.listTables(db, sql, schemaList) | ||
if (!dbschema) { | ||
dbschema = await queries.listTables(db, sql, schemaList) | ||
for (const { table, schema } of tablesWithSchema) { | ||
// TODO make this parallel or a single query | ||
for (const wrap of dbschema) { | ||
const { table, schema } = wrap | ||
const columns = await queries.listColumns(db, sql, table, schema) | ||
wrap.constraints = await queries.listConstraints(db, sql, table, schema) | ||
wrap.columns = columns | ||
// To get enum values in pg | ||
/* istanbul ignore next */ | ||
if (db.isPg) { | ||
const enums = await queries.listEnumValues(db, sql, table, schema) | ||
for (const enumValue of enums) { | ||
const column = columns.find(column => column.column_name === enumValue.column_name) | ||
if (!column.enum) { | ||
column.enum = [enumValue.enumlabel] | ||
} else { | ||
column.enum.push(enumValue.enumlabel) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
for (const { table, schema, columns, constraints } of dbschema) { | ||
// The following line is a safety net when developing this module, | ||
@@ -127,3 +151,3 @@ // it should never happen. | ||
} | ||
const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit, schemaList) | ||
const entity = buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit, schemaList, columns, constraints) | ||
// Check for primary key of all entities | ||
@@ -143,2 +167,10 @@ if (entity.primaryKeys.size === 0) { | ||
} | ||
return { | ||
db, | ||
sql, | ||
entities, | ||
addEntityHooks, | ||
dbschema | ||
} | ||
} catch (err) /* istanbul ignore next */ { | ||
@@ -149,9 +181,2 @@ db.dispose() | ||
return { | ||
db, | ||
sql, | ||
entities, | ||
addEntityHooks | ||
} | ||
function addEntityHooks (entityName, hooks) { | ||
@@ -158,0 +183,0 @@ const entity = entities[entityName] |
{ | ||
"name": "@platformatic/sql-mapper", | ||
"version": "0.22.0", | ||
"version": "0.23.0", | ||
"description": "A data mapper utility for SQL databases", | ||
@@ -5,0 +5,0 @@ "main": "mapper.js", |
@@ -349,23 +349,28 @@ 'use strict' | ||
test('[SQLite] throws if PK is not INTEGER', { skip: !isSQLite }, async ({ fail, equal, teardown, rejects }) => { | ||
test('[SQLite] allows to have VARCHAR PK', { skip: !isSQLite }, async ({ same, teardown }) => { | ||
async function onDatabaseLoad (db, sql) { | ||
await clear(db, sql) | ||
teardown(() => db.dispose()) | ||
await db.query(sql`CREATE TABLE pages ( | ||
id int PRIMARY KEY, | ||
title varchar(255) NOT NULL, | ||
content text NOT NULL | ||
id varchar(255) PRIMARY KEY, | ||
title varchar(255) NOT NULL | ||
);`) | ||
} | ||
try { | ||
await connect({ | ||
connectionString: connInfo.connectionString, | ||
log: fakeLogger, | ||
onDatabaseLoad, | ||
ignore: {}, | ||
hooks: {} | ||
}) | ||
fail() | ||
} catch (err) { | ||
equal(err.message, 'Invalid Primary Key type. Expected "integer", found "int"') | ||
} | ||
const mapper = await connect({ | ||
connectionString: connInfo.connectionString, | ||
log: fakeLogger, | ||
onDatabaseLoad, | ||
ignore: {}, | ||
hooks: {} | ||
}) | ||
const pageEntity = mapper.entities.page | ||
const [newPage] = await pageEntity.insert({ | ||
fields: ['id', 'title'], | ||
inputs: [{ id: 'varchar_id', title: '13th page with explicit id equal to 13' }] | ||
}) | ||
same(newPage, { | ||
id: 'varchar_id', | ||
title: '13th page with explicit id equal to 13' | ||
}) | ||
}) | ||
@@ -372,0 +377,0 @@ |
@@ -5,3 +5,3 @@ 'use strict' | ||
const { clear, connInfo, isMysql8 } = require('./helper') | ||
const { clear, connInfo, isMysql8, isSQLite } = require('./helper') | ||
const { connect } = require('..') | ||
@@ -40,3 +40,3 @@ const fakeLogger = { | ||
equal(pageEntity.pluralName, 'pages') | ||
if (isMysql8) { | ||
if (isMysql8 || isSQLite) { | ||
same(pageEntity.primaryKeys, new Set(['name'])) | ||
@@ -43,0 +43,0 @@ equal(pageEntity.camelCasedFields.name.primaryKey, true) |
@@ -64,3 +64,3 @@ const { test } = require('tap') | ||
test('find enums correctly using schemas', { skip: isSQLite }, async ({ pass, teardown, equal }) => { | ||
test('find enums correctly using schemas', { skip: isSQLite }, async ({ pass, teardown, equal, match }) => { | ||
async function onDatabaseLoad (db, sql) { | ||
@@ -106,2 +106,27 @@ await clear(db, sql) | ||
equal(pageEntity.pluralName, 'test1Pages') | ||
match(mapper.dbschema, [ | ||
{ | ||
schema: 'test1', | ||
table: 'pages', | ||
constraints: [ | ||
{ | ||
constraint_type: isMysql8 ? 'UNIQUE' : 'PRIMARY KEY' | ||
} | ||
], | ||
columns: [ | ||
{ | ||
column_name: 'id', | ||
is_nullable: 'NO' | ||
}, | ||
{ | ||
column_name: 'title', | ||
is_nullable: 'NO' | ||
}, | ||
{ | ||
column_name: 'type', | ||
is_nullable: 'YES' | ||
} | ||
] | ||
} | ||
]) | ||
pass() | ||
@@ -384,1 +409,69 @@ }) | ||
}) | ||
test('recreate mapper from schema', async ({ pass, teardown, equal, match, fail }) => { | ||
async function onDatabaseLoad (db, sql) { | ||
await clear(db, sql) | ||
teardown(() => db.dispose()) | ||
if (isMysql || isMysql8) { | ||
await db.query(sql` | ||
CREATE TABLE IF NOT EXISTS \`pages\` ( | ||
id SERIAL PRIMARY KEY, | ||
title VARCHAR(255) NOT NULL | ||
);`) | ||
} else if (isPg) { | ||
await db.query(sql` | ||
CREATE TABLE IF NOT EXISTS "pages" ( | ||
id SERIAL PRIMARY KEY, | ||
title VARCHAR(255) NOT NULL | ||
);`) | ||
} else if (isSQLite) { | ||
await db.query(sql` | ||
CREATE TABLE IF NOT EXISTS "pages" ( | ||
id INTEGER PRIMARY KEY, | ||
title VARCHAR(255) NOT NULL | ||
);`) | ||
} else { | ||
await db.query(sql`CREATE TABLE IF NOT EXISTS "pages" ( | ||
id SERIAL PRIMARY KEY, | ||
title VARCHAR(255) NOT NULL, | ||
);`) | ||
} | ||
} | ||
const mapper = await connect({ | ||
connectionString: connInfo.connectionString, | ||
log: fakeLogger, | ||
onDatabaseLoad, | ||
ignore: {}, | ||
hooks: {} | ||
}) | ||
const dbschema = mapper.dbschema | ||
const knownQueries = [ | ||
'SELECT VERSION()' | ||
] | ||
const mapper2 = await connect({ | ||
connectionString: connInfo.connectionString, | ||
log: { | ||
trace (msg) { | ||
if (knownQueries.indexOf(msg.query?.text) < 0) { | ||
console.log(msg) | ||
fail('no trace') | ||
} | ||
}, | ||
error (...msg) { | ||
console.log(...msg) | ||
fail('no error') | ||
} | ||
}, | ||
dbschema, | ||
ignore: {}, | ||
hooks: {} | ||
}) | ||
teardown(() => mapper2.db.dispose()) | ||
const pageEntity = mapper2.entities.page | ||
equal(pageEntity.name, 'Page') | ||
equal(pageEntity.singularName, 'page') | ||
equal(pageEntity.pluralName, 'pages') | ||
pass() | ||
}) |
162742
5157