'TypeORM column type dependant on database
I have simple entity
@Entity()
export class File {
@PrimaryGeneratedColumn()
id: number;
@Column({type: "mediumblob"})
data: Buffer;
}
Which I want to use on production with Mysql ("mediumblob" because I want to store 10MB files).
I also want to run integration tests but with sqlite
but it supports only "blob".
Then I want test like that:
describe("CR on File and blobs", () => {
it("should store arbitrary binary file", async (done) => {
const conn = await createConnection({
type: "sqlite",
database: ":memory:",
entities: ["src/models/file.ts"],
synchronize: true
});
const fileRepo = await conn.getRepository(File);
fileRepo.createQueryBuilder("file")
.where("file.id == :id", {id: 1})
.select([
"file.data"
])
.stream();
done();
});
});
When I run such code I get error like this
DataTypeNotSupportedError: Data type "mediumblob" in "File.data" is not supported by "sqlite" database.
If I change column type to blob
then for mysql
I get following error when uploading 116kb
file
QueryFailedError: ER_DATA_TOO_LONG: Data too long for column 'data' at row 1
Is it somehow possible to generate some kind of logic/mapping to work arround incompatibility of mysql
/sqlite
so "blob"
is used for sqlite
while "mediumblob"
is used for mysql
?
Solution 1:[1]
We can create a simple decorator, @DbAwareColumn, on top of @Column. The new decorator corrects a column type based on environment. I expect you're using sqlite for the test environment
import { Column, ColumnOptions, ColumnType } from 'typeorm';
const mysqlSqliteTypeMapping: { [key: string]: ColumnType } = {
'mediumtext': 'text',
'timestamp': 'datetime',
'mediumblob': 'blob'
};
export function resolveDbType(mySqlType: ColumnType): ColumnType {
const isTestEnv = process.env.NODE_ENV === 'test';
if (isTestEnv && mySqlType in mysqlSqliteTypeMapping) {
return mysqlSqliteTypeMapping[mySqlType.toString()];
}
return mySqlType;
}
export function DbAwareColumn(columnOptions: ColumnOptions) {
if (columnOptions.type) {
columnOptions.type = resolveDbType(columnOptions.type);
}
return Column(columnOptions);
}
In entities, we can use it as
@Entity({name: 'document'})
export class Document {
@DbAwareColumn({ name: 'body', type: 'mediumtext'})
body: string;
@DbAwareColumn({type: "mediumblob"})
data: Buffer;
@DbAwareColumn({type: "timestamp"})
createdAt: Date;
}
Solution 2:[2]
I run into a similar problem. I'm using Postgres and wanted to use SQLite for unit tests.
@Hung's answer was basically what I was looking for.
However, I am using JSON types alongside default values so I had to add some improvements there.
I'm posting it here to share with you and I hope it will be useful to somebody else.
const Env = {isTest: process.env.NODE_ENV === 'test'};
function resolveType(type: ColumnType): ColumnType {
if (!Env.isTest) return type;
if (type === 'timestamp') return 'datetime';
if (type === 'mediumblob') return 'blob';
if (type === 'mediumtext') return 'text';
if (type === 'jsonb') return 'text';
if (type === 'json') return 'text';
if (type === 'enum') return 'text';
if (type === 'uuid') return 'text';
return type;
}
function resolveDefault(defaultValue: unknown): any {
if (!Env.isTest) return defaultValue;
const whitelist = ['string', 'number'];
const type = typeof defaultValue;
if (!whitelist.includes(type)) return JSON.stringify(defaultValue);
return defaultValue;
}
export function EnvSpecificDecoratorValue(options: ColumnOptions) {
if (options.type) options.type = resolveType(options.type);
if (options.default) options.default = resolveDefault(options.default);
return options;
}
import { ColumnOptions, Column as OriginalColumn } from 'typeorm';
import { UpdateDateColumn as OriginalUpdateDateColumn } from 'typeorm';
import { CreateDateColumn as OriginalCreateDateColumn } from 'typeorm';
export function Column(columnOptions: ColumnOptions) {
return OriginalColumn(EnvSpecificDecoratorValue(columnOptions));
}
export function CreateDateColumn(columnOptions: ColumnOptions) {
return OriginalCreateDateColumn(EnvSpecificDecoratorValue(columnOptions));
}
export function UpdateDateColumn(columnOptions: ColumnOptions) {
return OriginalUpdateDateColumn(EnvSpecificDecoratorValue(columnOptions));
}
Now you can reference Column
, CreateDateColumn
, UpdateDateColumn
as it was the original decorator but imported from your local file.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | n1md7 |