Skip to content

Commit

Permalink
feat: add fulltext parser option (#5380)
Browse files Browse the repository at this point in the history
* feat: add fulltext parser option

* test: add missing hook

* fix: revert aurora query runner

* fix: synchronize parser

* revert: "fix: synchronize parser"

This reverts commit d37909f.
  • Loading branch information
bizen241 authored and pleerock committed Jan 22, 2020
1 parent 072c7d7 commit dd73395
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/decorator/Index.ts
Expand Up @@ -77,6 +77,7 @@ export function Index(nameOrFieldsOrOptions?: string|string[]|((object: any) =>
unique: options && options.unique ? true : false,
spatial: options && options.spatial ? true : false,
fulltext: options && options.fulltext ? true : false,
parser: options ? options.parser : undefined,
sparse: options && options.sparse ? true : false,
background: options && options.background ? true : false,
expireAfterSeconds: options ? options.expireAfterSeconds : undefined
Expand Down
6 changes: 6 additions & 0 deletions src/decorator/options/IndexOptions.ts
Expand Up @@ -20,6 +20,12 @@ export interface IndexOptions {
*/
fulltext?: boolean;

/**
* Fulltext parser.
* Works only in MySQL.
*/
parser?: string;

/**
* Index filter condition.
*/
Expand Down
1 change: 0 additions & 1 deletion src/driver/aurora-data-api/AuroraDataApiQueryRunner.ts
Expand Up @@ -1416,7 +1416,6 @@ export class AuroraDataApiQueryRunner extends BaseQueryRunner implements QueryRu
indexType += "SPATIAL ";
if (index.isFulltext)
indexType += "FULLTEXT ";

return `${indexType}INDEX \`${index.name}\` (${columnNames})`;
}).join(", ");

Expand Down
19 changes: 13 additions & 6 deletions src/driver/mysql/MysqlQueryRunner.ts
Expand Up @@ -397,8 +397,10 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
indexType += "SPATIAL ";
if (index.isFulltext)
indexType += "FULLTEXT ";
upQueries.push(new Query(`ALTER TABLE ${this.escapePath(newTable)} DROP INDEX \`${index.name}\`, ADD ${indexType}INDEX \`${newIndexName}\` (${columnNames})`));
downQueries.push(new Query(`ALTER TABLE ${this.escapePath(newTable)} DROP INDEX \`${newIndexName}\`, ADD ${indexType}INDEX \`${index.name}\` (${columnNames})`));
const indexParser = index.isFulltext && index.parser ? ` WITH PARSER ${index.parser}` : "";

upQueries.push(new Query(`ALTER TABLE ${this.escapePath(newTable)} DROP INDEX \`${index.name}\`, ADD ${indexType}INDEX \`${newIndexName}\` (${columnNames})${indexParser}`));
downQueries.push(new Query(`ALTER TABLE ${this.escapePath(newTable)} DROP INDEX \`${newIndexName}\`, ADD ${indexType}INDEX \`${index.name}\` (${columnNames})${indexParser}`));

// replace constraint name
index.name = newIndexName;
Expand Down Expand Up @@ -586,8 +588,10 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
indexType += "SPATIAL ";
if (index.isFulltext)
indexType += "FULLTEXT ";
upQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} DROP INDEX \`${index.name}\`, ADD ${indexType}INDEX \`${newIndexName}\` (${columnNames})`));
downQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} DROP INDEX \`${newIndexName}\`, ADD ${indexType}INDEX \`${index.name}\` (${columnNames})`));
const indexParser = index.isFulltext && index.parser ? ` WITH PARSER ${index.parser}` : "";

upQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} DROP INDEX \`${index.name}\`, ADD ${indexType}INDEX \`${newIndexName}\` (${columnNames})${indexParser}`));
downQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} DROP INDEX \`${newIndexName}\`, ADD ${indexType}INDEX \`${index.name}\` (${columnNames})${indexParser}`));

// replace constraint name
index.name = newIndexName;
Expand Down Expand Up @@ -1474,8 +1478,9 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
indexType += "SPATIAL ";
if (index.isFulltext)
indexType += "FULLTEXT ";
const indexParser = index.isFulltext && index.parser ? ` WITH PARSER ${index.parser}` : "";

return `${indexType}INDEX \`${index.name}\` (${columnNames})`;
return `${indexType}INDEX \`${index.name}\` (${columnNames})${indexParser}`;
}).join(", ");

sql += `, ${indicesSql}`;
Expand Down Expand Up @@ -1573,7 +1578,9 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
indexType += "SPATIAL ";
if (index.isFulltext)
indexType += "FULLTEXT ";
return new Query(`CREATE ${indexType}INDEX \`${index.name}\` ON ${this.escapePath(table)} (${columns})`);
const indexParser = index.isFulltext && index.parser ? ` WITH PARSER ${index.parser}` : "";

return new Query(`CREATE ${indexType}INDEX \`${index.name}\` ON ${this.escapePath(table)} (${columns})${indexParser}`);
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/entity-schema/EntitySchemaIndexOptions.ts
Expand Up @@ -38,6 +38,12 @@ export interface EntitySchemaIndexOptions {
* Works only in MySQL.
*/
fulltext?: boolean;

/**
* Fulltext parser.
* Works only in MySQL.
*/
parser?: string;

/**
* Index filter condition.
Expand Down
1 change: 1 addition & 0 deletions src/entity-schema/EntitySchemaTransformer.ts
Expand Up @@ -192,6 +192,7 @@ export class EntitySchemaTransformer {
unique: index.unique === true ? true : false,
spatial: index.spatial === true ? true : false,
fulltext: index.fulltext === true ? true : false,
parser: index.parser,
synchronize: index.synchronize === false ? false : true,
where: index.where,
sparse: index.sparse,
Expand Down
6 changes: 6 additions & 0 deletions src/metadata-args/IndexMetadataArgs.ts
Expand Up @@ -34,6 +34,12 @@ export interface IndexMetadataArgs {
* Works only in MySQL.
*/
fulltext?: boolean;

/**
* Fulltext parser.
* Works only in MySQL.
*/
parser?: string;

/**
* Index filter condition.
Expand Down
7 changes: 7 additions & 0 deletions src/metadata/IndexMetadata.ts
Expand Up @@ -40,6 +40,12 @@ export class IndexMetadata {
*/
isFulltext: boolean = false;

/**
* Fulltext parser.
* Works only in MySQL.
*/
parser?: string;

/**
* Indicates if this index must synchronize with database index.
*/
Expand Down Expand Up @@ -124,6 +130,7 @@ export class IndexMetadata {
this.isUnique = !!options.args.unique;
this.isSpatial = !!options.args.spatial;
this.isFulltext = !!options.args.fulltext;
this.parser = options.args.parser;
this.where = options.args.where;
this.isSparse = options.args.sparse;
this.isBackground = options.args.background;
Expand Down
6 changes: 6 additions & 0 deletions src/schema-builder/options/TableIndexOptions.ts
Expand Up @@ -33,6 +33,12 @@ export interface TableIndexOptions {
* Works only in MySQL.
*/
isFulltext?: boolean;

/**
* Fulltext parser.
* Works only in MySQL.
*/
parser?: string;

/**
* Index filter condition.
Expand Down
9 changes: 9 additions & 0 deletions src/schema-builder/table/TableIndex.ts
Expand Up @@ -37,6 +37,12 @@ export class TableIndex {
*/
isFulltext: boolean;

/**
* Fulltext parser.
* Works only in MySQL.
*/
parser?: string;

/**
* Index filter condition.
*/
Expand All @@ -52,6 +58,7 @@ export class TableIndex {
this.isUnique = !!options.isUnique;
this.isSpatial = !!options.isSpatial;
this.isFulltext = !!options.isFulltext;
this.parser = options.parser;
this.where = options.where ? options.where : "";
}

Expand All @@ -69,6 +76,7 @@ export class TableIndex {
isUnique: this.isUnique,
isSpatial: this.isSpatial,
isFulltext: this.isFulltext,
parser: this.parser,
where: this.where
});
}
Expand All @@ -87,6 +95,7 @@ export class TableIndex {
isUnique: indexMetadata.isUnique,
isSpatial: indexMetadata.isSpatial,
isFulltext: indexMetadata.isFulltext,
parser: indexMetadata.parser,
where: indexMetadata.where
});
}
Expand Down
18 changes: 18 additions & 0 deletions test/functional/indices/fulltext-index-test/entity/Post.ts
@@ -0,0 +1,18 @@
import {Column, Entity, PrimaryGeneratedColumn} from "../../../../../src";
import {Index} from "../../../../../src/decorator/Index";

@Entity()
export class Post {

@PrimaryGeneratedColumn()
id: number;

@Index({ fulltext: true })
@Column()
default: string;

@Index({ fulltext: true, parser: "ngram" })
@Column()
ngram: string;

}
80 changes: 80 additions & 0 deletions test/functional/indices/fulltext-index-test/fulltext-index-test.ts
@@ -0,0 +1,80 @@
import "reflect-metadata";
import {Connection} from "../../../../src";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {expect} from "chai";
import { Post } from "./entity/Post";

describe("indices > fulltext index", () => {

let connections: Connection[];
before(async () => {
connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
enabledDrivers: ["mysql"],
schemaCreate: true,
dropSchema: true,
});
});
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));

it("should correctly create fulltext indices", () => Promise.all(connections.map(async connection => {

const queryRunner = connection.createQueryRunner();
const table = await queryRunner.getTable("post");

table!.indices.length.should.be.equal(2);
expect(table!.indices[0].isFulltext).to.be.true;
expect(table!.indices[1].isFulltext).to.be.true;

await queryRunner.release();
})));

it("with default parser", () => Promise.all(connections.map(async connection => {

const postRepository = connection.getRepository(Post);

const text = "This is text";
const post = new Post();
post.default = text;
post.ngram = text;
await postRepository.save(post);

const loadedPost1 = await postRepository
.createQueryBuilder("post")
.where("MATCH(post.default) AGAINST (:token)", { token: "text" })
.getOne();
expect(loadedPost1).to.be.exist;

const loadedPost2 = await postRepository
.createQueryBuilder("post")
.where("MATCH(post.default) AGAINST (:token)", { token: "te" })
.getOne();
expect(loadedPost2).to.be.undefined;
})));


it("with ngram parser", () => Promise.all(connections.map(async connection => {

const postRepository = connection.getRepository(Post);

const text = "This is text";
const post = new Post();
post.default = text;
post.ngram = text;
await postRepository.save(post);

const loadedPost1 = await postRepository
.createQueryBuilder("post")
.where("MATCH(post.ngram) AGAINST (:token)", { token: "text" })
.getOne();
expect(loadedPost1).to.be.exist;

const loadedPost2 = await postRepository
.createQueryBuilder("post")
.where("MATCH(post.ngram) AGAINST (:token)", { token: "te" })
.getOne();
expect(loadedPost2).to.be.exist;
})));

});

0 comments on commit dd73395

Please sign in to comment.