1 /*jshint node:true, indent:2, curly:false, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
2 regexp:true, undef:true, strict:true, trailing:true, white:true */
3 /*global X:true, Backbone:true, _:true, XM:true, XT:true*/
5 _ = require('underscore');
7 var async = require('async'),
8 dataSource = require('../../node-datasource/lib/ext/datasource').dataSource,
9 buildDatabaseUtil = require('./build_database_util'),
10 exec = require('child_process').exec,
12 ormInstaller = require('./orm'),
13 dictionaryBuilder = require('./build_dictionary'),
14 clientBuilder = require('./build_client'),
15 path = require('path'),
18 winston = require('winston');
24 @param {Object} specs Specification for the build process, in the form:
26 [ '/home/user/git/xtuple/enyo-client',
27 '/home/user/git/xtuple/enyo-client/extensions/source/crm',
28 '/home/user/git/xtuple/enyo-client/extensions/source/sales',
29 '/home/user/git/private-extensions/source/incident_plus' ],
33 [ '/home/user/git/xtuple/enyo-client',
34 '/home/user/git/xtuple/enyo-client/extensions/source/sales',
35 '/home/user/git/xtuple/enyo-client/extensions/source/project' ],
39 @param {Object} creds Database credentials, in the form:
40 { hostname: 'localhost',
46 var buildDatabase = exports.buildDatabase = function (specs, creds, masterCallback) {
48 // The function to generate all the scripts for a database
50 var installDatabase = function (spec, databaseCallback) {
51 var extensions = spec.extensions,
52 databaseName = spec.database;
55 // The function to install all the scripts for an extension
57 var getExtensionSql = function (extension, extensionCallback) {
58 if (spec.clientOnly) {
59 extensionCallback(null, "");
62 // deal with directory structure quirks
63 var baseName = path.basename(extension),
64 isFoundation = extension.indexOf("foundation-database") >= 0,
65 isFoundationExtension = extension.indexOf("inventory/foundation-database") >= 0 ||
66 extension.indexOf("manufacturing/foundation-database") >= 0 ||
67 extension.indexOf("distribution/foundation-database") >= 0,
68 isLibOrm = extension.indexOf("lib/orm") >= 0,
69 isApplicationCore = extension.indexOf("enyo-client") >= 0 &&
70 extension.indexOf("extension") < 0,
71 isCoreExtension = extension.indexOf("enyo-client") >= 0 &&
72 extension.indexOf("extension") >= 0,
73 isPublicExtension = extension.indexOf("xtuple-extensions") >= 0,
74 isPrivateExtension = extension.indexOf("private-extensions") >= 0,
75 isNpmExtension = baseName.indexOf("xtuple-") >= 0,
76 dbSourceRoot = (isFoundation || isFoundationExtension) ? extension :
77 isLibOrm ? path.join(extension, "source") :
78 path.join(extension, "database/source"),
80 useFrozenScripts: spec.frozen,
81 useFoundationScripts: baseName.indexOf('inventory') >= 0 ||
82 baseName.indexOf('manufacturing') >= 0 ||
83 baseName.indexOf('distribution') >= 0,
84 registerExtension: !isFoundation && !isLibOrm && !isApplicationCore,
85 runJsInit: !isFoundation && !isLibOrm,
86 wipeViews: isApplicationCore && spec.wipeViews,
87 extensionLocation: isCoreExtension ? "/core-extensions" :
88 isPublicExtension ? "/xtuple-extensions" :
89 isPrivateExtension ? "/private-extensions" :
90 isNpmExtension ? "npm" : "not-applicable"
93 buildDatabaseUtil.explodeManifest(path.join(dbSourceRoot, "manifest.js"),
94 manifestOptions, extensionCallback);
97 // We also need to get the sql that represents the queries to generate
98 // the XM views from the ORMs. We use the old ORM installer for this,
99 // which has been retooled to return the queryString instead of running
101 var getOrmSql = function (extension, callback) {
102 if (spec.clientOnly) {
106 var ormDir = path.join(extension, "database/orm");
108 if (fs.existsSync(ormDir)) {
109 var updateSpecs = function (err, res) {
113 // if the orm installer has added any new orms we want to know about them
114 // so we can inform the next call to the installer.
115 spec.orms = _.unique(_.union(spec.orms, res.orms), function (orm) {
116 return orm.namespace + orm.type;
118 callback(err, res.query);
120 ormInstaller.run(ormDir, spec, updateSpecs);
122 // No ORM dir? No problem! Nothing to install.
127 // We also need to get the sql that represents the queries to put the
128 // client source in the database.
129 var getClientSql = function (extension, callback) {
130 if (spec.databaseOnly) {
134 clientBuilder.getClientSql(extension, callback);
138 The sql for each extension comprises the sql in the the source directory
139 with the orm sql tacked on to the end. Note that an alternate methodology
140 dictates that *all* source for all extensions should be run before *any*
141 orm queries for any extensions, but that is not the way it works here.
143 var getAllSql = function (extension, masterCallback) {
146 function (callback) {
147 getExtensionSql(extension, callback);
149 function (callback) {
150 if (spec.clientOnly) {
154 dictionaryBuilder.getDictionarySql(extension, callback);
156 function (callback) {
157 getOrmSql(extension, callback);
159 function (callback) {
160 getClientSql(extension, callback);
162 ], function (err, results) {
163 masterCallback(err, _.reduce(results, function (memo, sql) {
171 // Asyncronously run all the functions to all the extension sql for the database,
172 // in series, and execute the query when they all have come back.
174 async.mapSeries(extensions, getAllSql, function (err, extensionSql) {
176 credsClone = JSON.parse(JSON.stringify(creds));
179 databaseCallback(err);
182 // each String of the scriptContents is the concatenated SQL for the extension.
183 // join these all together into a single string for the whole database.
184 allSql = _.reduce(extensionSql, function (memo, script) {
185 return memo + script;
188 // Without this, when we delegate to exec psql the err var will not be set even
189 // on the case of error.
190 allSql = "\\set ON_ERROR_STOP TRUE;\n" + allSql;
192 if (spec.wasInitialized && !_.isEqual(extensions, ["foundation-database"])) {
193 // give the admin user every extension by default
194 allSql = allSql + "insert into xt.usrext (usrext_usr_username, usrext_ext_id) " +
195 "select '" + creds.username +
196 "', ext_id from xt.ext where ext_location = '/core-extensions' and ext_name NOT LIKE 'oauth2';";
199 winston.info("Applying build to database " + spec.database);
200 credsClone.database = spec.database;
201 buildDatabaseUtil.sendToDatabase(allSql, credsClone, spec, function (err, res) {
202 if (spec.populateData && creds.encryptionKeyFile) {
203 var populateSql = "DO $$ XT.disableLocks = true; $$ language plv8;";
204 var encryptionKey = fs.readFileSync(path.resolve(__dirname, "../../node-datasource", creds.encryptionKeyFile), "utf8");
205 var patches = require(path.join(__dirname, "../../enyo-client/database/source/populate_data")).patches;
206 _.each(patches, function (patch) {
207 patch.encryptionKey = encryptionKey;
208 patch.username = creds.username;
209 populateSql += "select xt.patch(\'" + JSON.stringify(patch) + "\');";
211 populateSql += "DO $$ XT.disableLocks = undefined; $$ language plv8;";
212 dataSource.query(populateSql, credsClone, databaseCallback);
214 databaseCallback(err, res);
222 // Okay, before we install the database there is ONE thing we need to check,
223 // which is the pre-installed ORMs. Check that now.
225 var preInstallDatabase = function (spec, callback) {
226 var existsSql = "select relname from pg_class where relname = 'orm'",
227 credsClone = JSON.parse(JSON.stringify(creds)),
228 ormTestSql = "select orm_namespace as namespace, " +
229 " orm_type as type " +
231 "where not orm_ext;";
233 credsClone.database = spec.database;
235 dataSource.query(existsSql, credsClone, function (err, res) {
239 if (spec.wipeViews || res.rowCount === 0) {
240 // xt.orm doesn't exist, because this is probably a brand-new DB.
241 // No problem! That just means that there are no pre-existing ORMs.
243 installDatabase(spec, callback);
245 dataSource.query(ormTestSql, credsClone, function (err, res) {
249 spec.orms = res.rows;
250 installDatabase(spec, callback);
257 // Install all the databases
259 async.map(specs, preInstallDatabase, function (err, res) {
261 winston.error(err.message, err.stack, err);
262 if (masterCallback) {
267 winston.info("Success installing all scripts.");
268 winston.info("Cleaning up.");
269 clientBuilder.cleanup(specs, function (err) {
270 if (masterCallback) {
271 masterCallback(err, res);