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/delete_system_orms.sql"),
62 function (err, wipeSql) {
67 extensionSql = wipeSql + extensionSql;
68 callback(null, extensionSql);
71 callback(null, extensionSql);
75 var explodeManifest = function (options, manifestCallback) {
76 var manifestFilename = options.manifestFilename;
78 var dbSourceRoot = path.dirname(manifestFilename);
80 if (options.extensionPath && fs.existsSync(path.resolve(options.extensionPath, "package.json"))) {
81 packageJson = require(path.resolve(options.extensionPath, "package.json"));
85 // Read the manifest files.
88 if (!fs.existsSync(manifestFilename) && packageJson) {
89 console.log("No manifest file " + manifestFilename + ". There is probably no db-side code in the extension.");
90 composeExtensionSql([], packageJson, options, manifestCallback);
93 } else if (!fs.existsSync(manifestFilename)) {
94 // error condition: no manifest file
95 manifestCallback("Cannot find manifest " + manifestFilename);
98 fs.readFile(manifestFilename, "utf8", function (err, manifestString) {
104 extraManifestScripts,
105 alterPaths = dbSourceRoot.indexOf("foundation-database") < 0;
108 manifest = JSON.parse(manifestString);
109 databaseScripts = manifest.databaseScripts;
110 defaultSchema = manifest.defaultSchema;
113 // error condition: manifest file is not properly formatted
114 manifestCallback("Manifest is not valid JSON" + manifestFilename);
122 // supported use cases:
124 // 1. add mobilized inventory to quickbooks
125 // need the frozen_manifest, the foundation/manifest, and the mobile manifest
126 // -e ../private-extensions/source/inventory -f
127 // useFrozenScripts, useFoundationScripts
129 // 2. add mobilized inventory to masterref (foundation inventory is already there)
130 // need the the foundation/manifest and the mobile manifest
131 // -e ../private-extensions/source/inventory
132 // useFoundationScripts
134 // 3. add unmobilized inventory to quickbooks
135 // need the frozen_manifest and the foundation/manifest
136 // -e ../private-extensions/source/inventory/foundation-database -f
137 // useFrozenScripts (useFoundationScripts already taken care of by -e path)
139 // 4. upgrade unmobilized inventory
140 // not sure if this is necessary, but it would look like
141 // -e ../private-extensions/source/inventory/foundation-database
143 if (options.useFoundationScripts) {
144 extraManifest = JSON.parse(fs.readFileSync(path.join(dbSourceRoot, "../../foundation-database/manifest.js")));
145 defaultSchema = defaultSchema || extraManifest.defaultSchema;
146 extraManifestScripts = extraManifest.databaseScripts;
147 extraManifestScripts = _.map(extraManifestScripts, function (path) {
148 return "../../foundation-database/" + path;
150 databaseScripts.unshift(extraManifestScripts);
151 databaseScripts = _.flatten(databaseScripts);
153 if (options.useFrozenScripts) {
154 // Frozen files are not idempotent and should only be run upon first registration
155 extraManifestPath = alterPaths ?
156 path.join(dbSourceRoot, "../../foundation-database/frozen_manifest.js") :
157 path.join(dbSourceRoot, "frozen_manifest.js");
159 extraManifest = JSON.parse(fs.readFileSync(extraManifestPath));
160 defaultSchema = defaultSchema || extraManifest.defaultSchema;
161 extraManifestScripts = extraManifest.databaseScripts;
163 extraManifestScripts = _.map(extraManifestScripts, function (path) {
164 return "../../foundation-database/" + path;
167 databaseScripts.unshift(extraManifestScripts);
168 databaseScripts = _.flatten(databaseScripts);
173 // Concatenate together all the files referenced in the manifest.
175 var getScriptSql = function (filename, scriptCallback) {
176 var fullFilename = path.join(dbSourceRoot, filename);
177 if (!fs.existsSync(fullFilename)) {
178 // error condition: script referenced in manifest.js isn't there
179 scriptCallback(path.join(dbSourceRoot, filename) + " does not exist");
182 fs.readFile(fullFilename, "utf8", function (err, scriptContents) {
183 // error condition: can't read script
188 var beforeNoticeSql = "do $$ BEGIN RAISE NOTICE 'Loading file " + path.basename(fullFilename) +
189 "'; END $$ language plpgsql;\n",
190 extname = path.extname(fullFilename).substring(1);
192 // convert special files: metasql, uiforms, reports, uijs
193 scriptContents = conversionMap[extname](scriptContents, fullFilename, defaultSchema);
196 // Incorrectly-ended sql files (i.e. no semicolon) make for unhelpful error messages
197 // when we concatenate 100's of them together. Guard against these.
199 scriptContents = scriptContents.trim();
200 if (scriptContents.charAt(scriptContents.length - 1) !== ';') {
201 // error condition: script is improperly formatted
202 scriptCallback("Error: " + fullFilename + " contents do not end in a semicolon.");
205 scriptCallback(null, '\n' + scriptContents);
208 async.mapSeries(databaseScripts || [], getScriptSql, function (err, scriptSql) {
213 manifestCallback(err);
217 composeExtensionSql(scriptSql, packageJson || manifest, options, manifestCallback);
221 // End script installation code
226 exports.explodeManifest = explodeManifest;