Skip to content

fixing allowNull, onDelete, onChange, deferrable #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"clean": "rm -fr build",
"lint": "eslint --fix --ext .ts output/*.ts",
"build": "npm run clean && tsc",
"prepare": "npm run build",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is redundant, it's the same as build

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not
https://docs.npmjs.com/misc/scripts#description
This is needed so when you simply call npm install it will build the packages correctly, otherwise the build is not run. This is especially visible when you use the package using github path.

"audit": "npm audit",
"docker-remove-containers": "./src/tests/integration/docker-remove-containers.sh",
"docker-start-mysql": "./src/tests/integration/mysql/docker-start-mysql.sh",
Expand Down
12 changes: 12 additions & 0 deletions src/builders/ModelBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export class ModelBuilder extends Builder {
...col.dataType && { type: col.dataType },
...col.comment && { comment: col.comment },
...col.defaultValue && { defaultValue: col.defaultValue },
...col.onUpdate && { onUpdate: col.onUpdate },
...col.onDelete && { onDelete: col.onDelete },
// @TODO fix this by creating a typescript definition for model
// ...col.references && { references: col.references as ModelAttributeColumnReferencesOptions },
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the following type must be imported?

import { ModelAttributeColumnOptions, ModelAttributeColumnReferencesOptions } from 'sequelize';

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not about importing types. This is about reading references directly from postgres tables instead of taking it from file definition - this was just a try

};

return props;
Expand Down Expand Up @@ -179,6 +183,14 @@ export class ModelBuilder extends Builder {
'sequelize-typescript'
));

generatedCode += '\n';
generatedCode += nodeToString(generateNamedImports(
[
'Deferrable',
],
'sequelize'
));

generatedCode += '\n';

// Named imports for associations
Expand Down
22 changes: 15 additions & 7 deletions src/builders/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export const nodeToString = (node: ts.Node): string => {
* @param {string} moduleSpecifier
* @returns {string} Named import code
*/
export const generateNamedImports = (importsSpecifier: string[], moduleSpecifier: string): ts.ImportDeclaration => {
export const generateNamedImports = (
importsSpecifier: string[],
moduleSpecifier: string): ts.ImportDeclaration => {
return ts.createImportDeclaration(
/* decorators */ undefined,
/* modifiers */ undefined,
Expand All @@ -34,7 +36,10 @@ export const generateNamedImports = (importsSpecifier: string[], moduleSpecifier
ts.createNamedImports(
[
...importsSpecifier
.map(is => ts.createImportSpecifier(undefined, ts.createIdentifier(is)))
.map(is => ts.createImportSpecifier(
undefined,
ts.createIdentifier(is)
))
]
)
),
Expand Down Expand Up @@ -71,13 +76,16 @@ export const generateObjectLiteralDecorator = (
ts.createIdentifier(decoratorIdentifier),
undefined,
[
ts.createObjectLiteral(
[
ts.createObjectLiteral([
...Object.entries(props)
.map(e => ts.createPropertyAssignment(e[0],
.map(e => ts.createPropertyAssignment(
e[0],
typeof e[1] === 'string' && (
e[1].startsWith('DataType.') || e[1].startsWith('Sequelize.')
) ? ts.createIdentifier(e[1]) : ts.createLiteral(e[1])))
e[1].startsWith('DataType.') ||
e[1].startsWith('Sequelize.') ||
e[1].startsWith('Deferrable.')
) ? ts.createIdentifier(e[1]) : ts.createLiteral(e[1])
))
]
)
]
Expand Down
21 changes: 19 additions & 2 deletions src/dialects/Dialect.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { IndexType, IndexMethod, AbstractDataTypeConstructor } from 'sequelize';
import { Sequelize } from 'sequelize-typescript';
import { Deferrable } from 'sequelize/types/lib/deferrable';
import { IConfig } from '../config';
import { createConnection } from "../connection";
import { AssociationsParser, IAssociationsParsed, IAssociationMetadata } from './AssociationsParser'
import { AssociationsParser, IAssociationMetadata } from './AssociationsParser'
import { caseTransformer } from './utils';
import {parse} from "@typescript-eslint/parser";

export interface ITablesMetadata {
[tableName: string]: ITableMetadata;
Expand All @@ -22,6 +22,20 @@ export interface ITableMetadata {
comment?: string;
}

export enum CONSTRAINT_TYPES {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these types common to all the Dialects?
If I assuming correctly these are constraint for foreign keys, so we should probably name it something like FOREIGN_KEY_CONSTRAINTS. Also for enum we should probably use more verbose naming, using constant case:

export enum FOREIGN_KEY_CONSTRAINTS {
    CASCADE = 'CASCADE',
    SET_DEFAULT = 'SET DEFAULT',
    SET_NULL = 'SET NULL',
    RESTRICT = 'RESTRICT',
    NO_ACTION = 'NO ACTION',
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know about other dialects, this is for postgresql. The keys are what is coming from postgresql already and then how it should be mapped so you can't change it

c = 'CASCADE',
d = 'SET DEFAULT',
n = 'SET NULL',
r = 'RESTRICT',
a = 'NO ACTION',
}

export interface IColumnMetadataReference {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don't need to create this interface, this type is already defined in sequelize:

/**
 * References options for the column's attributes
 */
export interface ModelAttributeColumnReferencesOptions {
  /**
   * If this column references another table, provide it here as a Model, or a string
   */
  model?: string | typeof Model;

  /**
   * The column of the foreign table that this column references
   */
  key?: string;

  /**
   * When to check for the foreign key constraing
   *
   * PostgreSQL only
   */
  deferrable?: Deferrable;
}

model: string | null;
key: string | null;
deferrable: string | Deferrable | null;
}

export interface IColumnMetadata {
name: string; // Model field name
originName: string; // Database column name
Expand All @@ -38,6 +52,9 @@ export interface IColumnMetadata {
indices?: IIndexMetadata[],
comment?: string;
defaultValue?: any;
onDelete?: CONSTRAINT_TYPES;
onUpdate?: CONSTRAINT_TYPES;
references?: IColumnMetadataReference;
}

export interface IIndexMetadata {
Expand Down
51 changes: 49 additions & 2 deletions src/dialects/DialectPostgres.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { QueryTypes, AbstractDataTypeConstructor } from 'sequelize';
import { Sequelize, DataType } from 'sequelize-typescript';
import { IConfig } from '../config';
import { IColumnMetadata, IIndexMetadata, Dialect, ITable } from './Dialect';
import {
IColumnMetadata,
IIndexMetadata,
Dialect,
ITable,
CONSTRAINT_TYPES
} from './Dialect';
import { generatePrecisionSignature, warnUnknownMappingForDataType } from './utils';

interface ITableRow {
Expand Down Expand Up @@ -57,6 +63,12 @@ interface IColumnMetadataPostgres {
generation_expression: string;
is_updatable: string;
description: string | null;
confdeltype: string | null;
confupdtype: string | null;
condeferrable: boolean;
condeferred: boolean;
foreign_table_name: string | null;
foreign_column_name: string | null;
}

interface IIndexMetadataPostgres {
Expand Down Expand Up @@ -235,13 +247,25 @@ export class DialectPostgres extends Dialect {
WHERE a.attrelid = '${config.metadata!.schema}.${table}'::regclass AND a.attnum > 0
AND c.ordinal_position = a.attnum AND x.indisprimary IS TRUE
) AS is_primary,
isc_f.table_name as foreign_table_name,
isc_f.column_name as foreign_column_name,
pgc.confdeltype,
pgc.confupdtype,
pgc.condeferrable,
pgc.condeferred,
c.*,
pgd.description
FROM information_schema.columns c
INNER JOIN pg_catalog.pg_statio_all_tables as st
ON c.table_schema = st.schemaname AND c.table_name = st.relname
LEFT OUTER JOIN pg_catalog.pg_description pgd
ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
LEFT OUTER JOIN pg_catalog.pg_constraint pgc
ON pgc.conrelid = st.relid AND pgc.conkey[1] = c.ordinal_position
LEFT OUTER JOIN pg_catalog.pg_class pgc_f
ON pgc_f.oid = pgc.confrelid AND pgc.conkey[1] = c.ordinal_position
LEFT OUTER JOIN information_schema.columns isc_f ON isc_f.table_name = pgc_f.relname
AND isc_f.ordinal_position = pgc.confkey[1]
LEFT OUTER JOIN ( -- Sequences (auto increment) metadata
SELECT seqclass.relname AS sequence_name,
pn.nspname AS schema_name,
Expand Down Expand Up @@ -290,12 +314,35 @@ export class DialectPostgres extends Dialect {
this.mapDbTypeToSequelize(column.udt_name).key
.split(' ')[0], // avoids 'DOUBLE PRECISION' key to include PRECISION in the mapping
},
allowNull: !!column.is_nullable && !column.is_primary,
allowNull: column.is_nullable === 'YES' && !column.is_primary,
primaryKey: column.is_primary,
autoIncrement: column.is_sequence,
indices: [],
comment: column.description ?? undefined,
};

if (CONSTRAINT_TYPES[column.confdeltype as keyof typeof CONSTRAINT_TYPES]) {
columnMetadata.onDelete = CONSTRAINT_TYPES[column.confdeltype as keyof typeof CONSTRAINT_TYPES];
}
if (CONSTRAINT_TYPES[column.confupdtype as keyof typeof CONSTRAINT_TYPES]) {
columnMetadata.onUpdate = CONSTRAINT_TYPES[column.confupdtype as keyof typeof CONSTRAINT_TYPES];
}

if (column.condeferrable || column.condeferred) {
let deferrable = 'Deferrable.NOT()';
if (column.condeferrable && column.condeferred) {
deferrable = 'Deferrable.INITIALLY_DEFERRED()';
}
if (column.condeferrable && !column.condeferred) {
deferrable = 'Deferrable.INITIALLY_IMMEDIATE()';
}
columnMetadata.references = {
model: column.foreign_table_name,
key: column.foreign_column_name,
deferrable,
}
}

if (column.column_default) {
columnMetadata.defaultValue = `Sequelize.literal("${column.column_default}")`;
}
Expand Down