Skip to content

Commit

Permalink
feat(migrations): add support for migrations (closes #38)
Browse files Browse the repository at this point in the history
  • Loading branch information
rogerpadilla committed Aug 16, 2021
1 parent 65402d3 commit b3ddb2b
Show file tree
Hide file tree
Showing 70 changed files with 1,304 additions and 760 deletions.
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ services:
image: 'mysql:8.0.25'
restart: always
environment:
MYSQL_ROOT_PASSWORD: 'admin'
MYSQL_ROOT_PASSWORD: 'root'
MYSQL_USER: 'test'
MYSQL_PASSWORD: 'test'
MYSQL_DATABASE: 'test'
ports:
- '3307:3306'
container_name: 'uql-mysql-8'

mariadb:
image: 'mariadb:10.5.10'
restart: always
environment:
MYSQL_ROOT_PASSWORD: 'admin'
MYSQL_ROOT_PASSWORD: 'root'
MYSQL_USER: 'test'
MYSQL_PASSWORD: 'test'
MYSQL_DATABASE: 'test'
Expand Down
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,25 @@
"devDependencies": {
"@commitlint/cli": "^13.1.0",
"@commitlint/config-conventional": "^13.1.0",
"@types/jest": "^26.0.24",
"@typescript-eslint/eslint-plugin": "^4.29.0",
"@typescript-eslint/parser": "^4.29.0",
"concurrently": "^6.2.0",
"@types/jest": "^27.0.1",
"@typescript-eslint/eslint-plugin": "^4.29.2",
"@typescript-eslint/parser": "^4.29.2",
"concurrently": "^6.2.1",
"conventional-changelog-cli": "^2.1.1",
"coveralls": "^3.1.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-import": "^2.24.0",
"eslint-plugin-prettier": "^3.4.0",
"jest": "^27.0.6",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"run-rs": "^0.7.5",
"semantic-release": "^17.4.4",
"semantic-release": "^17.4.5",
"ts-jest": "^27.0.4",
"ts-node": "^10.1.0",
"ts-node": "^10.2.0",
"ts-node-dev": "^1.1.8",
"typescript": "^4.3.5"
"typescript": "^4.4.1-rc"
},
"prettier": {
"tabWidth": 2,
Expand Down
6 changes: 3 additions & 3 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
"rimraf": "^3.0.2",
"source-map-loader": "^3.0.0",
"ts-loader": "^9.2.5",
"typescript": "^4.3.5",
"webpack": "^5.49.0",
"webpack-cli": "^4.7.2",
"typescript": "^4.4.1-rc",
"webpack": "^5.50.0",
"webpack-cli": "^4.8.0",
"webpack-log": "^3.0.2"
},
"author": "Roger Padilla",
Expand Down
6 changes: 3 additions & 3 deletions packages/client/src/options.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { User } from '@uql/core/test';
import { getOptions, getQuerier, getQuerierPool, getRepository, setOptions } from './options';
import { BaseClientRepository, HttpQuerier } from './querier';
import { GenericClientRepository, HttpQuerier } from './querier';
import { ClientQuerier, UqlClientOptions } from './type';

describe('options', () => {
Expand Down Expand Up @@ -48,8 +48,8 @@ describe('options', () => {
const querier2 = getQuerier();
expect(querier2).toBe(querierMock);

expect(getRepository(User)).toBeInstanceOf(BaseClientRepository);
expect(getRepository(User, querier1)).toBeInstanceOf(BaseClientRepository);
expect(getRepository(User)).toBeInstanceOf(GenericClientRepository);
expect(getRepository(User, querier1)).toBeInstanceOf(GenericClientRepository);

expect(getQuerierPool()).toBe(getQuerierPool());
});
Expand Down
9 changes: 4 additions & 5 deletions packages/client/src/options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Type } from '@uql/core/type';
import { BaseClientRepository } from './querier/baseClientRepository';
import { GenericClientRepository } from './querier/genericClientRepository';
import { HttpQuerier } from './querier/httpQuerier';
import { ClientQuerier, UqlClientOptions } from './type';
import { UqlClientOptions } from './type';

let options: UqlClientOptions;

Expand All @@ -27,7 +27,6 @@ export function getQuerier() {
return getQuerierPool().getQuerier();
}

export function getRepository<E>(entity: Type<E>, querier?: ClientQuerier) {
const theQuerier = querier ?? getQuerier();
return new BaseClientRepository(entity, theQuerier);
export function getRepository<E>(entity: Type<E>, querier = getQuerier()) {
return new GenericClientRepository(entity, querier);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IdValue, Query, QueryCriteria, QueryOptions, QuerySearch, QueryUnique, Type } from '@uql/core/type';
import { ClientQuerier, ClientRepository, RequestOptions } from '../type';

export class BaseClientRepository<E> implements ClientRepository<E> {
export class GenericClientRepository<E> implements ClientRepository<E> {
constructor(readonly entity: Type<E>, readonly querier: ClientQuerier) {}

count(qm: QuerySearch<E>, opts?: RequestOptions) {
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/querier/httpQuerier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { kebabCase } from '@uql/core/util';
import { RequestOptions, RequestFindOptions, ClientQuerier, ClientRepository } from '../type';
import { get, post, patch, remove } from '../http';
import { stringifyQuery } from './querier.util';
import { BaseClientRepository } from './baseClientRepository';
import { GenericClientRepository } from './genericClientRepository';

export class HttpQuerier implements ClientQuerier {
constructor(readonly basePath: string) {}
Expand Down Expand Up @@ -73,7 +73,7 @@ export class HttpQuerier implements ClientQuerier {
}

getRepository<E>(entity: Type<E>): ClientRepository<E> {
return new BaseClientRepository(entity, this);
return new GenericClientRepository(entity, this);
}

getBasePath<E>(entity: Type<E>) {
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/querier/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './httpQuerier';
export * from './baseClientRepository';
export * from './genericClientRepository';
export * from './querier.util';
6 changes: 3 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
"dependencies": {
"reflect-metadata": "^0.1.13",
"sqlstring": "^2.3.2",
"tslib": "^2.3.0"
"tslib": "^2.3.1"
},
"devDependencies": {
"@types/node": "^16.4.13",
"@types/node": "^16.6.1",
"@types/sqlstring": "^2.3.0",
"@types/uuid": "^8.3.1",
"copyfiles": "^2.4.1",
"rimraf": "^3.0.2",
"typescript": "^4.3.5",
"typescript": "^4.4.1-rc",
"uuid": "^8.3.2"
},
"author": "Roger Padilla",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { Spec, User, Item, ItemAdjustment, TaxCategory, Profile, InventoryAdjust

import { FieldKey, QueryFilter } from '@uql/core/type';
import { raw } from '@uql/core/util';
import { BaseSqlDialect } from './baseSqlDialect';
import { AbstractSqlDialect } from './abstractSqlDialect';

export abstract class BaseSqlDialectSpec implements Spec {
constructor(readonly dialect: BaseSqlDialect) {}
export abstract class AbstractSqlDialectSpec implements Spec {
constructor(readonly dialect: AbstractSqlDialect) {}

shouldBeValidEscapeCharacter() {
expect(this.dialect.escapeIdChar).toBe('`');
}

shouldBeginTransaction() {
expect(this.dialect.beginTransactionCommand).toBe('BEGIN TRANSACTION');
expect(this.dialect.beginTransactionCommand).toBe('START TRANSACTION');
}

shouldInsertMany() {
Expand Down Expand Up @@ -1079,22 +1079,22 @@ export abstract class BaseSqlDialectSpec implements Spec {
shouldFind$text() {
expect(
this.dialect.find(Item, {
$project: { id: true },
$filter: { $text: { $fields: ['name', 'description'], $value: 'some text' }, companyId: 1 },
$project: ['id'],
$filter: { $text: { $fields: ['name', 'description'], $value: 'some text' }, creatorId: 1 },
$limit: 30,
})
).toBe("SELECT `id` FROM `Item` WHERE `Item` MATCH 'some text' AND `companyId` = 1 LIMIT 30");
).toBe("SELECT `id` FROM `Item` WHERE MATCH(`name`, `description`) AGAINST('some text') AND `creatorId` = 1 LIMIT 30");

expect(
this.dialect.find(User, {
$project: { id: 1 },
$project: { id: true },
$filter: {
$text: { $fields: ['name'], $value: 'something' },
name: { $ne: 'other unwanted' },
companyId: 1,
creatorId: 1,
},
$limit: 10,
})
).toBe("SELECT `id` FROM `User` WHERE `User` MATCH 'something' AND `name` <> 'other unwanted' AND `companyId` = 1 LIMIT 10");
).toBe("SELECT `id` FROM `User` WHERE MATCH(`name`) AGAINST('something') AND `name` <> 'other unwanted' AND `creatorId` = 1 LIMIT 10");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
QueryFilterLogical,
} from '@uql/core/type';

export abstract class BaseSqlDialect implements QueryDialect {
export abstract class AbstractSqlDialect implements QueryDialect {
readonly escapeIdRegex: RegExp;

constructor(readonly beginTransactionCommand: string, readonly escapeIdChar: '`' | '"') {
Expand Down Expand Up @@ -229,8 +229,10 @@ export abstract class BaseSqlDialect implements QueryDialect {
}

if (key === '$text') {
const meta = getMeta(entity);
const search = val as QueryTextSearchOptions<E>;
return `${this.escapeId(meta.name)} MATCH ${this.escape(search.$value)}`;
const fields = search.$fields.map((field) => this.escapeId(meta.fields[field]?.name ?? field));
return `MATCH(${fields.join(', ')}) AGAINST(${this.escape(search.$value)})`;
}

if (key === '$and' || key === '$or' || key === '$not' || key === '$nor') {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/dialect/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './baseSqlDialect';
export * from './abstractSqlDialect';
export * from './mysqlDialect';
export * from './postgresDialect';
export * from './sqliteDialect';
32 changes: 3 additions & 29 deletions packages/core/src/dialect/mysqlDialect.spec.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,11 @@
import { createSpec, Item, User } from '@uql/core/test';
import { BaseSqlDialectSpec } from './baseSqlDialect-spec';
import { createSpec } from '@uql/core/test';
import { AbstractSqlDialectSpec } from './abstractSqlDialect-spec';
import { MySqlDialect } from './mysqlDialect';

export class MySqlDialectSpec extends BaseSqlDialectSpec {
export class MySqlDialectSpec extends AbstractSqlDialectSpec {
constructor() {
super(new MySqlDialect());
}

override shouldBeginTransaction() {
expect(this.dialect.beginTransactionCommand).toBe('START TRANSACTION');
}

override shouldFind$text() {
expect(
this.dialect.find(Item, {
$project: ['id'],
$filter: { $text: { $fields: ['name', 'description'], $value: 'some text' }, creatorId: 1 },
$limit: 30,
})
).toBe("SELECT `id` FROM `Item` WHERE MATCH(`name`, `description`) AGAINST('some text') AND `creatorId` = 1 LIMIT 30");

expect(
this.dialect.find(User, {
$project: { id: true },
$filter: {
$text: { $fields: ['name'], $value: 'something' },
name: { $ne: 'other unwanted' },
creatorId: 1,
},
$limit: 10,
})
).toBe("SELECT `id` FROM `User` WHERE MATCH(`name`) AGAINST('something') AND `name` <> 'other unwanted' AND `creatorId` = 1 LIMIT 10");
}
}

createSpec(new MySqlDialectSpec());
17 changes: 2 additions & 15 deletions packages/core/src/dialect/mysqlDialect.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
import { getMeta } from '@uql/core/entity';
import { QueryComparisonOptions, QueryFilterMap, QueryTextSearchOptions, Type } from '@uql/core/type';
import { BaseSqlDialect } from './baseSqlDialect';
import { AbstractSqlDialect } from './abstractSqlDialect';

export class MySqlDialect extends BaseSqlDialect {
export class MySqlDialect extends AbstractSqlDialect {
constructor() {
super('START TRANSACTION', '`');
}

override compare<E, K extends keyof QueryFilterMap<E>>(entity: Type<E>, key: K, val: QueryFilterMap<E>[K], opts?: QueryComparisonOptions): string {
if (key === '$text') {
const meta = getMeta(entity);
const search = val as QueryTextSearchOptions<E>;
const fields = search.$fields.map((field) => this.escapeId(meta.fields[field]?.name ?? field));
return `MATCH(${fields.join(', ')}) AGAINST(${this.escape(search.$value)})`;
}

return super.compare(entity, key, val, opts);
}
}
2 changes: 1 addition & 1 deletion packages/core/src/dialect/postgresDialect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class PostgresDialectSpec {
}

shouldBeginTransaction() {
expect(this.dialect.beginTransactionCommand).toBe('BEGIN');
expect(this.dialect.beginTransactionCommand).toBe('BEGIN TRANSACTION');
}

shouldInsertMany() {
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/dialect/postgresDialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {
Type,
FieldKey,
} from '@uql/core/type';
import { BaseSqlDialect } from './baseSqlDialect';
import { AbstractSqlDialect } from './abstractSqlDialect';

export class PostgresDialect extends BaseSqlDialect {
export class PostgresDialect extends AbstractSqlDialect {
constructor() {
super('BEGIN', '"');
super('BEGIN TRANSACTION', '"');
}

override insert<E>(entity: Type<E>, payload: E | E[]): string {
Expand All @@ -29,7 +29,6 @@ export class PostgresDialect extends BaseSqlDialect {
const fields = search.$fields.map((field) => this.escapeId(meta.fields[field]?.name ?? field)).join(` || ' ' || `);
return `to_tsvector(${fields}) @@ to_tsquery(${this.escape(search.$value)})`;
}

return super.compare(entity, key, val, opts);
}

Expand Down
32 changes: 29 additions & 3 deletions packages/core/src/dialect/sqliteDialect.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
import { createSpec } from '@uql/core/test';
import { BaseSqlDialectSpec } from './baseSqlDialect-spec';
import { createSpec, Item, User } from '@uql/core/test';
import { AbstractSqlDialectSpec } from './abstractSqlDialect-spec';
import { SqliteDialect } from './sqliteDialect';

class SqliteDialectSpec extends BaseSqlDialectSpec {
class SqliteDialectSpec extends AbstractSqlDialectSpec {
constructor() {
super(new SqliteDialect());
}

override shouldBeginTransaction() {
expect(this.dialect.beginTransactionCommand).toBe('BEGIN TRANSACTION');
}

override shouldFind$text() {
expect(
this.dialect.find(Item, {
$project: { id: true },
$filter: { $text: { $fields: ['name', 'description'], $value: 'some text' }, companyId: 1 },
$limit: 30,
})
).toBe("SELECT `id` FROM `Item` WHERE `Item` MATCH 'some text' AND `companyId` = 1 LIMIT 30");

expect(
this.dialect.find(User, {
$project: { id: 1 },
$filter: {
$text: { $fields: ['name'], $value: 'something' },
name: { $ne: 'other unwanted' },
companyId: 1,
},
$limit: 10,
})
).toBe("SELECT `id` FROM `User` WHERE `User` MATCH 'something' AND `name` <> 'other unwanted' AND `companyId` = 1 LIMIT 10");
}
}

createSpec(new SqliteDialectSpec());
15 changes: 13 additions & 2 deletions packages/core/src/dialect/sqliteDialect.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { BaseSqlDialect } from './baseSqlDialect';
import { getMeta } from '../entity';
import { QueryComparisonOptions, QueryFilterMap, QueryTextSearchOptions, Type } from '../type';
import { AbstractSqlDialect } from './abstractSqlDialect';

export class SqliteDialect extends BaseSqlDialect {
export class SqliteDialect extends AbstractSqlDialect {
constructor() {
super('BEGIN TRANSACTION', '`');
}

override compare<E, K extends keyof QueryFilterMap<E>>(entity: Type<E>, key: K, val: QueryFilterMap<E>[K], opts?: QueryComparisonOptions): string {
if (key === '$text') {
const meta = getMeta(entity);
const search = val as QueryTextSearchOptions<E>;
return `${this.escapeId(meta.name)} MATCH ${this.escape(search.$value)}`;
}
return super.compare(entity, key, val, opts);
}
}
Loading

0 comments on commit b3ddb2b

Please sign in to comment.