meadow-connection-postgresql 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "meadow-connection-postgresql",
3
+ "version": "1.0.0",
4
+ "description": "Meadow PostgreSQL Connection Plugin",
5
+ "main": "source/Meadow-Connection-PostgreSQL.js",
6
+ "scripts": {
7
+ "coverage": "npx quack coverage",
8
+ "test": "npx quack test"
9
+ },
10
+ "mocha": {
11
+ "diff": true,
12
+ "extension": [
13
+ "js"
14
+ ],
15
+ "package": "./package.json",
16
+ "reporter": "spec",
17
+ "slow": "75",
18
+ "timeout": "5000",
19
+ "ui": "tdd",
20
+ "watch-files": [
21
+ "source/**/*.js",
22
+ "test/**/*.js"
23
+ ],
24
+ "watch-ignore": [
25
+ "lib/vendor"
26
+ ]
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/stevenvelozo/meadow-connection-postgresql.git"
31
+ },
32
+ "keywords": [
33
+ "meadow",
34
+ "postgresql",
35
+ "database"
36
+ ],
37
+ "author": "Steven Velozo <steven@velozo.com> (http://velozo.com/)",
38
+ "license": "MIT",
39
+ "bugs": {
40
+ "url": "https://github.com/stevenvelozo/meadow-connection-postgresql/issues"
41
+ },
42
+ "homepage": "https://github.com/stevenvelozo/meadow-connection-postgresql",
43
+ "devDependencies": {
44
+ "fable": "^3.1.55",
45
+ "quackage": "^1.0.58"
46
+ },
47
+ "dependencies": {
48
+ "fable-serviceproviderbase": "^3.0.18",
49
+ "pg": "^8.13.0"
50
+ }
51
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Meadow PostgreSQL Provider Fable Service
3
+ * @author Steven Velozo <steven@velozo.com>
4
+ */
5
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
6
+
7
+ const libPG = require('pg');
8
+
9
+ class MeadowConnectionPostgreSQL extends libFableServiceProviderBase
10
+ {
11
+ constructor(pFable, pManifest, pServiceHash)
12
+ {
13
+ super(pFable, pManifest, pServiceHash);
14
+
15
+ this.serviceType = 'MeadowConnectionPostgreSQL';
16
+
17
+ // See if the user passed in a PostgreSQL object already
18
+ if (typeof(this.options.PostgreSQL) == 'object')
19
+ {
20
+ // Test if this needs property coercion from the old format to the raw
21
+ if (!this.options.PostgreSQL.hasOwnProperty('max') && this.options.PostgreSQL.hasOwnProperty('ConnectionPoolLimit'))
22
+ {
23
+ this.options.PostgreSQL.max = this.options.PostgreSQL.ConnectionPoolLimit;
24
+ }
25
+ if (!this.options.PostgreSQL.hasOwnProperty('host') && this.options.PostgreSQL.hasOwnProperty('Server'))
26
+ {
27
+ this.options.PostgreSQL.host = this.options.PostgreSQL.Server;
28
+ }
29
+ if (!this.options.PostgreSQL.hasOwnProperty('port') && this.options.PostgreSQL.hasOwnProperty('Port'))
30
+ {
31
+ this.options.PostgreSQL.port = this.options.PostgreSQL.Port;
32
+ }
33
+ if (!this.options.PostgreSQL.hasOwnProperty('user') && this.options.PostgreSQL.hasOwnProperty('User'))
34
+ {
35
+ this.options.PostgreSQL.user = this.options.PostgreSQL.User;
36
+ }
37
+ if (!this.options.PostgreSQL.hasOwnProperty('password') && this.options.PostgreSQL.hasOwnProperty('Password'))
38
+ {
39
+ this.options.PostgreSQL.password = this.options.PostgreSQL.Password;
40
+ }
41
+ if (!this.options.PostgreSQL.hasOwnProperty('database') && this.options.PostgreSQL.hasOwnProperty('Database'))
42
+ {
43
+ this.options.PostgreSQL.database = this.options.PostgreSQL.Database;
44
+ }
45
+ }
46
+ else if (typeof(this.fable.settings.PostgreSQL) == 'object')
47
+ {
48
+ this.options.PostgreSQL = (
49
+ {
50
+ max: this.fable.settings.PostgreSQL.ConnectionPoolLimit,
51
+ host: this.fable.settings.PostgreSQL.Server,
52
+ port: this.fable.settings.PostgreSQL.Port,
53
+ user: this.fable.settings.PostgreSQL.User,
54
+ password: this.fable.settings.PostgreSQL.Password,
55
+ database: this.fable.settings.PostgreSQL.Database
56
+ });
57
+ }
58
+
59
+ if (!this.options.MeadowConnectionPostgreSQLAutoConnect)
60
+ {
61
+ // See if there is a global autoconnect
62
+ this.options.MeadowConnectionPostgreSQLAutoConnect = this.fable.settings.MeadowConnectionPostgreSQLAutoConnect;
63
+ }
64
+
65
+ this.serviceType = 'MeadowConnectionPostgreSQL';
66
+ this._ConnectionPool = false;
67
+ this.connected = false;
68
+
69
+ if (this.options.MeadowConnectionPostgreSQLAutoConnect)
70
+ {
71
+ this.connect();
72
+ }
73
+ }
74
+
75
+ generateDropTableStatement(pTableName)
76
+ {
77
+ return `DROP TABLE IF EXISTS "${pTableName}";`;
78
+ }
79
+
80
+ generateCreateTableStatement(pMeadowTableSchema)
81
+ {
82
+ this.log.info(`--> Building the table create string for ${pMeadowTableSchema} ...`);
83
+
84
+ let tmpPrimaryKey = false;
85
+ let tmpCreateTableStatement = `-- [ ${pMeadowTableSchema.TableName} ]`;
86
+
87
+ tmpCreateTableStatement += `\nCREATE TABLE IF NOT EXISTS\n "${pMeadowTableSchema.TableName}"\n (`;
88
+ for (let j = 0; j < pMeadowTableSchema.Columns.length; j++)
89
+ {
90
+ let tmpColumn = pMeadowTableSchema.Columns[j];
91
+
92
+ // If we aren't the first column, append a comma.
93
+ if (j > 0)
94
+ {
95
+ tmpCreateTableStatement += `,`;
96
+ }
97
+
98
+ tmpCreateTableStatement += `\n`;
99
+ // Dump out each column......
100
+ switch (tmpColumn.DataType)
101
+ {
102
+ case 'ID':
103
+ tmpCreateTableStatement += ` "${tmpColumn.Column}" SERIAL PRIMARY KEY`;
104
+ tmpPrimaryKey = tmpColumn.Column;
105
+ break;
106
+ case 'GUID':
107
+ let tmpSize = tmpColumn.hasOwnProperty('Size') ? tmpColumn.Size : 36;
108
+ if (isNaN(tmpSize))
109
+ {
110
+ // Use the old default if Size is improper
111
+ tmpSize = 36;
112
+ }
113
+ tmpCreateTableStatement += ` "${tmpColumn.Column}" VARCHAR(${tmpSize}) DEFAULT '0xDe'`;
114
+ break;
115
+ case 'ForeignKey':
116
+ tmpCreateTableStatement += ` "${tmpColumn.Column}" INTEGER NOT NULL DEFAULT 0`;
117
+ break;
118
+ case 'Numeric':
119
+ tmpCreateTableStatement += ` "${tmpColumn.Column}" INTEGER NOT NULL DEFAULT 0`;
120
+ break;
121
+ case 'Decimal':
122
+ tmpCreateTableStatement += ` "${tmpColumn.Column}" DECIMAL(${tmpColumn.Size})`;
123
+ break;
124
+ case 'String':
125
+ tmpCreateTableStatement += ` "${tmpColumn.Column}" VARCHAR(${tmpColumn.Size}) NOT NULL DEFAULT ''`;
126
+ break;
127
+ case 'Text':
128
+ tmpCreateTableStatement += ` "${tmpColumn.Column}" TEXT`;
129
+ break;
130
+ case 'DateTime':
131
+ tmpCreateTableStatement += ` "${tmpColumn.Column}" TIMESTAMP`;
132
+ break;
133
+ case 'Boolean':
134
+ tmpCreateTableStatement += ` "${tmpColumn.Column}" BOOLEAN NOT NULL DEFAULT false`;
135
+ break;
136
+ default:
137
+ break;
138
+ }
139
+ }
140
+ // PostgreSQL SERIAL PRIMARY KEY is declared inline; no separate PRIMARY KEY clause needed
141
+ tmpCreateTableStatement += `\n );`;
142
+
143
+ return tmpCreateTableStatement;
144
+ }
145
+
146
+ createTables(pMeadowSchema, fCallback)
147
+ {
148
+ this.fable.Utility.eachLimit(pMeadowSchema.Tables, 1,
149
+ (pTable, fCreateComplete) =>
150
+ {
151
+ return this.createTable(pTable, fCreateComplete);
152
+ },
153
+ (pCreateError) =>
154
+ {
155
+ if (pCreateError)
156
+ {
157
+ this.log.error(`Meadow-PostgreSQL Error creating tables from Schema: ${pCreateError}`, pCreateError);
158
+ }
159
+ this.log.info('Done creating tables!');
160
+ return fCallback(pCreateError);
161
+ });
162
+ }
163
+
164
+ createTable(pMeadowTableSchema, fCallback)
165
+ {
166
+ let tmpCreateTableStatement = this.generateCreateTableStatement(pMeadowTableSchema);
167
+ this._ConnectionPool.query(tmpCreateTableStatement,
168
+ (pError, pResult) =>
169
+ {
170
+ if (pError)
171
+ {
172
+ // Check for "already exists" type errors
173
+ if (pError.code === '42P07')
174
+ {
175
+ // 42P07 = duplicate_table
176
+ this.log.warn(`Meadow-PostgreSQL CREATE TABLE ${pMeadowTableSchema.TableName} executed but table already existed.`);
177
+ return fCallback();
178
+ }
179
+ else
180
+ {
181
+ this.log.error(`Meadow-PostgreSQL CREATE TABLE ${pMeadowTableSchema.TableName} failed!`, pError);
182
+ return fCallback(pError);
183
+ }
184
+ }
185
+ else
186
+ {
187
+ this.log.info(`Meadow-PostgreSQL CREATE TABLE ${pMeadowTableSchema.TableName} executed successfully.`);
188
+ return fCallback();
189
+ }
190
+ });
191
+ }
192
+
193
+ connect()
194
+ {
195
+ let tmpConnectionSettings = (
196
+ {
197
+ max: this.options.PostgreSQL.max,
198
+ host: this.options.PostgreSQL.host,
199
+ port: this.options.PostgreSQL.port,
200
+ user: this.options.PostgreSQL.user,
201
+ password: this.options.PostgreSQL.password,
202
+ database: this.options.PostgreSQL.database
203
+ });
204
+ if (this._ConnectionPool)
205
+ {
206
+ let tmpCleansedLogSettings = JSON.parse(JSON.stringify(tmpConnectionSettings));
207
+ // No leaking passwords!
208
+ tmpCleansedLogSettings.password = '*****************';
209
+ this.log.error(`Meadow-Connection-PostgreSQL trying to connect but is already connected - skipping the generation of extra connections.`, tmpCleansedLogSettings);
210
+ }
211
+ else
212
+ {
213
+ this.fable.log.info(`Meadow-Connection-PostgreSQL connecting to [${this.options.PostgreSQL.host} : ${this.options.PostgreSQL.port}] as ${this.options.PostgreSQL.user} for database ${this.options.PostgreSQL.database} at a pool limit of ${this.options.PostgreSQL.max}`);
214
+ this._ConnectionPool = new libPG.Pool(tmpConnectionSettings);
215
+ this.connected = true;
216
+ }
217
+ }
218
+
219
+ connectAsync(fCallback)
220
+ {
221
+ let tmpCallback = fCallback;
222
+ if (typeof (tmpCallback) !== 'function')
223
+ {
224
+ this.log.error(`Meadow PostgreSQL connectAsync() called without a callback; this could lead to connection race conditions.`);
225
+ tmpCallback = () => { };
226
+ }
227
+
228
+ try
229
+ {
230
+ // Create the pool if it doesn't exist
231
+ if (this._ConnectionPool)
232
+ {
233
+ return tmpCallback(null, this._ConnectionPool);
234
+ }
235
+ else
236
+ {
237
+ this.connect();
238
+ return tmpCallback(null, this._ConnectionPool);
239
+ }
240
+ }
241
+ catch(pError)
242
+ {
243
+ this.log.error(`Meadow PostgreSQL connectAsync() trapped an error trying to connect to server: ${pError}`, pError);
244
+ return tmpCallback(pError);
245
+ }
246
+ }
247
+
248
+ get pool()
249
+ {
250
+ return this._ConnectionPool;
251
+ }
252
+ }
253
+
254
+ module.exports = MeadowConnectionPostgreSQL;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Unit tests for Meadow Connection PostgreSQL
3
+ *
4
+ * @license MIT
5
+ *
6
+ * @author Steven Velozo <steven@velozo.com>
7
+ */
8
+
9
+ const Chai = require('chai');
10
+ const Expect = Chai.expect;
11
+
12
+ const libFable = require('fable');
13
+ const libMeadowConnectionPostgreSQL = require('../source/Meadow-Connection-PostgreSQL.js');
14
+
15
+ const _FableConfig = (
16
+ {
17
+ "Product": "MeadowPostgreSQLTest",
18
+ "ProductVersion": "1.0.0",
19
+
20
+ "UUID":
21
+ {
22
+ "DataCenter": 0,
23
+ "Worker": 0
24
+ },
25
+ "LogStreams":
26
+ [
27
+ {
28
+ "streamtype": "console"
29
+ }
30
+ ],
31
+
32
+ "PostgreSQL":
33
+ {
34
+ "Server": "127.0.0.1",
35
+ "Port": 5432,
36
+ "User": "postgres",
37
+ "Password": "testpassword",
38
+ "Database": "testdb",
39
+ "ConnectionPoolLimit": 20
40
+ }
41
+ });
42
+
43
+ suite
44
+ (
45
+ 'Meadow-Connection-PostgreSQL',
46
+ () =>
47
+ {
48
+ setup(() => {});
49
+
50
+ suite
51
+ (
52
+ 'Object Sanity',
53
+ () =>
54
+ {
55
+ test
56
+ (
57
+ 'constructor should create a connection service',
58
+ () =>
59
+ {
60
+ let _Fable = new libFable(_FableConfig);
61
+ _Fable.serviceManager.addServiceType('MeadowPostgreSQLProvider', libMeadowConnectionPostgreSQL);
62
+ _Fable.serviceManager.instantiateServiceProvider('MeadowPostgreSQLProvider');
63
+
64
+ Expect(_Fable.MeadowPostgreSQLProvider).to.be.an('object');
65
+ Expect(_Fable.MeadowPostgreSQLProvider.serviceType).to.equal('MeadowConnectionPostgreSQL');
66
+ Expect(_Fable.MeadowPostgreSQLProvider.connected).to.equal(false);
67
+ }
68
+ );
69
+ test
70
+ (
71
+ 'pass in your own settings',
72
+ () =>
73
+ {
74
+ let _Fable = new libFable();
75
+ _Fable.serviceManager.addServiceType('MeadowPostgreSQLProvider', libMeadowConnectionPostgreSQL);
76
+ _Fable.serviceManager.instantiateServiceProvider('MeadowPostgreSQLProvider', {PostgreSQL: _FableConfig.PostgreSQL});
77
+
78
+ Expect(_Fable.MeadowPostgreSQLProvider).to.be.an('object');
79
+ Expect(_Fable.MeadowPostgreSQLProvider.serviceType).to.equal('MeadowConnectionPostgreSQL');
80
+ Expect(_Fable.MeadowPostgreSQLProvider.connected).to.equal(false);
81
+ }
82
+ );
83
+ }
84
+ );
85
+ suite
86
+ (
87
+ 'DDL Generation',
88
+ () =>
89
+ {
90
+ test
91
+ (
92
+ 'generateCreateTableStatement produces valid PostgreSQL DDL',
93
+ () =>
94
+ {
95
+ let _Fable = new libFable(_FableConfig);
96
+ _Fable.serviceManager.addServiceType('MeadowPostgreSQLProvider', libMeadowConnectionPostgreSQL);
97
+ _Fable.serviceManager.instantiateServiceProvider('MeadowPostgreSQLProvider');
98
+
99
+ let tmpSchema = {
100
+ TableName: 'Animal',
101
+ Columns: [
102
+ { Column: 'IDAnimal', DataType: 'ID' },
103
+ { Column: 'GUIDAnimal', DataType: 'GUID', Size: 36 },
104
+ { Column: 'Name', DataType: 'String', Size: 128 },
105
+ { Column: 'Age', DataType: 'Numeric' },
106
+ { Column: 'Cost', DataType: 'Decimal', Size: '10,2' },
107
+ { Column: 'Description', DataType: 'Text' },
108
+ { Column: 'Birthday', DataType: 'DateTime' },
109
+ { Column: 'Active', DataType: 'Boolean' },
110
+ { Column: 'IDFarm', DataType: 'ForeignKey' }
111
+ ]
112
+ };
113
+
114
+ let tmpResult = _Fable.MeadowPostgreSQLProvider.generateCreateTableStatement(tmpSchema);
115
+ Expect(tmpResult).to.contain('CREATE TABLE IF NOT EXISTS');
116
+ Expect(tmpResult).to.contain('"Animal"');
117
+ Expect(tmpResult).to.contain('SERIAL PRIMARY KEY');
118
+ Expect(tmpResult).to.contain('VARCHAR(36)');
119
+ Expect(tmpResult).to.contain('VARCHAR(128)');
120
+ Expect(tmpResult).to.contain('INTEGER NOT NULL DEFAULT 0');
121
+ Expect(tmpResult).to.contain('DECIMAL(10,2)');
122
+ Expect(tmpResult).to.contain('TEXT');
123
+ Expect(tmpResult).to.contain('TIMESTAMP');
124
+ Expect(tmpResult).to.contain('BOOLEAN NOT NULL DEFAULT false');
125
+ // Should NOT contain MySQL-specific syntax
126
+ Expect(tmpResult).to.not.contain('AUTO_INCREMENT');
127
+ Expect(tmpResult).to.not.contain('TINYINT');
128
+ Expect(tmpResult).to.not.contain('CHARSET');
129
+ Expect(tmpResult).to.not.contain('COLLATE');
130
+ }
131
+ );
132
+ test
133
+ (
134
+ 'generateDropTableStatement produces valid PostgreSQL DDL',
135
+ () =>
136
+ {
137
+ let _Fable = new libFable(_FableConfig);
138
+ _Fable.serviceManager.addServiceType('MeadowPostgreSQLProvider', libMeadowConnectionPostgreSQL);
139
+ _Fable.serviceManager.instantiateServiceProvider('MeadowPostgreSQLProvider');
140
+
141
+ let tmpResult = _Fable.MeadowPostgreSQLProvider.generateDropTableStatement('Animal');
142
+ Expect(tmpResult).to.equal('DROP TABLE IF EXISTS "Animal";');
143
+ }
144
+ );
145
+ }
146
+ );
147
+ }
148
+ );