Skip to content

Commit 497f5c3

Browse files
authored
Merge pull request #65 from Entity-Access/main
Migration steps reduced
2 parents 2078405 + 549eec5 commit 497f5c3

16 files changed

Lines changed: 197 additions & 64 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@entity-access/entity-access",
3-
"version": "1.0.482",
3+
"version": "1.0.490",
44
"description": "",
55
"main": "index.js",
66
"scripts": {

src/common/IColumnSchema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ export default interface IColumnSchema {
66
default: string;
77
key: boolean;
88
computed?: any;
9+
ownerName?: string;
10+
ownerType?: string;
911
}

src/compiler/sql-server/SqlServerSqlMethodTransformer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ export const SqlServerSqlHelper: ISqlHelpers = {
2222
spatial: {
2323
point(x, y, srid) {
2424
if (srid === void 0) {
25-
return prepareAny `Point(${x}, ${y}, 4326)`;
25+
return prepareAny `geography::Point(${x}, ${y}, 4326)`;
2626
}
27-
return prepareAny `Point(${x}, ${y}, ${srid})`;
27+
return prepareAny `geography::Point(${x}, ${y}, ${srid})`;
2828
},
2929
location(x: any) {
30-
return prepareAny `Point(${[(p) => x[0](p).longitude]}, ${[(p) => x[0](p).latitude]}, 4326)`;
30+
return prepareAny `geography::Point(${[(p) => x[0](p).longitude]}, ${[(p) => x[0](p).latitude]}, 4326)`;
3131
},
3232
distance(x, y) {
3333
return prepareAny `${x}.STDistance(${y})`;

src/drivers/base/BaseDriver.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IColumn } from "../../decorators/IColumn.js";
99
import EntityType from "../../entity-query/EntityType.js";
1010
import Migrations from "../../migrations/Migrations.js";
1111
import ChangeEntry, { IChange } from "../../model/changes/ChangeEntry.js";
12+
import type EntityContext from "../../model/EntityContext.js";
1213
import { BinaryExpression, Constant, DeleteStatement, Expression, Identifier, InsertStatement, ReturnUpdated, SelectStatement, TableLiteral, UpdateStatement, UpsertStatement, ValuesStatement } from "../../query/ast/Expressions.js";
1314
import { Query } from "../../query/Query.js";
1415

@@ -180,7 +181,7 @@ export abstract class BaseConnection {
180181
* This migrations only support creation of missing items.
181182
* However, you can provide events to change existing items.
182183
*/
183-
public abstract automaticMigrations(): Migrations;
184+
public abstract automaticMigrations(context: EntityContext): Migrations;
184185

185186
public async runInTransaction<T = any>(fx?: () => Promise<T>) {
186187
await using tx = await this.createTransaction();
@@ -209,7 +210,7 @@ export abstract class BaseConnection {
209210
return tx;
210211
}
211212

212-
abstract getSchema(schema: string, table: string): Promise<IColumnSchema[]>;
213+
abstract getColumnSchema(schema: string): Promise<IColumnSchema[]>;
213214

214215

215216
protected abstract createDbTransaction(): Promise<EntityTransaction>;

src/drivers/postgres/PostgreSqlDriver.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import QueryCompiler from "../../compiler/QueryCompiler.js";
66
import EntityType from "../../entity-query/EntityType.js";
77
import Migrations from "../../migrations/Migrations.js";
88
import PostgresAutomaticMigrations from "../../migrations/postgres/PostgresAutomaticMigrations.js";
9+
import EntityContext from "../../model/EntityContext.js";
910
import DateTime from "../../types/DateTime.js";
1011
import { BaseConnection, BaseDriver, EntityTransaction, IDbConnectionString, IDbReader, IQuery, toQuery } from "../base/BaseDriver.js";
1112
import pg from "pg";
@@ -215,12 +216,40 @@ class PostgreSqlConnection extends BaseConnection {
215216
}
216217

217218

218-
public automaticMigrations(): Migrations {
219-
return new PostgresAutomaticMigrations(this.compiler);
219+
public automaticMigrations(context: EntityContext): Migrations {
220+
return new PostgresAutomaticMigrations(context);
220221
}
221222

222-
getSchema(schema: string, table: string): Promise<IColumnSchema[]> {
223-
throw new EntityAccessError("Not implemented");
223+
async getColumnSchema(schema: string): Promise<IColumnSchema[]> {
224+
const text = `
225+
select
226+
column_name as "columnName",
227+
case data_type
228+
when 'bigint' then 'BigInt'
229+
when 'boolean' then 'Boolean'
230+
when 'timestamp' then 'DateTime'
231+
when 'timestamp with time zone' then 'DateTime'
232+
when 'timestamp without time zone' then 'DateTime'
233+
when 'integer' then 'Int'
234+
when 'real' then 'Double'
235+
when 'numeric' then 'Decimal'
236+
else 'Char' end as "dataType",
237+
case
238+
when is_nullable = 'YES' then true
239+
else false end as "nullable",
240+
character_maximum_length as "length",
241+
case
242+
when is_identity = 'YES' then 'identity'
243+
else null end as "identity",
244+
case
245+
when is_generated = 'YES' then '() => 1'
246+
else null end as "computed",
247+
table_name as "ownerName",
248+
'table' as "ownerType"
249+
from information_schema.columns
250+
where table_schema = $1`;
251+
const r = await this.executeQuery({ text, values: [ schema ]});
252+
return r.rows;
224253
}
225254

226255
public async executeReader(command: IQuery, signal?: AbortSignal): Promise<IDbReader> {

src/drivers/sql-server/SqlServerDriver.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import TimedCache from "../../common/cache/TimedCache.js";
1010
import EntityType from "../../entity-query/EntityType.js";
1111
import DateTime from "../../types/DateTime.js";
1212
import IColumnSchema from "../../common/IColumnSchema.js";
13+
import type EntityContext from "../../model/EntityContext.js";
1314

1415
export type ISqlServerConnectionString = IDbConnectionString & sql.config;
1516

@@ -128,7 +129,7 @@ export class SqlServerConnection extends BaseConnection {
128129
super(driver);
129130
}
130131

131-
async getSchema(schema: string, table: string): Promise<IColumnSchema[]> {
132+
async getColumnSchema(schema: string): Promise<IColumnSchema[]> {
132133
const text = `
133134
SELECT
134135
COLUMN_NAME as [name],
@@ -160,12 +161,13 @@ export class SqlServerConnection extends BaseConnection {
160161
WHEN COLUMN_DEFAULT is NULL THEN ''
161162
ELSE '() => ' + COLUMN_DEFAULT
162163
END as [default],
163-
ColumnProperty(OBJECT_ID(TABLE_SCHEMA+'.'+TABLE_NAME),COLUMN_NAME,'IsComputed') as [computed]
164+
ColumnProperty(OBJECT_ID(TABLE_SCHEMA+'.'+TABLE_NAME),COLUMN_NAME,'IsComputed') as [computed],
165+
TABLE_NAME as [ownerName],
166+
'table' as [ownerType]
164167
FROM INFORMATION_SCHEMA.COLUMNS
165168
WHERE TABLE_SCHEMA = $1
166-
AND TABLE_NAME = $2
167169
`;
168-
const r = await this.executeQuery({ text, values: [schema, table] });
170+
const r = await this.executeQuery({ text, values: [schema] });
169171
return r.rows;
170172
}
171173

@@ -237,8 +239,8 @@ export class SqlServerConnection extends BaseConnection {
237239
return value;
238240
}
239241

240-
public automaticMigrations(): Migrations {
241-
return new SqlServerAutomaticMigrations(this.sqlQueryCompiler);
242+
public automaticMigrations(context: EntityContext): Migrations {
243+
return new SqlServerAutomaticMigrations(context);
242244
}
243245

244246
protected async createDbTransaction(): Promise<EntityTransaction> {

src/migrations/ExistingSchema.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import IColumnSchema from "../common/IColumnSchema.js";
2+
import type { BaseConnection } from "../drivers/base/BaseDriver.js";
3+
4+
export default class ExistingSchema {
5+
6+
7+
public static async getSchema(connection: BaseConnection, schema: string, table: string, caseInsensitive = false) {
8+
let s = this.cache.get(schema);
9+
if (!s) {
10+
const columns = await connection.getColumnSchema(schema);
11+
s = new ExistingSchema(columns, caseInsensitive);
12+
this.cache.set(schema, s);
13+
}
14+
if (caseInsensitive) {
15+
table = table.toLowerCase();
16+
}
17+
return s.tables.get(table) ?? [];
18+
}
19+
20+
private static cache = new Map<string, ExistingSchema>();
21+
22+
public tables = new Map<string,IColumnSchema[]>();
23+
24+
constructor(columns: IColumnSchema[], caseInsensitive = false) {
25+
for (const c of columns) {
26+
let tableName = c.ownerName;
27+
if (caseInsensitive) {
28+
tableName = tableName.toLowerCase();
29+
}
30+
let table = this.tables.get(tableName);
31+
if (!table) {
32+
table = [];
33+
this.tables.set(tableName, table);
34+
}
35+
table.push(c);
36+
}
37+
}
38+
39+
}

src/migrations/Migrations.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,52 @@
1+
import Logger, { ConsoleLogger } from "../common/Logger.js";
12
import { modelSymbol } from "../common/symbols/symbols.js";
23
import type QueryCompiler from "../compiler/QueryCompiler.js";
34
import ICheckConstraint from "../decorators/ICheckConstraint.js";
5+
import { IColumn } from "../decorators/IColumn.js";
46
import type { IForeignKeyConstraint } from "../decorators/IForeignKeyConstraint.js";
57
import type { IIndex } from "../decorators/IIndex.js";
8+
import type { BaseConnection, IQuery, IQueryResult } from "../drivers/base/BaseDriver.js";
69
import type EntityType from "../entity-query/EntityType.js";
710
import type EntityContext from "../model/EntityContext.js";
811
import type EntityQuery from "../model/EntityQuery.js";
12+
import ExistingSchema from "./ExistingSchema.js";
913

1014
export default abstract class Migrations {
1115

12-
constructor(protected compiler: QueryCompiler) {}
16+
logger: Logger;
1317

14-
public async migrate(context: EntityContext , {
18+
constructor(
19+
private context: EntityContext,
20+
private connection: BaseConnection = context.connection,
21+
protected compiler: QueryCompiler = context.driver.compiler
22+
) {
23+
24+
}
25+
26+
public async migrate({
1527
version,
1628
name = "default",
1729
historyTableName = "migrations",
30+
log = new ConsoleLogger(false),
1831
seed,
19-
}: { version?: string, name?: string, historyTableName?: string, seed?: (c: EntityContext) => Promise<any>} = {} ) {
32+
createIndexForForeignKeys = true
33+
}: {
34+
version?: string,
35+
name?: string,
36+
historyTableName?: string,
37+
log?: Logger,
38+
seed?: (c: EntityContext) => Promise<any>,
39+
createIndexForForeignKeys?: boolean
40+
} = {} ) {
41+
const { context } = this;
2042
const { model } = context;
43+
this.logger = log ?? context.logger;
2144
const postMigration = [] as (() => Promise<void>)[];
2245

2346
if (version) {
2447
// check if we have already stored this version...
2548
if(await this.hasVersion(context, name, version, historyTableName)) {
49+
// eslint-disable-next-line no-console
2650
console.warn(`Skipping migration, migration already exists for ${version}`);
2751
return false;
2852
}
@@ -68,6 +92,16 @@ export default abstract class Migrations {
6892
}
6993

7094
for (const { isInverseRelation , foreignKeyConstraint, relatedTypeClass } of type.relations) {
95+
96+
if (createIndexForForeignKeys) {
97+
postMigration.push(() =>
98+
this.createIndexForForeignKeys(context, type, type.nonKeys.filter((x) =>
99+
x.fkRelation
100+
&& (!x.key || type.keys.indexOf(x) !== 0)
101+
&& !x.fkRelation?.doNotCreateIndex))
102+
);
103+
}
104+
71105
if (isInverseRelation) {
72106
continue;
73107
}
@@ -120,6 +154,8 @@ export default abstract class Migrations {
120154

121155
abstract ensureVersionTable(context: EntityContext, table: string): Promise<any>;
122156

157+
abstract createIndexForForeignKeys(context: EntityContext, type: EntityType, fkColumns: IColumn[]): Promise<void>;
158+
123159
async commitVersion(context: EntityContext, name, version, table) {
124160
const { quote, escapeLiteral } = this.compiler;
125161

@@ -164,4 +200,10 @@ export default abstract class Migrations {
164200

165201
abstract migrateCheckConstraint(context: EntityContext, checkConstraint: ICheckConstraint, type: EntityType);
166202

203+
protected executeQuery(command: IQuery, signal?: AbortSignal): Promise<IQueryResult> {
204+
const text = typeof command === "string" ? command : command.text;
205+
this.logger?.log(text);
206+
return this.connection.executeQuery(command, signal);
207+
}
208+
167209
}

0 commit comments

Comments
 (0)