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 //winston.info("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 dbSourceRoot = (isFoundation || isFoundationExtension) ? extension :
100 isLibOrm ? path.join(extension, "source") :
101 path.join(extension, "database/source"),
103 useSafeFoundationToolkit: isFoundation && !isFoundationExtension && extensions.length === 1,
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" : "not-applicable"
116 buildDatabaseUtil.explodeManifest(path.join(dbSourceRoot, "manifest.js"),
117 manifestOptions, extensionCallback);
120 // We also need to get the sql that represents the queries to generate
121 // the XM views from the ORMs. We use the old ORM installer for this,
122 // which has been retooled to return the queryString instead of running
124 var getOrmSql = function (extension, callback) {
125 if (spec.clientOnly) {
129 var ormDir = path.join(extension, "database/orm");
131 if (fs.existsSync(ormDir)) {
132 var updateSpecs = function (err, res) {
136 // if the orm installer has added any new orms we want to know about them
137 // so we can inform the next call to the installer.
138 spec.orms = _.unique(_.union(spec.orms, res.orms), function (orm) {
139 return orm.namespace + orm.type;
141 callback(err, res.query);
143 ormInstaller.run(ormDir, spec, updateSpecs);
145 // No ORM dir? No problem! Nothing to install.
150 // We also need to get the sql that represents the queries to put the
151 // client source in the database.
152 var getClientSql = function (extension, callback) {
153 if (spec.databaseOnly) {
157 clientBuilder.getClientSql(extension, callback);
161 The sql for each extension comprises the sql in the the source directory
162 with the orm sql tacked on to the end. Note that an alternate methodology
163 dictates that *all* source for all extensions should be run before *any*
164 orm queries for any extensions, but that is not the way it works here.
166 var getAllSql = function (extension, masterCallback) {
169 function (callback) {
170 getExtensionSql(extension, callback);
172 function (callback) {
173 if (spec.clientOnly) {
177 dictionaryBuilder.getDictionarySql(extension, callback);
179 function (callback) {
180 getOrmSql(extension, callback);
182 function (callback) {
183 getClientSql(extension, callback);
185 ], function (err, results) {
186 masterCallback(err, _.reduce(results, function (memo, sql) {
194 // Asyncronously run all the functions to all the extension sql for the database,
195 // in series, and execute the query when they all have come back.
197 async.mapSeries(extensions, getAllSql, function (err, extensionSql) {
199 credsClone = JSON.parse(JSON.stringify(creds));
202 databaseCallback(err);
205 // each String of the scriptContents is the concatenated SQL for the extension.
206 // join these all together into a single string for the whole database.
207 allSql = _.reduce(extensionSql, function (memo, script) {
208 return memo + script;
211 // Without this, when we delegate to exec psql the err var will not be set even
212 // on the case of error.
213 allSql = "\\set ON_ERROR_STOP TRUE;\n" + allSql;
215 if (spec.wasInitialized && !_.isEqual(extensions, ["foundation-database"])) {
216 // give the admin user every extension by default
217 allSql = allSql + "insert into xt.usrext (usrext_usr_username, usrext_ext_id) " +
218 "select '" + creds.username +
219 "', ext_id from xt.ext where ext_location = '/core-extensions' and ext_name NOT LIKE 'oauth2';";
222 winston.info("Applying build to database " + spec.database);
223 credsClone.database = spec.database;
224 buildDatabaseUtil.sendToDatabase(allSql, credsClone, spec, function (err, res) {
225 databaseCallback(err, res);
232 // Okay, before we install the database there is ONE thing we need to check,
233 // which is the pre-installed ORMs. Check that now.
235 var preInstallDatabase = function (spec, callback) {
236 var existsSql = "select relname from pg_class where relname = 'orm'",
237 credsClone = JSON.parse(JSON.stringify(creds)),
238 ormTestSql = "select orm_namespace as namespace, " +
239 " orm_type as type " +
241 "where not orm_ext;";
243 credsClone.database = spec.database;
245 dataSource.query(existsSql, credsClone, function (err, res) {
249 if (spec.wipeViews || res.rowCount === 0) {
250 // xt.orm doesn't exist, because this is probably a brand-new DB.
251 // No problem! That just means that there are no pre-existing ORMs.
253 installDatabase(spec, callback);
255 dataSource.query(ormTestSql, credsClone, function (err, res) {
259 spec.orms = res.rows;
260 installDatabase(spec, callback);
267 // Install all the databases
269 async.map(specs, preInstallDatabase, function (err, res) {
271 winston.error(err.message, err.stack, err);
272 if (masterCallback) {
277 winston.info("Success installing all scripts.");
278 winston.info("Cleaning up.");
279 clientBuilder.cleanup(specs, function (err) {
280 if (masterCallback) {
281 masterCallback(err, res);