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 databaseCallback(err, res);
233 // Okay, before we install the database there is ONE thing we need to check,
234 // which is the pre-installed ORMs. Check that now.
236 var preInstallDatabase = function (spec, callback) {
237 var existsSql = "select relname from pg_class where relname = 'orm'",
238 credsClone = JSON.parse(JSON.stringify(creds)),
239 ormTestSql = "select orm_namespace as namespace, " +
240 " orm_type as type " +
242 "where not orm_ext;";
244 credsClone.database = spec.database;
246 dataSource.query(existsSql, credsClone, function (err, res) {
250 if (spec.wipeViews || res.rowCount === 0) {
251 // xt.orm doesn't exist, because this is probably a brand-new DB.
252 // No problem! That just means that there are no pre-existing ORMs.
254 installDatabase(spec, callback);
256 dataSource.query(ormTestSql, credsClone, function (err, res) {
260 spec.orms = res.rows;
261 installDatabase(spec, callback);
268 // Install all the databases
270 async.map(specs, preInstallDatabase, function (err, res) {
272 winston.error(err.message, err.stack, err);
273 if (masterCallback) {
278 winston.info("Success installing all scripts.");
279 winston.info("Cleaning up.");
280 clientBuilder.cleanup(specs, function (err) {
281 if (masterCallback) {
282 masterCallback(err, res);