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, psql runs all input and returns success even if errors occurred
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);