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 */
8 var _ = require('underscore'),
9 async = require('async'),
10 exec = require('child_process').exec,
12 path = require('path'),
13 conversionMap = require("./convert_specialized").conversionMap,
14 dataSource = require('../../../node-datasource/lib/ext/datasource').dataSource,
15 inspectDatabaseExtensions = require("./inspect_database").inspectDatabaseExtensions;
17 // register extension and dependencies
18 var getRegistrationSql = function (options, extensionLocation) {
19 var registerSql = 'do $$ plv8.elog(NOTICE, "About to register extension ' +
20 options.name + '"); $$ language plv8;\n';
22 registerSql = registerSql + "select xt.register_extension('%@', '%@', '%@', '', %@);\n"
23 .f(options.name, options.description || options.comment, extensionLocation, options.loadOrder || 9999);
25 registerSql = registerSql + "select xt.grant_role_ext('ADMIN', '%@');\n"
28 // TODO: infer dependencies from package.json using peerDependencies
29 var dependencies = options.dependencies || [];
30 _.each(dependencies, function (dependency) {
31 var dependencySql = "select xt.register_extension_dependency('%@', '%@');\n"
32 .f(options.name, dependency),
33 grantDependToAdmin = "select xt.grant_role_ext('ADMIN', '%@');\n"
36 registerSql = registerSql + dependencySql + grantDependToAdmin;
41 var composeExtensionSql = function (scriptSql, packageFile, options, callback) {
42 // each String of the scriptContents is the concatenated SQL for the script.
43 // join these all together into a single string for the whole extension.
44 var extensionSql = _.reduce(scriptSql, function (memo, script) {
48 if (options.registerExtension) {
49 extensionSql = getRegistrationSql(packageFile, options.extensionLocation) +
52 if (options.runJsInit) {
53 // unless it it hasn't yet been defined (ie. lib/orm),
54 // running xt.js_init() is probably a good idea.
55 extensionSql = "select xt.js_init();" + extensionSql;
58 if (options.wipeViews) {
59 // If we want to pre-emptively wipe out the views, the best place to do it
60 // is at the start of the core application code
61 fs.readFile(path.join(__dirname, "../../../enyo-client/database/source/wipe_views.sql"),
62 function (err, wipeSql) {
67 extensionSql = wipeSql + extensionSql;
68 callback(null, extensionSql);
71 } else if (options.wipeOrms) {
72 // If we want to pre-emptively wipe out the views, the best place to do it
73 // is at the start of the core application code
74 fs.readFile(path.join(__dirname, "../../../enyo-client/database/source/delete_system_orms.sql"),
75 function (err, wipeSql) {
80 extensionSql = wipeSql + extensionSql;
81 callback(null, extensionSql);
84 callback(null, extensionSql);
88 var explodeManifest = function (options, manifestCallback) {
89 var manifestFilename = options.manifestFilename;
91 var dbSourceRoot = path.dirname(manifestFilename);
93 if (options.extensionPath && fs.existsSync(path.resolve(options.extensionPath, "package.json"))) {
94 packageJson = require(path.resolve(options.extensionPath, "package.json"));
98 // Read the manifest files.
101 if (!fs.existsSync(manifestFilename) && packageJson) {
102 console.log("No manifest file " + manifestFilename + ". There is probably no db-side code in the extension.");
103 composeExtensionSql([], packageJson, options, manifestCallback);
106 } else if (!fs.existsSync(manifestFilename)) {
107 // error condition: no manifest file
108 manifestCallback("Cannot find manifest " + manifestFilename);
111 fs.readFile(manifestFilename, "utf8", function (err, manifestString) {
117 extraManifestScripts,
118 alterPaths = dbSourceRoot.indexOf("foundation-database") < 0;
121 manifest = JSON.parse(manifestString);
122 databaseScripts = manifest.databaseScripts;
123 defaultSchema = manifest.defaultSchema;
126 // error condition: manifest file is not properly formatted
127 manifestCallback("Manifest is not valid JSON" + manifestFilename);
135 // supported use cases:
137 // 1. add mobilized inventory to quickbooks
138 // need the frozen_manifest, the foundation/manifest, and the mobile manifest
139 // -e ../private-extensions/source/inventory -f
140 // useFrozenScripts, useFoundationScripts
142 // 2. add mobilized inventory to masterref (foundation inventory is already there)
143 // need the the foundation/manifest and the mobile manifest
144 // -e ../private-extensions/source/inventory
145 // useFoundationScripts
147 // 3. add unmobilized inventory to quickbooks
148 // need the frozen_manifest and the foundation/manifest
149 // -e ../private-extensions/source/inventory/foundation-database -f
150 // useFrozenScripts (useFoundationScripts already taken care of by -e path)
152 // 4. upgrade unmobilized inventory
153 // not sure if this is necessary, but it would look like
154 // -e ../private-extensions/source/inventory/foundation-database
156 if (options.useFoundationScripts) {
157 extraManifest = JSON.parse(fs.readFileSync(path.join(dbSourceRoot, "../../foundation-database/manifest.js")));
158 defaultSchema = defaultSchema || extraManifest.defaultSchema;
159 extraManifestScripts = extraManifest.databaseScripts;
160 extraManifestScripts = _.map(extraManifestScripts, function (path) {
161 return "../../foundation-database/" + path;
163 databaseScripts.unshift(extraManifestScripts);
164 databaseScripts = _.flatten(databaseScripts);
166 if (options.useFrozenScripts) {
167 // Frozen files are not idempotent and should only be run upon first registration
168 extraManifestPath = alterPaths ?
169 path.join(dbSourceRoot, "../../foundation-database/frozen_manifest.js") :
170 path.join(dbSourceRoot, "frozen_manifest.js");
172 extraManifest = JSON.parse(fs.readFileSync(extraManifestPath));
173 defaultSchema = defaultSchema || extraManifest.defaultSchema;
174 extraManifestScripts = extraManifest.databaseScripts;
176 extraManifestScripts = _.map(extraManifestScripts, function (path) {
177 return "../../foundation-database/" + path;
180 databaseScripts.unshift(extraManifestScripts);
181 databaseScripts = _.flatten(databaseScripts);
186 // Concatenate together all the files referenced in the manifest.
188 var getScriptSql = function (filename, scriptCallback) {
189 var fullFilename = path.join(dbSourceRoot, filename);
190 if (!fs.existsSync(fullFilename)) {
191 // error condition: script referenced in manifest.js isn't there
192 scriptCallback(path.join(dbSourceRoot, filename) + " does not exist");
195 fs.readFile(fullFilename, "utf8", function (err, scriptContents) {
196 // error condition: can't read script
201 var beforeNoticeSql = "do $$ BEGIN RAISE NOTICE 'Loading file " + path.basename(fullFilename) +
202 "'; END $$ language plpgsql;\n",
203 extname = path.extname(fullFilename).substring(1);
205 // convert special files: metasql, uiforms, reports, uijs
206 scriptContents = conversionMap[extname](scriptContents, fullFilename, defaultSchema);
209 // Incorrectly-ended sql files (i.e. no semicolon) make for unhelpful error messages
210 // when we concatenate 100's of them together. Guard against these.
212 scriptContents = scriptContents.trim();
213 if (scriptContents.charAt(scriptContents.length - 1) !== ';') {
214 // error condition: script is improperly formatted
215 scriptCallback("Error: " + fullFilename + " contents do not end in a semicolon.");
218 scriptCallback(null, '\n' + scriptContents);
221 async.mapSeries(databaseScripts || [], getScriptSql, function (err, scriptSql) {
226 manifestCallback(err);
230 composeExtensionSql(scriptSql, packageJson || manifest, options, manifestCallback);
234 // End script installation code
239 exports.explodeManifest = explodeManifest;