Skip to content

Commit

Permalink
fix(postgres): fix inserting values with ? into FullTextType prop…
Browse files Browse the repository at this point in the history
…erties

Closes #3457
  • Loading branch information
B4nan committed Sep 5, 2022
1 parent b3e43d0 commit 5095ddb
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 6 deletions.
1 change: 1 addition & 0 deletions packages/core/src/unit-of-work/ChangeSetPersister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export class ChangeSetPersister {
options = this.propagateSchemaFromMetadata(meta, options, {
convertCustomTypes: false,
});
// const res = await this.driver.nativeInsertMany(meta.className, [changeSet.payload], options);
const res = await this.driver.nativeInsert(changeSet.name, changeSet.payload, options);

if (!wrapped.hasPrimaryKey()) {
Expand Down
7 changes: 4 additions & 3 deletions packages/knex/src/AbstractSqlDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,16 +312,17 @@ export abstract class AbstractSqlDriver<C extends AbstractSqlConnection = Abstra
async nativeInsertMany<T extends object>(entityName: string, data: EntityDictionary<T>[], options: NativeInsertUpdateManyOptions<T> = {}): Promise<QueryResult<T>> {
options.processCollections ??= true;
options.convertCustomTypes ??= true;
const meta = this.metadata.get<T>(entityName);
const meta = this.metadata.find<T>(entityName);
const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
const pks = this.getPrimaryKeyFields(entityName);
const set = new Set<string>();
data.forEach(row => Object.keys(row).forEach(k => set.add(k)));
const props = [...set].map(name => meta.properties[name] ?? { name, fieldNames: [name] }) as EntityProperty<T>[];
const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] }) as EntityProperty<T>[];
const fields = Utils.flatten(props.map(prop => prop.fieldNames));
const params: unknown[] = [];

let sql = `insert into ${this.getTableName(meta, options)} `;
const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
let sql = `insert into ${tableName} `;
sql += fields.length > 0 ? '(' + fields.map(k => this.platform.quoteIdentifier(k)).join(', ') + ')' : `(${this.platform.quoteIdentifier(pks[0])})`;

if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
Expand Down
2 changes: 1 addition & 1 deletion packages/knex/src/query/QueryBuilderHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ export class QueryBuilderHelper {

if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(data[k])) {
const quoted = this.platform.quoteValue(data[k]);
data[k] = this.knex.raw(prop.customType.convertToDatabaseValueSQL!(quoted, this.platform));
data[k] = this.knex.raw(prop.customType.convertToDatabaseValueSQL!(quoted, this.platform).replace(/\?/, '\\?'));
}

if (!prop.customType && (Array.isArray(data[k]) || Utils.isPlainObject(data[k]))) {
Expand Down
110 changes: 110 additions & 0 deletions tests/features/fulltext/GH3457.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
ChangeSetType,
Entity,
EventSubscriber, FlushEventArgs,
Index,
MikroORM,
PrimaryKey,
Property,
Subscriber, wrap,
} from '@mikro-orm/core';
import type { PostgreSqlDriver } from '@mikro-orm/postgresql';
import { FullTextType } from '@mikro-orm/postgresql';
import { randomUUID } from 'crypto';

@Entity()
export class Test {

@PrimaryKey({ autoincrement: true })
id!: number;

@Property({ nullable: true })
clientFirstName?: string;

@Property({ nullable: true })
clientMiddleName?: string;

@Property({ nullable: true })
clientLastName?: string;

@Index({ type: 'fulltext' })
@Property({
type: FullTextType,
nullable: true,
onUpdate: (e: Test) =>
`${e.clientFirstName || ''} ${e.clientMiddleName || ''} ${e.clientLastName || ''}`
.replace(/\s+/g, ' ')
.trim(),
})
clientNameFull?: string;

}

@Entity()
export class TestHistory {

@PrimaryKey()
id!: string;

@Property({ nullable: true })
clientFirstName?: string;

@Property({ nullable: true })
clientMiddleName?: string;

@Property({ nullable: true })
clientLastName?: string;

@Index({ type: 'fulltext' })
@Property({ type: FullTextType, nullable: true })
clientNameFull?: string;

}

@Subscriber()
export class CaseHistorySubscriber implements EventSubscriber<Test> {

async onFlush(args: FlushEventArgs): Promise<void> {
const changeSets = args.uow.getChangeSets();

for (const cs of changeSets) {
if ((cs.type === ChangeSetType.UPDATE || cs.type === ChangeSetType.CREATE) && cs.entity instanceof Test) {
const record = args.em.create(TestHistory, {
...cs.entity,
id: randomUUID(),
});
args.uow.computeChangeSet(record);
}
}
}

}

let orm: MikroORM<PostgreSqlDriver>;

beforeAll(async () => {
orm = await MikroORM.init({
entities: [Test, TestHistory],
dbName: `mikro_orm_test_3457`,
type: 'postgresql',
});
await orm.schema.refreshDatabase();
});

afterAll(() => orm.close(true));

test('load entities', async () => {
const test = new Test();
await orm.em.fork().persistAndFlush(test);

const testGet = await orm.em.findOneOrFail(Test, 1);

wrap(testGet).assign({
clientFirstName: 'Janet?',
clientLastName: 'Smith',
});

await orm.em.flush();

const testGet2 = await orm.em.fork().findOneOrFail(Test, 1);
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,23 @@ describe('full text search tsvector in postgres', () => {
await generator.createSchema();
});

beforeEach(() => orm.schema.clearDatabase());
afterAll(() => orm.close(true));

test('load entities', async () => {
const repo = orm.em.getRepository(Book);

const book1 = new Book('My Life on The Wall, part 1');
const book1 = new Book('My Life on The ? Wall, part 1');
await repo.persist(book1).flush();

const fullTextBooks = (await repo.find({ searchableTitle: { $fulltext: 'life wall' } }))!;
expect(fullTextBooks.length).toBe(1);
});

test('load entities (multi)', async () => {
const repo = orm.em.getRepository(Book);

const book1 = new Book('My Life on The ? Wall, part 1');
const book2 = new Book('My Life on The Wall, part 2');
const book3 = new Book('My Life on The Wall, part 3');
const book4 = new Book('My Death on The Wall');
Expand All @@ -54,7 +65,7 @@ describe('full text search tsvector in postgres', () => {
await repo.flush();

const fullTextBooks = (await repo.find({ searchableTitle: { $fulltext: 'life wall' } }))!;
expect(fullTextBooks.length).toBe(3);
expect(fullTextBooks).toHaveLength(3);
});

});

0 comments on commit 5095ddb

Please sign in to comment.