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) {
47 if (specs.length === 1 &&
48 specs[0].initialize &&
49 (specs[0].backup || specs[0].source)) {
51 // The user wants to initialize the database first (i.e. Step 0)
52 // Do that, then call this function again
53 buildDatabaseUtil.initDatabase(specs[0], creds, function (err, res) {
55 winston.error("Init database error: ", err);
59 // recurse to do the build step. Of course we don't want to initialize a second
60 // time, so destroy those flags.
61 specs[0].initialize = false;
62 specs[0].wasInitialized = true;
63 specs[0].backup = undefined;
64 specs[0].source = undefined;
65 buildDatabase(specs, creds, masterCallback);
71 // The function to generate all the scripts for a database
73 var installDatabase = function (spec, databaseCallback) {
74 var extensions = spec.extensions,
75 databaseName = spec.database;
78 // The function to install all the scripts for an extension
80 var getExtensionSql = function (extension, extensionCallback) {
81 if (spec.clientOnly) {
82 extensionCallback(null, "");
85 //console.log("Installing extension", databaseName, extension);
86 // deal with directory structure quirks
87 var baseName = path.basename(extension),
88 isFoundation = extension.indexOf("foundation-database") >= 0,
89 isFoundationExtension = extension.indexOf("inventory/foundation-database") >= 0 ||
90 extension.indexOf("manufacturing/foundation-database") >= 0 ||
91 extension.indexOf("distribution/foundation-database") >= 0,
92 isLibOrm = extension.indexOf("lib/orm") >= 0,
93 isApplicationCore = extension.indexOf("enyo-client") >= 0 &&
94 extension.indexOf("extension") < 0,
95 isCoreExtension = extension.indexOf("enyo-client") >= 0 &&
96 extension.indexOf("extension") >= 0,
97 isPublicExtension = extension.indexOf("xtuple-extensions") >= 0,
98 isPrivateExtension = extension.indexOf("private-extensions") >= 0,
99 isNpmExtension = baseName.indexOf("xtuple-") >= 0,
100 dbSourceRoot = (isFoundation || isFoundationExtension) ? extension :
101 isLibOrm ? path.join(extension, "source") :
102 path.join(extension, "database/source"),
104 useFrozenScripts: spec.frozen,
105 useFoundationScripts: baseName.indexOf('inventory') >= 0 ||
106 baseName.indexOf('manufacturing') >= 0 ||
107 baseName.indexOf('distribution') >= 0,
108 registerExtension: !isFoundation && !isLibOrm && !isApplicationCore,
109 runJsInit: !isFoundation && !isLibOrm,
110 wipeViews: isApplicationCore && spec.wipeViews,
111 extensionLocation: isCoreExtension ? "/core-extensions" :
112 isPublicExtension ? "/xtuple-extensions" :
113 isPrivateExtension ? "/private-extensions" :
114 isNpmExtension ? "npm" : "not-applicable"
117 buildDatabaseUtil.explodeManifest(path.join(dbSourceRoot, "manifest.js"),
118 manifestOptions, extensionCallback);
121 // We also need to get the sql that represents the queries to generate
122 // the XM views from the ORMs. We use the old ORM installer for this,
123 // which has been retooled to return the queryString instead of running
125 var getOrmSql = function (extension, callback) {
126 if (spec.clientOnly) {
130 var ormDir = path.join(extension, "database/orm");
132 if (fs.existsSync(ormDir)) {
133 var updateSpecs = function (err, res) {
137 // if the orm installer has added any new orms we want to know about them
138 // so we can inform the next call to the installer.
139 spec.orms = _.unique(_.union(spec.orms, res.orms), function (orm) {
140 return orm.namespace + orm.type;
142 callback(err, res.query);
144 ormInstaller.run(ormDir, spec, updateSpecs);
146 // No ORM dir? No problem! Nothing to install.
151 // We also need to get the sql that represents the queries to put the
152 // client source in the database.
153 var getClientSql = function (extension, callback) {
154 if (spec.databaseOnly) {
158 clientBuilder.getClientSql(extension, callback);
162 The sql for each extension comprises the sql in the the source directory
163 with the orm sql tacked on to the end. Note that an alternate methodology
164 dictates that *all* source for all extensions should be run before *any*
165 orm queries for any extensions, but that is not the way it works here.
167 var getAllSql = function (extension, masterCallback) {
170 function (callback) {
171 getExtensionSql(extension, callback);
173 function (callback) {
174 if (spec.clientOnly) {
178 dictionaryBuilder.getDictionarySql(extension, callback);
180 function (callback) {
181 getOrmSql(extension, callback);
183 function (callback) {
184 getClientSql(extension, callback);
186 ], function (err, results) {
187 masterCallback(err, _.reduce(results, function (memo, sql) {
195 // Asyncronously run all the functions to all the extension sql for the database,
196 // in series, and execute the query when they all have come back.
198 async.mapSeries(extensions, getAllSql, function (err, extensionSql) {
200 credsClone = JSON.parse(JSON.stringify(creds));
203 databaseCallback(err);
206 // each String of the scriptContents is the concatenated SQL for the extension.
207 // join these all together into a single string for the whole database.
208 allSql = _.reduce(extensionSql, function (memo, script) {
209 return memo + script;
212 // Without this, when we delegate to exec psql the err var will not be set even
213 // on the case of error.
214 allSql = "\\set ON_ERROR_STOP TRUE;\n" + allSql;
216 if (spec.wasInitialized && !_.isEqual(extensions, ["foundation-database"])) {
217 // give the admin user every extension by default
218 allSql = allSql + "insert into xt.usrext (usrext_usr_username, usrext_ext_id) " +
219 "select '" + creds.username +
220 "', ext_id from xt.ext where ext_location = '/core-extensions' and ext_name NOT LIKE 'oauth2';";
223 winston.info("Applying build to database " + spec.database);
224 credsClone.database = spec.database;
225 buildDatabaseUtil.sendToDatabase(allSql, credsClone, spec, function (err, res) {
226 if (spec.populateData && creds.encryptionKeyFile) {
227 var populateSql = "DO $$ XT.disableLocks = true; $$ language plv8;";
228 var encryptionKey = fs.readFileSync(path.resolve(__dirname, "../../node-datasource", creds.encryptionKeyFile), "utf8");
229 var patches = require(path.join(__dirname, "../../enyo-client/database/source/populate_data")).patches;
230 _.each(patches, function (patch) {
231 patch.encryptionKey = encryptionKey;
232 patch.username = creds.username;
233 populateSql += "select xt.patch(\'" + JSON.stringify(patch) + "\');";
235 populateSql += "DO $$ XT.disableLocks = undefined; $$ language plv8;";
236 dataSource.query(populateSql, credsClone, databaseCallback);
238 databaseCallback(err, res);
246 // Okay, before we install the database there is ONE thing we need to check,
247 // which is the pre-installed ORMs. Check that now.
249 var preInstallDatabase = function (spec, callback) {
250 var existsSql = "select relname from pg_class where relname = 'orm'",
251 credsClone = JSON.parse(JSON.stringify(creds)),
252 ormTestSql = "select orm_namespace as namespace, " +
253 " orm_type as type " +
255 "where not orm_ext;";
257 credsClone.database = spec.database;
259 dataSource.query(existsSql, credsClone, function (err, res) {
263 if (spec.wipeViews || res.rowCount === 0) {
264 // xt.orm doesn't exist, because this is probably a brand-new DB.
265 // No problem! That just means that there are no pre-existing ORMs.
267 installDatabase(spec, callback);
269 dataSource.query(ormTestSql, credsClone, function (err, res) {
273 spec.orms = res.rows;
274 installDatabase(spec, callback);
281 // Install all the databases
283 async.map(specs, preInstallDatabase, function (err, res) {
285 winston.error(err.message, err.stack, err);
286 if (masterCallback) {
291 winston.info("Success installing all scripts.");
292 winston.info("Cleaning up.");
293 clientBuilder.cleanup(specs, function (err) {
294 if (masterCallback) {
295 masterCallback(err, res);