UNPKG

jsonschema2ddl

Version:
312 lines (280 loc) 9.01 kB
import * as crypto from "crypto"; import { COLUMNS_TYPES, COLUMNS_TYPES_PREFERENCE, FK_TYPES } from "./types"; import { db_column_name, get_one_schema } from "./utils"; interface ColumnParams { name: string; database_flavor?: string; comment?: string; constraints?: any; jsonschema_type?: string; jsonschema_fields?: any; } interface ForeignKeyColumnParams extends ColumnParams { table_ref: Table; } interface TableParams { ref: string; name: string; database_flavor: string; columns?: Column[]; primary_key?: Column; comment?: string; indexes?: string[]; unique_columns?: string[]; jsonschema_fields: any; } interface ExpandColumnsParams { table_definitions?: any; columns_definitions?: any; referenced?: boolean; } /** * Object to encapsulate a Column. * * Attributes: * name(str): name of the Column. * database_flavor(str): postgres or redshift.Defaults to postgres. * comment(str): comment of the Column.Defaults to None. * constraints(Dict): other columns constraints (not implemented). * jsonschema_fields(Dict): Original fields in the jsonschema. */ export class Column { name: string; database_flavor: string; comment: string; constraints: any = {}; jsonschema_type: string; jsonschema_fields: any; constructor({ name, database_flavor, comment, constraints, jsonschema_type, jsonschema_fields }: ColumnParams) { this.name = name; this.database_flavor = database_flavor || "postgres"; this.comment = comment || ''; this.constraints = constraints || {}; this.jsonschema_type = jsonschema_type || ''; this.jsonschema_fields = jsonschema_fields || {}; } public get max_length(): number { return this.jsonschema_fields["maxLength"] || 256; } /** * Data type of the columns. * * It accounts of the mapping of the original type to the db types. * * Returns: * str: data type of the column. */ public get data_type(): string { if ("format" in this.jsonschema_fields && this.jsonschema_fields["format"] in COLUMNS_TYPES_PREFERENCE) { this.jsonschema_type = this.jsonschema_fields["format"] } return COLUMNS_TYPES[this.database_flavor][this.jsonschema_type] .replace('{}', String(this.max_length)); } public get is_pk(): boolean { return !!this.jsonschema_fields["pk"]; } /** * Returns true if the column is a index. * * Returns: * bool: True if it is index. */ public get is_index(): boolean { return !!this.jsonschema_fields["index"]; } /** * Returns true if the column is a unique. * * Returns: * bool: True if it is unique. */ public get is_unique(): boolean { return !!this.jsonschema_fields["unique"]; } /** * Returns true if the column is a foreign key. * * Returns: * bool: True if it is foreign key */ public get is_fk(): boolean { return false; } hash() { return crypto.createHash('sha256').update(this.name).digest('hex'); } toString() { return `Column(name=${this.name} data_type=${this.data_type})`; } } /** * Object to encapsulate a Table. * * Attributes: * ref(str): id or reference to the table in the jsonschema. * name(str): name of the table. * database_flavor(str): postgres or redshift.Defaults to postgres. * columns(List[Column]): columns of the table. * primary_key(Column): Primary key column of the table. * comment(str): comment of the table.Defaults to None. * indexes(List[str]): Table indexeses(not implemented). * unique_columns(List[str]): Table unique constraints(not implemented). * jsonschema_fields(Dict): Original fields in the jsonschema. */ export class Table { ref: string; name: string; database_flavor: string; columns: Column[]; jsonschema_fields: any; primary_key?: Column; comment?: string; indexes?: string[]; unique_columns?: string[]; constructor({ ref, name, database_flavor, columns, primary_key, comment, indexes, unique_columns, jsonschema_fields }: TableParams) { this.ref = ref; this.name = name; this.database_flavor = database_flavor || "postgres"; this.columns = columns || []; this.primary_key = primary_key; this.comment = comment; this.indexes = indexes || []; this.unique_columns = unique_columns || []; this.jsonschema_fields = jsonschema_fields || {}; } private _expanded: boolean = false; /** * Expand the columns definitions of the * * Args: * table_definitions(Dict, optional): Dictionary with the rest of the * tables definitions.It is used for recursive calls to get the * foreign keys.Defaults to dict(). * columns_definitions(Dict, optional): Dictionary with the definition * of columns outside the main properties field.Defaults to dict(). * referenced(bool, optional): Whether or not the table is referenced * by others.Used to make sure there is a Primary Key defined. * Defaults to False. * * @param table_definitions * @param columns_definitions * @param referenced * @returns */ expand_columns({ table_definitions = {}, columns_definitions = {}, referenced }: ExpandColumnsParams) { if (this._expanded) { console.log("Already expanded table. Skipping..."); return this; } const props: Record<string, any> = this.jsonschema_fields["properties"]; for (let [col_name, col_object] of Object.entries(props)) { console.log(`Creating column ${col_name}`); col_name = db_column_name(col_name); console.log(`Renamed column to ${col_name}`); let col: Column; if ("$ref" in col_object) { console.log(`Expanding ${col_name} reference ${col_object["$ref"]}`); console.log(JSON.stringify(table_definitions, null, 2)); if (col_object["$ref"] in table_definitions) { const myref = col_object["$ref"]; console.log(`Column is a FK! Expanding ${myref} before continue...`); table_definitions[myref] = table_definitions[myref].expand_columns({ table_definitions, referenced: true, }); col = new FKColumn({ table_ref: table_definitions[myref], name: col_name, database_flavor: this.database_flavor, }); } else if (col_object["$ref"] in columns_definitions) { console.log("Column ref a type that is not a object. Copy Column from columns definitions"); const myref = col_object["$ref"]; const ref_col = columns_definitions[myref]; const col_as_dict = { ...ref_col, "name": col_name }; col = new Column(col_as_dict); } else { console.log("Skipping ref as it is not in table definitions neither in columns definitions"); continue; } } else { if (!("type" in col_object)) { col_object = get_one_schema(col_object); } col = new Column({ name: col_name, database_flavor: this.database_flavor, jsonschema_type: col_object["type"], jsonschema_fields: col_object, }); } this.columns.push(col); if (col.is_pk) { this.primary_key = col; } console.log(`New created column ${col}`); } if (referenced && !this.primary_key) { console.log("Creating id column for the table in order to reference it as PK"); let idcol = new Column({ name: "id", database_flavor: this.database_flavor, jsonschema_type: "id", }); this.columns.push(idcol); this.primary_key = idcol; } this.columns = this._deduplicate_columns(this.columns); return this; } _deduplicate_columns(columns: Column[]): Column[] { return columns.reduce((a: Column[], c: Column) => { if (!a.find(x => x.name === c.name)) { a.push(c); } return a; }, []); } } /** * Special type of Column object to represent a foreign key * * Attributes: * table_ref(Table): Pointer to the foreign table object */ export class FKColumn extends Column { table_ref: Table; constructor(params: ForeignKeyColumnParams) { super(params); this.table_ref = params.table_ref; } /** * Data type of the foreign key. * * Accounts of the data type of the primary key of the foreing table. * * Returns: * str: the column data type. */ public get data_type(): string { const data_type_ref = this.table_ref.primary_key?.data_type || ''; if ("varchar" === data_type_ref) { return data_type_ref; } return FK_TYPES[data_type_ref] || "bigint"; } /** * Returns true if the column is a foreign key. * * Returns: * bool: True if it is foreign key. */ public get is_fk(): boolean { return true; } toString() { return `FKColumn(name=${this.name} data_type=${this.data_type} table_ref.name=${this.table_ref.name})`; } }