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 exec = require('child_process').exec,
10 explodeManifest = require("./util/process_manifest").explodeManifest,
12 ormInstaller = require('./orm'),
13 dictionaryBuilder = require('./build_dictionary'),
14 clientBuilder = require('./build_client'),
15 path = require('path'),
18 sendToDatabase = require("./util/send_to_database").sendToDatabase,
19 winston = require('winston');
25 @param {Object} specs Specification for the build process, in the form:
27 [ '/home/user/git/xtuple/enyo-client',
28 '/home/user/git/xtuple/enyo-client/extensions/source/crm',
29 '/home/user/git/xtuple/enyo-client/extensions/source/sales',
30 '/home/user/git/private-extensions/source/incident_plus' ],
34 [ '/home/user/git/xtuple/enyo-client',
35 '/home/user/git/xtuple/enyo-client/extensions/source/sales',
36 '/home/user/git/xtuple/enyo-client/extensions/source/project' ],
40 @param {Object} creds Database credentials, in the form:
41 { hostname: 'localhost',
47 var buildDatabase = exports.buildDatabase = function (specs, creds, masterCallback) {
49 // The function to generate all the scripts for a database
51 var installDatabase = function (spec, databaseCallback) {
52 var extensions = spec.extensions,
53 databaseName = spec.database;
56 // The function to install all the scripts for an extension
58 var getExtensionSql = function (extension, extensionCallback) {
59 if (spec.clientOnly) {
60 extensionCallback(null, "");
63 // deal with directory structure quirks
64 var baseName = path.basename(extension),
65 isFoundation = extension.indexOf("foundation-database") >= 0,
66 isFoundationExtension = extension.indexOf("inventory/foundation-database") >= 0 ||
67 extension.indexOf("manufacturing/foundation-database") >= 0 ||
68 extension.indexOf("distribution/foundation-database") >= 0,
69 isLibOrm = extension.indexOf("lib/orm") >= 0,
70 isApplicationCore = extension.indexOf("enyo-client") >= 0 &&
71 extension.indexOf("extension") < 0,
72 isCoreExtension = extension.indexOf("enyo-client") >= 0 &&
73 extension.indexOf("extension") >= 0,
74 isPublicExtension = extension.indexOf("xtuple-extensions") >= 0,
75 isPrivateExtension = extension.indexOf("private-extensions") >= 0,
76 isNpmExtension = baseName.indexOf("xtuple-") >= 0,
77 isExtension = !isFoundation && !isLibOrm && !isApplicationCore,
78 dbSourceRoot = (isFoundation || isFoundationExtension) ? extension :
79 isLibOrm ? path.join(extension, "source") :
80 path.join(extension, "database/source"),
82 manifestFilename: path.resolve(dbSourceRoot, "manifest.js"),
83 extensionPath: isExtension ?
84 path.resolve(dbSourceRoot, "../../") :
86 useFrozenScripts: spec.frozen,
87 useFoundationScripts: baseName.indexOf('inventory') >= 0 ||
88 baseName.indexOf('manufacturing') >= 0 ||
89 baseName.indexOf('distribution') >= 0,
90 registerExtension: isExtension,
91 runJsInit: !isFoundation && !isLibOrm,
92 wipeViews: isApplicationCore && spec.wipeViews,
93 extensionLocation: isCoreExtension ? "/core-extensions" :
94 isPublicExtension ? "/xtuple-extensions" :
95 isPrivateExtension ? "/private-extensions" :
96 isNpmExtension ? "npm" : "not-applicable"
99 explodeManifest(manifestOptions, extensionCallback);
102 // We also need to get the sql that represents the queries to generate
103 // the XM views from the ORMs. We use the old ORM installer for this,
104 // which has been retooled to return the queryString instead of running
106 var getOrmSql = function (extension, callback) {
107 if (spec.clientOnly) {
111 var ormDir = path.join(extension, "database/orm");
113 if (fs.existsSync(ormDir)) {
114 var updateSpecs = function (err, res) {
118 // if the orm installer has added any new orms we want to know about them
119 // so we can inform the next call to the installer.
120 spec.orms = _.unique(_.union(spec.orms, res.orms), function (orm) {
121 return orm.namespace + orm.type;
123 callback(err, res.query);
125 ormInstaller.run(ormDir, spec, updateSpecs);
127 // No ORM dir? No problem! Nothing to install.
132 // We also need to get the sql that represents the queries to put the
133 // client source in the database.
134 var getClientSql = function (extension, callback) {
135 if (spec.databaseOnly) {
139 clientBuilder.getClientSql(extension, callback);
143 The sql for each extension comprises the sql in the the source directory
144 with the orm sql tacked on to the end. Note that an alternate methodology
145 dictates that *all* source for all extensions should be run before *any*
146 orm queries for any extensions, but that is not the way it works here.
148 var getAllSql = function (extension, masterCallback) {
151 function (callback) {
152 getExtensionSql(extension, callback);
154 function (callback) {
155 if (spec.clientOnly) {
159 dictionaryBuilder.getDictionarySql(extension, callback);
161 function (callback) {
162 getOrmSql(extension, callback);
164 function (callback) {
165 getClientSql(extension, callback);
167 ], function (err, results) {
168 masterCallback(err, _.reduce(results, function (memo, sql) {
176 // Asyncronously run all the functions to all the extension sql for the database,
177 // in series, and execute the query when they all have come back.
179 async.mapSeries(extensions, getAllSql, function (err, extensionSql) {
181 credsClone = JSON.parse(JSON.stringify(creds));
184 databaseCallback(err);
187 // each String of the scriptContents is the concatenated SQL for the extension.
188 // join these all together into a single string for the whole database.
189 allSql = _.reduce(extensionSql, function (memo, script) {
190 return memo + script;
193 // Without this, when we delegate to exec psql the err var will not be set even
194 // on the case of error.
195 allSql = "\\set ON_ERROR_STOP TRUE;\n" + allSql;
197 winston.info("Applying build to database " + spec.database);
198 credsClone.database = spec.database;
199 sendToDatabase(allSql, credsClone, spec, function (err, res) {
200 if (spec.populateData && creds.encryptionKeyFile) {
201 var populateSql = "DO $$ XT.disableLocks = true; $$ language plv8;";
202 var encryptionKey = fs.readFileSync(path.resolve(__dirname, "../../node-datasource", creds.encryptionKeyFile), "utf8");
203 var patches = require(path.join(__dirname, "../../enyo-client/database/source/populate_data")).patches;
204 _.each(patches, function (patch) {
205 patch.encryptionKey = encryptionKey;
206 patch.username = creds.username;
207 populateSql += "select xt.patch(\'" + JSON.stringify(patch) + "\');";
209 populateSql += "DO $$ XT.disableLocks = undefined; $$ language plv8;";
210 dataSource.query(populateSql, credsClone, databaseCallback);
212 databaseCallback(err, res);
220 // Okay, before we install the database there is ONE thing we need to check,
221 // which is the pre-installed ORMs. Check that now.
223 var preInstallDatabase = function (spec, callback) {
224 var existsSql = "select relname from pg_class where relname = 'orm'",
225 credsClone = JSON.parse(JSON.stringify(creds)),
226 ormTestSql = "select orm_namespace as namespace, " +
227 " orm_type as type " +
229 "where not orm_ext;";
231 credsClone.database = spec.database;
233 dataSource.query(existsSql, credsClone, function (err, res) {
237 if (spec.wipeViews || res.rowCount === 0) {
238 // xt.orm doesn't exist, because this is probably a brand-new DB.
239 // No problem! That just means that there are no pre-existing ORMs.
241 installDatabase(spec, callback);
243 dataSource.query(ormTestSql, credsClone, function (err, res) {
247 spec.orms = res.rows;
248 installDatabase(spec, callback);
255 // Install all the databases
257 async.map(specs, preInstallDatabase, function (err, res) {
259 winston.error(err.message, err.stack, err);
260 if (masterCallback) {
265 winston.info("Success installing all scripts.");
266 winston.info("Cleaning up.");
267 clientBuilder.cleanup(specs, function (err) {
268 if (masterCallback) {
269 masterCallback(err, res);