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 useFrozenScripts: spec.frozen,
104 useFoundationScripts: baseName.indexOf('inventory') >= 0 ||
105 baseName.indexOf('manufacturing') >= 0 ||
106 baseName.indexOf('distribution') >= 0,
107 registerExtension: !isFoundation && !isLibOrm && !isApplicationCore,
108 runJsInit: !isFoundation && !isLibOrm,
109 wipeViews: isApplicationCore && spec.wipeViews,
110 extensionLocation: isCoreExtension ? "/core-extensions" :
111 isPublicExtension ? "/xtuple-extensions" :
112 isPrivateExtension ? "/private-extensions" : "not-applicable"
115 buildDatabaseUtil.explodeManifest(path.join(dbSourceRoot, "manifest.js"),
116 manifestOptions, extensionCallback);
119 // We also need to get the sql that represents the queries to generate
120 // the XM views from the ORMs. We use the old ORM installer for this,
121 // which has been retooled to return the queryString instead of running
123 var getOrmSql = function (extension, callback) {
124 if (spec.clientOnly) {
128 var ormDir = path.join(extension, "database/orm");
130 if (fs.existsSync(ormDir)) {
131 var updateSpecs = function (err, res) {
135 // if the orm installer has added any new orms we want to know about them
136 // so we can inform the next call to the installer.
137 spec.orms = _.unique(_.union(spec.orms, res.orms), function (orm) {
138 return orm.namespace + orm.type;
140 callback(err, res.query);
142 ormInstaller.run(ormDir, spec, updateSpecs);
144 // No ORM dir? No problem! Nothing to install.
149 // We also need to get the sql that represents the queries to put the
150 // client source in the database.
151 var getClientSql = function (extension, callback) {
152 if (spec.databaseOnly) {
156 clientBuilder.getClientSql(extension, callback);
160 The sql for each extension comprises the sql in the the source directory
161 with the orm sql tacked on to the end. Note that an alternate methodology
162 dictates that *all* source for all extensions should be run before *any*
163 orm queries for any extensions, but that is not the way it works here.
165 var getAllSql = function (extension, masterCallback) {
168 function (callback) {
169 getExtensionSql(extension, callback);
171 function (callback) {
172 if (spec.clientOnly) {
176 dictionaryBuilder.getDictionarySql(extension, callback);
178 function (callback) {
179 getOrmSql(extension, callback);
181 function (callback) {
182 getClientSql(extension, callback);
184 ], function (err, results) {
185 masterCallback(err, _.reduce(results, function (memo, sql) {
193 // Asyncronously run all the functions to all the extension sql for the database,
194 // in series, and execute the query when they all have come back.
196 async.mapSeries(extensions, getAllSql, function (err, extensionSql) {
198 credsClone = JSON.parse(JSON.stringify(creds));
201 databaseCallback(err);
204 // each String of the scriptContents is the concatenated SQL for the extension.
205 // join these all together into a single string for the whole database.
206 allSql = _.reduce(extensionSql, function (memo, script) {
207 return memo + script;
210 // Without this, when we delegate to exec psql the err var will not be set even
211 // on the case of error.
212 allSql = "\\set ON_ERROR_STOP TRUE;\n" + allSql;
214 if (spec.wasInitialized && !_.isEqual(extensions, ["foundation-database"])) {
215 // give the admin user every extension by default
216 allSql = allSql + "insert into xt.usrext (usrext_usr_username, usrext_ext_id) " +
217 "select '" + creds.username +
218 "', ext_id from xt.ext where ext_location = '/core-extensions' and ext_name NOT LIKE 'oauth2';";
221 winston.info("Applying build to database " + spec.database);
222 credsClone.database = spec.database;
223 buildDatabaseUtil.sendToDatabase(allSql, credsClone, spec, function (err, res) {
224 databaseCallback(err, res);
231 // Okay, before we install the database there is ONE thing we need to check,
232 // which is the pre-installed ORMs. Check that now.
234 var preInstallDatabase = function (spec, callback) {
235 var existsSql = "select relname from pg_class where relname = 'orm'",
236 credsClone = JSON.parse(JSON.stringify(creds)),
237 ormTestSql = "select orm_namespace as namespace, " +
238 " orm_type as type " +
240 "where not orm_ext;";
242 credsClone.database = spec.database;
244 dataSource.query(existsSql, credsClone, function (err, res) {
248 if (spec.wipeViews || res.rowCount === 0) {
249 // xt.orm doesn't exist, because this is probably a brand-new DB.
250 // No problem! That just means that there are no pre-existing ORMs.
252 installDatabase(spec, callback);
254 dataSource.query(ormTestSql, credsClone, function (err, res) {
258 spec.orms = res.rows;
259 installDatabase(spec, callback);
266 // Install all the databases
268 async.map(specs, preInstallDatabase, function (err, res) {
270 winston.error(err.message, err.stack, err);
271 if (masterCallback) {
276 winston.info("Success installing all scripts.");
277 winston.info("Cleaning up.");
278 clientBuilder.cleanup(specs, function (err) {
279 if (masterCallback) {
280 masterCallback(err, res);