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: isFoundation && spec.wipeViews,
93 wipeOrms: isApplicationCore && spec.wipeViews,
94 extensionLocation: isCoreExtension ? "/core-extensions" :
95 isPublicExtension ? "/xtuple-extensions" :
96 isPrivateExtension ? "/private-extensions" :
97 isNpmExtension ? "npm" : "not-applicable"
100 explodeManifest(manifestOptions, extensionCallback);
103 // We also need to get the sql that represents the queries to generate
104 // the XM views from the ORMs. We use the old ORM installer for this,
105 // which has been retooled to return the queryString instead of running
107 var getOrmSql = function (extension, callback) {
108 if (spec.clientOnly) {
112 var ormDir = path.join(extension, "database/orm");
114 if (fs.existsSync(ormDir)) {
115 var updateSpecs = function (err, res) {
119 // if the orm installer has added any new orms we want to know about them
120 // so we can inform the next call to the installer.
121 spec.orms = _.unique(_.union(spec.orms, res.orms), function (orm) {
122 return orm.namespace + orm.type;
124 callback(err, res.query);
126 ormInstaller.run(ormDir, spec, updateSpecs);
128 // No ORM dir? No problem! Nothing to install.
133 // We also need to get the sql that represents the queries to put the
134 // client source in the database.
135 var getClientSql = function (extension, callback) {
136 if (spec.databaseOnly) {
140 clientBuilder.getClientSql(extension, callback);
144 The sql for each extension comprises the sql in the the source directory
145 with the orm sql tacked on to the end. Note that an alternate methodology
146 dictates that *all* source for all extensions should be run before *any*
147 orm queries for any extensions, but that is not the way it works here.
149 var getAllSql = function (extension, masterCallback) {
152 function (callback) {
153 getExtensionSql(extension, callback);
155 function (callback) {
156 if (spec.clientOnly) {
160 dictionaryBuilder.getDictionarySql(extension, callback);
162 function (callback) {
163 getOrmSql(extension, callback);
165 function (callback) {
166 getClientSql(extension, callback);
168 ], function (err, results) {
169 masterCallback(err, _.reduce(results, function (memo, sql) {
177 // Asyncronously run all the functions to all the extension sql for the database,
178 // in series, and execute the query when they all have come back.
180 async.mapSeries(extensions, getAllSql, function (err, extensionSql) {
182 credsClone = JSON.parse(JSON.stringify(creds));
185 databaseCallback(err);
188 // each String of the scriptContents is the concatenated SQL for the extension.
189 // join these all together into a single string for the whole database.
190 allSql = _.reduce(extensionSql, function (memo, script) {
191 return memo + script;
194 // Without this, when we delegate to exec psql the err var will not be set even
195 // on the case of error.
196 allSql = "\\set ON_ERROR_STOP TRUE;\n" + allSql;
198 winston.info("Applying build to database " + spec.database);
199 credsClone.database = spec.database;
200 sendToDatabase(allSql, credsClone, spec, function (err, res) {
201 if (spec.populateData && creds.encryptionKeyFile) {
202 var populateSql = "DO $$ XT.disableLocks = true; $$ language plv8;";
203 var encryptionKey = fs.readFileSync(path.resolve(__dirname, "../../node-datasource", creds.encryptionKeyFile), "utf8");
204 var patches = require(path.join(__dirname, "../../enyo-client/database/source/populate_data")).patches;
205 _.each(patches, function (patch) {
206 patch.encryptionKey = encryptionKey;
207 patch.username = creds.username;
208 populateSql += "select xt.patch(\'" + JSON.stringify(patch) + "\');";
210 populateSql += "DO $$ XT.disableLocks = undefined; $$ language plv8;";
211 dataSource.query(populateSql, credsClone, databaseCallback);
213 databaseCallback(err, res);
221 // Okay, before we install the database there is ONE thing we need to check,
222 // which is the pre-installed ORMs. Check that now.
224 var preInstallDatabase = function (spec, callback) {
225 var existsSql = "select relname from pg_class where relname = 'orm'",
226 credsClone = JSON.parse(JSON.stringify(creds)),
227 ormTestSql = "select orm_namespace as namespace, " +
228 " orm_type as type " +
230 "where not orm_ext;";
232 credsClone.database = spec.database;
234 dataSource.query(existsSql, credsClone, function (err, res) {
238 if (spec.wipeViews || res.rowCount === 0) {
239 // xt.orm doesn't exist, because this is probably a brand-new DB.
240 // No problem! That just means that there are no pre-existing ORMs.
242 installDatabase(spec, callback);
244 dataSource.query(ormTestSql, credsClone, function (err, res) {
248 spec.orms = res.rows;
249 installDatabase(spec, callback);
256 // Install all the databases
258 async.map(specs, preInstallDatabase, function (err, res) {
260 winston.error(err.message, err.stack, err);
261 if (masterCallback) {
266 winston.info("Success installing all scripts.");
267 winston.info("Cleaning up.");
268 clientBuilder.cleanup(specs, function (err) {
269 if (masterCallback) {
270 masterCallback(err, res);