mirror of
https://github.com/strapi/strapi.git
synced 2025-11-14 01:02:04 +00:00
Refactor column creation to use plain knex schema builder
This commit is contained in:
parent
909266232f
commit
dd072c40c3
@ -9,6 +9,94 @@ module.exports = async ({
|
||||
connection,
|
||||
model,
|
||||
}) => {
|
||||
const createIdType = (table, definition) => {
|
||||
if (definition.primaryKeyType === 'uuid' && definition.client === 'pg') {
|
||||
return table
|
||||
.specificType('id', 'uuid DEFAULT uuid_generate_v4()')
|
||||
.notNullable()
|
||||
.primary();
|
||||
}
|
||||
|
||||
if (definition.primaryKeyType !== 'integer') {
|
||||
const col = buildColType({
|
||||
name: 'id',
|
||||
table,
|
||||
definition,
|
||||
attribute: {
|
||||
type: definition.primaryKeyType,
|
||||
},
|
||||
});
|
||||
|
||||
if (!col) throw new Error('Invalid primaryKeyType');
|
||||
|
||||
return col.notNullable().primary();
|
||||
}
|
||||
|
||||
return table.increments('id');
|
||||
};
|
||||
|
||||
const buildColType = ({ name, attribute, table }) => {
|
||||
if (!attribute.type) {
|
||||
const relation = definition.associations.find(association => {
|
||||
return association.alias === name;
|
||||
});
|
||||
|
||||
if (['oneToOne', 'manyToOne', 'oneWay'].includes(relation.nature)) {
|
||||
return buildColType({
|
||||
name,
|
||||
attribute: { type: definition.primaryKeyType },
|
||||
table,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// allo custom data type for a column
|
||||
if (_.has(attribute, 'columnType')) {
|
||||
return table.specificType(name, attribute.columnType);
|
||||
}
|
||||
|
||||
switch (attribute.type) {
|
||||
case 'uuid':
|
||||
return table.uuid(name);
|
||||
case 'richtext':
|
||||
case 'text':
|
||||
return table.text(name, 'longtext');
|
||||
case 'json':
|
||||
// return client === 'pg' ? 'jsonb' : 'longtext';
|
||||
return table.jsonb(name);
|
||||
case 'enumeration':
|
||||
return table.enu(name, attribute.enum || []);
|
||||
case 'string':
|
||||
case 'password':
|
||||
case 'email':
|
||||
return table.string(name);
|
||||
case 'integer':
|
||||
return table.integer(name);
|
||||
case 'biginteger':
|
||||
return table.bigInteger(name);
|
||||
case 'float':
|
||||
return table.double(name);
|
||||
case 'decimal':
|
||||
return table.decimal(name, 10, 2);
|
||||
case 'date':
|
||||
return table.date(name);
|
||||
case 'time':
|
||||
return table.time(name);
|
||||
case 'datetime':
|
||||
return table.datetime(name);
|
||||
case 'timestamp':
|
||||
return table.timestamp(name);
|
||||
case 'currentTimestamp':
|
||||
return table.timestamp(name).defaultTo(ORM.knex.fn.now());
|
||||
case 'boolean':
|
||||
return table.boolean(name);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const { hasTimestamps } = loadedModel;
|
||||
|
||||
let [createAtCol, updatedAtCol] = ['created_at', 'updated_at'];
|
||||
@ -83,15 +171,9 @@ module.exports = async ({
|
||||
|
||||
Object.keys(columns).forEach(key => {
|
||||
const attribute = columns[key];
|
||||
const type = getType({
|
||||
definition,
|
||||
attribute,
|
||||
name: key,
|
||||
tableExists,
|
||||
});
|
||||
|
||||
if (type) {
|
||||
const col = tbl.specificType(key, type);
|
||||
const col = buildColType({ name: key, attribute, table: tbl });
|
||||
if (!col) return;
|
||||
|
||||
if (attribute.required === true) {
|
||||
if (definition.client !== 'sqlite3' || !tableExists) {
|
||||
@ -110,7 +192,6 @@ module.exports = async ({
|
||||
if (alter) {
|
||||
col.alter();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -124,7 +205,7 @@ module.exports = async ({
|
||||
|
||||
const createTable = (table, { trx = ORM.knex, ...opts } = {}) => {
|
||||
return trx.schema.createTable(table, tbl => {
|
||||
tbl.specificType('id', getIdType(definition));
|
||||
createIdType(tbl, definition);
|
||||
createColumns(tbl, attributes, { ...opts, tableExists: false });
|
||||
});
|
||||
};
|
||||
@ -201,7 +282,7 @@ module.exports = async ({
|
||||
await createTable(table, { trx });
|
||||
|
||||
const attrs = Object.keys(attributes).filter(attribute =>
|
||||
getType({
|
||||
isColumn({
|
||||
definition,
|
||||
attribute: attributes[attribute],
|
||||
name: attribute,
|
||||
@ -286,7 +367,7 @@ module.exports = async ({
|
||||
type: 'currentTimestamp',
|
||||
};
|
||||
definition.attributes[updatedAtCol] = {
|
||||
type: 'timestampUpdate',
|
||||
type: 'currentTimestamp',
|
||||
};
|
||||
}
|
||||
|
||||
@ -376,87 +457,26 @@ module.exports = async ({
|
||||
}
|
||||
};
|
||||
|
||||
const getType = ({ definition, attribute, name, tableExists = false }) => {
|
||||
const { client } = definition;
|
||||
|
||||
if (!attribute.type) {
|
||||
// Add integer value if there is a relation
|
||||
const isColumn = ({ definition, attribute, name }) => {
|
||||
if (!_.has(attribute, 'type')) {
|
||||
const relation = definition.associations.find(association => {
|
||||
return association.alias === name;
|
||||
});
|
||||
|
||||
switch (relation.nature) {
|
||||
case 'oneToOne':
|
||||
case 'manyToOne':
|
||||
case 'oneWay':
|
||||
return definition.primaryKeyType;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
if (!relation) return false;
|
||||
|
||||
if (['oneToOne', 'manyToOne', 'oneWay'].includes(relation.nature)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (attribute.type) {
|
||||
case 'uuid':
|
||||
return client === 'pg' ? 'uuid' : 'varchar(36)';
|
||||
case 'richtext':
|
||||
case 'text':
|
||||
return client === 'pg' ? 'text' : 'longtext';
|
||||
case 'json':
|
||||
return client === 'pg' ? 'jsonb' : 'longtext';
|
||||
case 'string':
|
||||
case 'enumeration':
|
||||
case 'password':
|
||||
case 'email':
|
||||
return 'varchar(255)';
|
||||
case 'integer':
|
||||
return client === 'pg' ? 'integer' : 'int';
|
||||
case 'biginteger':
|
||||
if (client === 'sqlite3') return 'bigint(53)'; // no choice until the sqlite3 package supports returning strings for big integers
|
||||
return 'bigint';
|
||||
case 'float':
|
||||
return client === 'pg' ? 'double precision' : 'double';
|
||||
case 'decimal':
|
||||
return 'decimal(10,2)';
|
||||
case 'date':
|
||||
return 'date';
|
||||
case 'time':
|
||||
return 'time';
|
||||
case 'datetime': {
|
||||
if (client === 'pg') return 'timestampz';
|
||||
return 'datetime';
|
||||
}
|
||||
case 'timestamp': {
|
||||
if (client === 'pg') {
|
||||
return 'timestampz';
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'timestamp';
|
||||
}
|
||||
case 'currentTimestamp': {
|
||||
if (client === 'pg') {
|
||||
return 'timestamp with time zone DEFAULT CURRENT_TIMESTAMP';
|
||||
} else if (client === 'sqlite3' && tableExists) {
|
||||
return 'timestamp DEFAULT NULL';
|
||||
if (['component', 'dynamiczone'].includes(attribute.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'timestamp DEFAULT CURRENT_TIMESTAMP';
|
||||
}
|
||||
case 'timestampUpdate':
|
||||
switch (client) {
|
||||
case 'pg':
|
||||
return 'timestamp with time zone DEFAULT CURRENT_TIMESTAMP';
|
||||
case 'sqlite3':
|
||||
if (tableExists) {
|
||||
return 'timestamp DEFAULT NULL';
|
||||
}
|
||||
return 'timestamp DEFAULT CURRENT_TIMESTAMP';
|
||||
default:
|
||||
return 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP';
|
||||
}
|
||||
case 'boolean':
|
||||
return 'boolean';
|
||||
default:
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const storeTable = async (table, attributes) => {
|
||||
@ -479,29 +499,4 @@ const storeTable = async (table, attributes) => {
|
||||
}).save();
|
||||
};
|
||||
|
||||
const defaultIdType = {
|
||||
mysql: 'INT AUTO_INCREMENT NOT NULL PRIMARY KEY',
|
||||
pg: 'SERIAL NOT NULL PRIMARY KEY',
|
||||
sqlite3: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
|
||||
};
|
||||
|
||||
const getIdType = definition => {
|
||||
if (definition.primaryKeyType === 'uuid' && definition.client === 'pg') {
|
||||
return 'uuid NOT NULL DEFAULT uuid_generate_v4() NOT NULL PRIMARY KEY';
|
||||
}
|
||||
|
||||
if (definition.primaryKeyType !== 'integer') {
|
||||
const type = getType({
|
||||
definition,
|
||||
attribute: {
|
||||
type: definition.primaryKeyType,
|
||||
},
|
||||
});
|
||||
|
||||
return `${type} NOT NULL PRIMARY KEY`;
|
||||
}
|
||||
|
||||
return defaultIdType[definition.client];
|
||||
};
|
||||
|
||||
const uniqueColName = (table, key) => `${table}_${key}_unique`;
|
||||
|
||||
@ -19,7 +19,10 @@ const createFormatter = client => ({ type }, value) => {
|
||||
};
|
||||
|
||||
const defaultFormatter = {
|
||||
json: value => JSON.parse(value),
|
||||
json: value => {
|
||||
if (typeof value === 'object') return value;
|
||||
return JSON.parse(value);
|
||||
},
|
||||
boolean: value => {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
@ -34,10 +37,6 @@ const defaultFormatter = {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const formatters = {
|
||||
sqlite3: {
|
||||
date: value => {
|
||||
const cast = new Date(value);
|
||||
return isValid(cast) ? formatISO(cast, { representation: 'date' }) : null;
|
||||
@ -50,10 +49,30 @@ const formatters = {
|
||||
const cast = new Date(value);
|
||||
return isValid(cast) ? format(cast, 'T') : null;
|
||||
},
|
||||
};
|
||||
|
||||
const formatters = {
|
||||
sqlite3: {
|
||||
biginteger: value => {
|
||||
return value.toString();
|
||||
},
|
||||
},
|
||||
mysql: {
|
||||
boolean: value => {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
|
||||
const strVal = value.toString();
|
||||
if (strVal === '1') {
|
||||
return true;
|
||||
} else if (strVal === '0') {
|
||||
return false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@ -464,7 +464,8 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
const mapper = (params = {}) => {
|
||||
Object.keys(params).map(key => {
|
||||
const attr = definition.attributes[key] || {};
|
||||
params[key] = parseValue(attr.type, params[key], definition);
|
||||
|
||||
params[key] = parseValue(attr.type, params[key]);
|
||||
});
|
||||
|
||||
return _.mapKeys(params, (value, key) => {
|
||||
@ -613,16 +614,30 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
: Promise.resolve();
|
||||
});
|
||||
|
||||
this.on('saving', instance => {
|
||||
instance.attributes = mapper(instance.attributes);
|
||||
this.on('saving', (instance, attrs) => {
|
||||
instance.attributes = mapper(attrs);
|
||||
|
||||
return _.isFunction(target[model.toLowerCase()]['beforeSave'])
|
||||
? target[model.toLowerCase()]['beforeSave']
|
||||
: Promise.resolve();
|
||||
});
|
||||
|
||||
// Convert to JSON format stringify json for mysql database
|
||||
if (definition.client === 'mysql' || definition.client === 'sqlite3') {
|
||||
const formatValue = createFormatter(definition.client);
|
||||
function formatEntry(entry) {
|
||||
Object.keys(entry.attributes).forEach(key => {
|
||||
const attr = definition.attributes[key] || {};
|
||||
entry.attributes[key] = formatValue(attr, entry.attributes[key]);
|
||||
});
|
||||
}
|
||||
|
||||
function formatOutput(instance) {
|
||||
if (Array.isArray(instance.models)) {
|
||||
instance.models.map(formatEntry);
|
||||
} else {
|
||||
formatEntry(instance);
|
||||
}
|
||||
}
|
||||
|
||||
const events = [
|
||||
{
|
||||
name: 'saved',
|
||||
@ -638,35 +653,15 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
},
|
||||
];
|
||||
|
||||
const formatValue = createFormatter(definition.client);
|
||||
const formatter = attributes => {
|
||||
Object.keys(attributes).forEach(key => {
|
||||
const attr = definition.attributes[key] || {};
|
||||
attributes[key] = formatValue(attr, attributes[key]);
|
||||
});
|
||||
};
|
||||
|
||||
events.forEach(event => {
|
||||
let fn;
|
||||
|
||||
if (event.name.indexOf('collection') !== -1) {
|
||||
fn = instance =>
|
||||
instance.models.map(entry => {
|
||||
formatter(entry.attributes);
|
||||
});
|
||||
} else {
|
||||
fn = instance => formatter(instance.attributes);
|
||||
}
|
||||
|
||||
this.on(event.name, instance => {
|
||||
fn(instance);
|
||||
formatOutput(instance);
|
||||
|
||||
return _.isFunction(target[model.toLowerCase()][event.target])
|
||||
? target[model.toLowerCase()][event.target]
|
||||
: Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
loadedModel.hidden = _.keys(
|
||||
|
||||
@ -16,7 +16,7 @@ const createMockSchema = (attrs, timestamps = true) => {
|
||||
type: 'timestamp',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'timestampUpdate',
|
||||
type: 'timestamp',
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user