sequelize-auto icon indicating copy to clipboard operation
sequelize-auto copied to clipboard

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

Open paulkr opened this issue 3 years ago • 5 comments

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;

paulkr avatar Jun 10 '21 15:06 paulkr

Should we prefix the filename with the schema name?

On Thu, Jun 10, 2021, 8:50 AM Paul K. @.***> wrote:

When programmatically 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;

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/sequelize/sequelize-auto/issues/530, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAMYFNWN7XPDJRQ2WUXB2BTTSDNNZANCNFSM46OZUC7A .

steveschmitt avatar Jun 10 '21 17:06 steveschmitt

That would work!

paulkr avatar Jun 11 '21 14:06 paulkr

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 avatar Jul 25 '21 14:07 danielfev

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

danielfev avatar Jul 25 '21 19:07 danielfev

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 avatar Jul 18 '22 12:07 mat813