Skip to content
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

PostgreSQL: Model file writing for tables with same names but different schemas #530

Open
paulkr opened this issue Jun 10, 2021 · 5 comments · May be fixed by #630
Open

PostgreSQL: Model file writing for tables with same names but different schemas #530

paulkr opened this issue Jun 10, 2021 · 5 comments · May be fixed by #630

Comments

@paulkr
Copy link

paulkr commented Jun 10, 2021

When writing your models to files using the option directory, there is no distinction for tables with the same name but different schemas.

Example:

Given the following 2 tables in a database schema1.users and schema2.users, where schema1 and schema2 are different schemas, when creating the models using the file writing option, the initial model file will get overridden as the table names are the same, despite being different tables. So there will be only 1 model file generated.

In the above example, the init-models.js file generated would look like this:

var DataTypes = require("sequelize").DataTypes;
var _users = require("./users");
var _users = require("./users");

function initModels(sequelize) {
  var users = _users(sequelize, DataTypes);
  var users = _users(sequelize, DataTypes);


  return {
    users,
    users,
  };
}
module.exports = initModels;
module.exports.initModels = initModels;
module.exports.default = initModels;
@steveschmitt
Copy link
Collaborator

steveschmitt commented Jun 10, 2021 via email

@paulkr
Copy link
Author

paulkr commented Jun 11, 2021

That would work!

@danielfev
Copy link

danielfev commented Jul 25, 2021

would be nice if the schema and the class prefix with "shema_" this will help a lot cause the foreign keys are also being affected in the init file
example users from public and gpus schemas:

import _sequelize from "sequelize";
const DataTypes = _sequelize.DataTypes;
import _public_users from "./public_users";
import _gpus_users from "./gpus_users";
import _gpus_userstatus from  "./gpus_userstatus.js";

export default function initModels(sequelize) {
  var public_users = _public_users.init(sequelize, DataTypes);
  var gpus_users= _gpus_users.init(sequelize, DataTypes);
  var gpus_userstatus = _gpus_userstatus.init(sequelize, DataTypes);

  gpus_users.belongsTo(gpus_userstatus, { as: "status_gpus_userstatus", foreignKey: "status"});
  gpus_userstatus.hasMany(gpus_users, { as: "gpus_users", foreignKey: "status"});

  return {
    public_users,
    gpus_users,
    gpus_userstatus,
  };
}
import _sequelize from 'sequelize';
const { Model, Sequelize } = _sequelize;

export default class public_users extends Model {
  static init(sequelize, DataTypes) {
  super.init({
    iduser: {
      autoIncrement: true,
      type: DataTypes.INTEGER,
      allowNull: false,
      primaryKey: true
    },
    name: {
      type: DataTypes.TEXT,
      allowNull: true
    },
    lastname: {
      type: DataTypes.TEXT,
      allowNull: true
    }
  }, {
    sequelize,
    tableName: 'users',
    schema: 'public',
    timestamps: true,
    indexes: [
      {
        name: "users_pkey",
        unique: true,
        fields: [
          { name: "iduser" },
        ]
      },
    ]
  });
  return public_users;
  }
}

@danielfev
Copy link

When writing your models to files using the option directory, there is no distinction for tables with the same name but different schemas.

Example:

Given the following 2 tables in a database schema1.users and schema2.users, where schema1 and schema2 are different schemas, when creating the models using the file writing option, the initial model file will get overridden as the table names are the same, despite being different tables. So there will be only 1 model file generated.

In the above example, the init-models.js file generated would look like this:

var DataTypes = require("sequelize").DataTypes;
var _users = require("./users");
var _users = require("./users");

function initModels(sequelize) {
  var users = _users(sequelize, DataTypes);
  var users = _users(sequelize, DataTypes);


  return {
    users,
    users,
  };
}
module.exports = initModels;
module.exports.initModels = initModels;
module.exports.default = initModels;

Hey @paulkr here is a temp fix of this replace the auto-writer.js in your library with the code bellow

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
   return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AutoWriter = void 0;
const fs_1 = __importDefault(require("fs"));
const lodash_1 = __importDefault(require("lodash"));
const path_1 = __importDefault(require("path"));
const util_1 = __importDefault(require("util"));
const sequelize_1 = require("sequelize");
const types_1 = require("./types");
const mkdirp = require('mkdirp');
/** Writes text into files from TableData.text, and writes init-models */
class AutoWriter {
   constructor(tableData, options) {
       this.tableText = tableData.text;
       this.foreignKeys = tableData.foreignKeys;
       this.relations = tableData.relations;
       this.options = options;
   }
   write() {
       if (this.options.noWrite) {
           return Promise.resolve();
       }
       mkdirp.sync(path_1.default.resolve(this.options.directory || "./models"));
       const tables = lodash_1.default.keys(this.tableText);
       // write the individual model files
       const promises = tables.map(t => {
           return this.createFile(t);
       });
       const isTypeScript = this.options.lang === 'ts';
       const assoc = this.createAssociations(isTypeScript);
       // get table names without schema
       // TODO: add schema to model and file names when schema is non-default for the dialect
       const tableNames = tables.map(t => {
           const [schemaName, tableName] = types_1.qNameSplit(t);
           return schemaName+"_"+tableName;
       }).sort();
       // write the init-models file
       if (!this.options.noInitModels) {
           const initString = this.createInitString(tableNames, assoc, this.options.lang);
           const initFilePath = path_1.default.join(this.options.directory, "init-models" + (isTypeScript ? '.ts' : '.js'));
           const writeFile = util_1.default.promisify(fs_1.default.writeFile);
           const initPromise = writeFile(path_1.default.resolve(initFilePath), initString);
           promises.push(initPromise);
       }
       return Promise.all(promises);
   }
   createInitString(tableNames, assoc, lang) {
       switch (lang) {
           case 'ts':
               return this.createTsInitString(tableNames, assoc);
           case 'esm':
               return this.createESMInitString(tableNames, assoc);
           default:
               return this.createES5InitString(tableNames, assoc);
       }
   }
   createFile(table) {
       // FIXME: schema is not used to write the file name and there could be collisions. For now it
       // is up to the developer to pick the right schema, and potentially chose different output
       // folders for each different schema.
       const [schemaName, tableName] = types_1.qNameSplit(table);
       const fileName = types_1.recase(this.options.caseFile, schemaName+"_"+tableName, this.options.singularize);
       const filePath = path_1.default.join(this.options.directory, fileName + (this.options.lang === 'ts' ? '.ts' : '.js'));
       const writeFile = util_1.default.promisify(fs_1.default.writeFile);
       let replacer = new RegExp(" +" + tableName, "g");
       let result = this.tableText[table].replace(replacer, " " + schemaName+"_"+tableName);
       return writeFile(path_1.default.resolve(filePath),result);
   }
   /** Create the belongsToMany/belongsTo/hasMany/hasOne association strings */
   createAssociations(typeScript) {
       let strBelongs = "";
       let strBelongsToMany = "";
       const rels = this.relations;
       rels.forEach(rel => {
           if (rel.isM2M) {
               const asprop = types_1.pluralize(rel.childProp);
               strBelongsToMany += `  ${rel.parentTable.replace(".","_")}.belongsToMany(${rel.childTable.replace(".","_")}, { as: '${asprop}', through: ${rel.joinModel}, foreignKey: "${rel.parentId}", otherKey: "${rel.childId}" });\n`;
           }
           else {
               const bAlias = (this.options.noAlias && rel.parentTable.replace(".","_").toLowerCase() === rel.parentProp.toLowerCase()) ? '' : `as: "${rel.parentProp}", `;
               strBelongs += `  ${rel.childTable.replace(".","_")}.belongsTo(${rel.parentTable.replace(".","_")}, { ${bAlias}foreignKey: "${rel.parentId}"});\n`;
               const hasRel = rel.isOne ? "hasOne" : "hasMany";
               const hAlias = (this.options.noAlias && sequelize_1.Utils.pluralize(rel.childTable.replace(".","_").toLowerCase()) === rel.childProp.toLowerCase()) ? '' : `as: "${rel.childProp}", `;
               strBelongs += `  ${rel.parentTable.replace(".","_")}.${hasRel}(${rel.childTable.replace(".","_")}, { ${hAlias}foreignKey: "${rel.parentId}"});\n`;
           }
       });
       // belongsToMany must come first
       return strBelongsToMany + strBelongs;
   }
   // create the TypeScript init-models file to load all the models into Sequelize
   createTsInitString(tables, assoc) {
       let str = 'import type { Sequelize, Model } from "sequelize";\n';
       const modelNames = [];
       // import statements
       tables.forEach(t => {
           const fileName = types_1.recase(this.options.caseFile, t, this.options.singularize);
           const modelName = types_1.recase(this.options.caseModel, t, this.options.singularize);
           modelNames.push(modelName);
           str += `import { ${modelName} } from "./${fileName}";\n`;
           str += `import type { ${modelName}Attributes, ${modelName}CreationAttributes } from "./${fileName}";\n`;
       });
       // re-export the model classes
       str += '\nexport {\n';
       modelNames.forEach(m => {
           str += `  ${m},\n`;
       });
       str += '};\n';
       // re-export the model attirbutes
       str += '\nexport type {\n';
       modelNames.forEach(m => {
           str += `  ${m}Attributes,\n`;
           str += `  ${m}CreationAttributes,\n`;
       });
       str += '};\n\n';
       // create the initialization function
       str += 'export function initModels(sequelize: Sequelize) {\n';
       modelNames.forEach(m => {
           str += `  ${m}.initModel(sequelize);\n`;
       });
       // add the asociations
       str += "\n" + assoc;
       // return the models
       str += "\n  return {\n";
       modelNames.forEach(m => {
           str += `    ${m}: ${m},\n`;
       });
       str += '  };\n';
       str += '}\n';
       return str;
   }
   // create the ES5 init-models file to load all the models into Sequelize
   createES5InitString(tables, assoc) {
       let str = 'var DataTypes = require("sequelize").DataTypes;\n';
       const modelNames = [];
       // import statements
       tables.forEach(t => {
           const fileName = types_1.recase(this.options.caseFile, t, this.options.singularize);
           const modelName = types_1.recase(this.options.caseModel, t, this.options.singularize);
           modelNames.push(modelName);
           str += `var _${modelName} = require("./${fileName}");\n`;
       });
       // create the initialization function
       str += '\nfunction initModels(sequelize) {\n';
       modelNames.forEach(m => {
           str += `  var ${m} = _${m}(sequelize, DataTypes);\n`;
       });
       // add the asociations
       str += "\n" + assoc;
       // return the models
       str += "\n  return {\n";
       modelNames.forEach(m => {
           str += `    ${m},\n`;
       });
       str += '  };\n';
       str += '}\n';
       str += 'module.exports = initModels;\n';
       str += 'module.exports.initModels = initModels;\n';
       str += 'module.exports.default = initModels;\n';
       return str;
   }
   // create the ESM init-models file to load all the models into Sequelize
   createESMInitString(tables, assoc) {
       let str = 'import _sequelize from "sequelize";\n';
       str += 'const DataTypes = _sequelize.DataTypes;\n';
       const modelNames = [];
       // import statements
       tables.forEach(t => {
           const fileName = types_1.recase(this.options.caseFile, t, this.options.singularize);
           const modelName = types_1.recase(this.options.caseModel, t, this.options.singularize);
           modelNames.push(modelName);
           str += `import _${modelName} from  "./${fileName}.js";\n`;
       });
       // create the initialization function
       str += '\nexport default function initModels(sequelize) {\n';
       modelNames.forEach(m => {
           str += `  var ${m} = _${m}.init(sequelize, DataTypes);\n`;
       });
       // add the asociations
       str += "\n" + assoc;
       // return the models
       str += "\n  return {\n";
       modelNames.forEach(m => {
           str += `    ${m},\n`;
       });
       str += '  };\n';
       str += '}\n';
       return str;
   }
}
exports.AutoWriter = AutoWriter;
//# sourceMappingURL=auto-writer.js.map

@mat813
Copy link
Sponsor Contributor

mat813 commented Jul 18, 2022

Ah, I was about to open an issue with a similar request, I am writing a large app with many schemas, and I also have tables with the same name in different schemas.

I am trying to merge the auto-writer in the last comment with the current one.

@mat813 mat813 linked a pull request Dec 6, 2022 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants