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, psql runs all input and returns success even if errors occurred
194 allSql = "\\set ON_ERROR_STOP TRUE\n" + allSql;
196 winston.info("Applying build to database " + spec.database);
197 credsClone.database = spec.database;
198 sendToDatabase(allSql, credsClone, spec, function (err, res) {
199 if (spec.populateData && creds.encryptionKeyFile) {
200 var populateSql = "DO $$ XT.disableLocks = true; $$ language plv8;";
201 var encryptionKey = fs.readFileSync(path.resolve(__dirname, "../../node-datasource", creds.encryptionKeyFile), "utf8");
202 var patches = require(path.join(__dirname, "../../enyo-client/database/source/populate_data")).patches;
203 _.each(patches, function (patch) {
204 patch.encryptionKey = encryptionKey;
205 patch.username = creds.username;
206 populateSql += "select xt.patch(\'" + JSON.stringify(patch) + "\');";
208 populateSql += "DO $$ XT.disableLocks = undefined; $$ language plv8;";
209 dataSource.query(populateSql, credsClone, databaseCallback);
211 databaseCallback(err, res);
219 // Okay, before we install the database there is ONE thing we need to check,
220 // which is the pre-installed ORMs. Check that now.
222 var preInstallDatabase = function (spec, callback) {
223 var existsSql = "select relname from pg_class where relname = 'orm'",
224 credsClone = JSON.parse(JSON.stringify(creds)),
225 ormTestSql = "select orm_namespace as namespace, " +
226 " orm_type as type " +
228 "where not orm_ext;";
230 credsClone.database = spec.database;
232 dataSource.query(existsSql, credsClone, function (err, res) {
236 if (spec.wipeViews || res.rowCount === 0) {
237 // xt.orm doesn't exist, because this is probably a brand-new DB.
238 // No problem! That just means that there are no pre-existing ORMs.
240 installDatabase(spec, callback);
242 dataSource.query(ormTestSql, credsClone, function (err, res) {
246 spec.orms = res.rows;
247 installDatabase(spec, callback);
254 // Install all the databases
256 async.map(specs, preInstallDatabase, function (err, res) {
258 winston.error(err.message, err.stack, err);
259 if (masterCallback) {
264 winston.info("Success installing all scripts.");
265 winston.info("Cleaning up.");
266 clientBuilder.cleanup(specs, function (err) {
267 if (masterCallback) {
268 masterCallback(err, res);