meadow-connection-postgresql 1.0.0 → 1.0.2

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/docs/schema.md ADDED
@@ -0,0 +1,148 @@
1
+ # Schema & Column Types
2
+
3
+ ## Overview
4
+
5
+ The PostgreSQL connector generates standard SQL DDL (`CREATE TABLE`, `DROP TABLE`) from Meadow table schemas. Unlike MongoDB or Dgraph connectors that produce descriptor objects, this connector generates SQL strings that are executed directly via `pool.query()`.
6
+
7
+ ## Column Type Mapping
8
+
9
+ | Meadow DataType | PostgreSQL Type | Constraints | Size Used |
10
+ |-----------------|-----------------|-------------|-----------|
11
+ | `ID` | `SERIAL` | `PRIMARY KEY` | No |
12
+ | `GUID` | `VARCHAR(Size)` | `DEFAULT '0xDe'` | Yes (default 36) |
13
+ | `ForeignKey` | `INTEGER` | `NOT NULL DEFAULT 0` | No |
14
+ | `Numeric` | `INTEGER` | `NOT NULL DEFAULT 0` | No |
15
+ | `Decimal` | `DECIMAL(Size)` | -- | Yes (e.g. `10,2`) |
16
+ | `String` | `VARCHAR(Size)` | `NOT NULL DEFAULT ''` | Yes |
17
+ | `Text` | `TEXT` | -- | No |
18
+ | `DateTime` | `TIMESTAMP` | -- | No |
19
+ | `Boolean` | `BOOLEAN` | `NOT NULL DEFAULT false` | No |
20
+
21
+ ### Key Behaviors
22
+
23
+ - **`ID`** -- uses PostgreSQL's `SERIAL` type which auto-creates a sequence and sets `NOT NULL DEFAULT nextval(...)`. The `PRIMARY KEY` constraint is declared inline.
24
+ - **`GUID`** -- defaults to `VARCHAR(36)` if no `Size` is provided or if the `Size` value is not a number.
25
+ - **`Boolean`** -- uses PostgreSQL's native `BOOLEAN` type (not `TINYINT` like MySQL).
26
+ - **`Decimal`** -- the `Size` property is passed directly (e.g. `'10,2'` becomes `DECIMAL(10,2)`).
27
+ - **Quoted identifiers** -- all table and column names are wrapped in double quotes (`"Name"`) to safely handle PostgreSQL reserved words.
28
+
29
+ ## Generated DDL Example
30
+
31
+ Given this Meadow table schema:
32
+
33
+ ```javascript
34
+ let tmpAnimalSchema =
35
+ {
36
+ TableName: 'Animal',
37
+ Columns:
38
+ [
39
+ { Column: 'IDAnimal', DataType: 'ID' },
40
+ { Column: 'GUIDAnimal', DataType: 'GUID', Size: 36 },
41
+ { Column: 'Name', DataType: 'String', Size: 128 },
42
+ { Column: 'Age', DataType: 'Numeric' },
43
+ { Column: 'Weight', DataType: 'Decimal', Size: '10,2' },
44
+ { Column: 'Description', DataType: 'Text' },
45
+ { Column: 'CreateDate', DataType: 'DateTime' },
46
+ { Column: 'Deleted', DataType: 'Boolean' }
47
+ ]
48
+ };
49
+ ```
50
+
51
+ The generated DDL:
52
+
53
+ ```sql
54
+ -- [ Animal ]
55
+ CREATE TABLE IF NOT EXISTS
56
+ "Animal"
57
+ (
58
+ "IDAnimal" SERIAL PRIMARY KEY,
59
+ "GUIDAnimal" VARCHAR(36) DEFAULT '0xDe',
60
+ "Name" VARCHAR(128) NOT NULL DEFAULT '',
61
+ "Age" INTEGER NOT NULL DEFAULT 0,
62
+ "Weight" DECIMAL(10,2),
63
+ "Description" TEXT,
64
+ "CreateDate" TIMESTAMP,
65
+ "Deleted" BOOLEAN NOT NULL DEFAULT false
66
+ );
67
+ ```
68
+
69
+ ## Drop Table Statement
70
+
71
+ The `generateDropTableStatement(name)` method generates a `DROP TABLE IF EXISTS` statement:
72
+
73
+ ```javascript
74
+ let tmpDrop = _Fable.MeadowPostgreSQLProvider.generateDropTableStatement('Animal');
75
+ // => 'DROP TABLE IF EXISTS "Animal";'
76
+ ```
77
+
78
+ ## Multiple Tables
79
+
80
+ Use `createTables()` to create multiple tables from a Stricture schema:
81
+
82
+ ```javascript
83
+ let tmpSchema =
84
+ {
85
+ Tables:
86
+ [
87
+ {
88
+ TableName: 'Animal',
89
+ Columns:
90
+ [
91
+ { Column: 'IDAnimal', DataType: 'ID' },
92
+ { Column: 'GUIDAnimal', DataType: 'GUID', Size: 36 },
93
+ { Column: 'Name', DataType: 'String', Size: 128 }
94
+ ]
95
+ },
96
+ {
97
+ TableName: 'Farm',
98
+ Columns:
99
+ [
100
+ { Column: 'IDFarm', DataType: 'ID' },
101
+ { Column: 'GUIDFarm', DataType: 'GUID', Size: 36 },
102
+ { Column: 'FarmName', DataType: 'String', Size: 256 }
103
+ ]
104
+ }
105
+ ]
106
+ };
107
+
108
+ _Fable.MeadowPostgreSQLProvider.createTables(tmpSchema,
109
+ (pError) =>
110
+ {
111
+ if (pError) { console.error(pError); return; }
112
+ console.log('All tables created!');
113
+ });
114
+ ```
115
+
116
+ Tables are created sequentially (concurrency of 1) to ensure deterministic ordering.
117
+
118
+ ## PostgreSQL vs MySQL Type Mapping
119
+
120
+ | Meadow DataType | PostgreSQL | MySQL |
121
+ |-----------------|-----------|-------|
122
+ | `ID` | `SERIAL PRIMARY KEY` | `INT UNSIGNED NOT NULL AUTO_INCREMENT` |
123
+ | `GUID` | `VARCHAR(36)` | `CHAR(36)` |
124
+ | `ForeignKey` | `INTEGER NOT NULL DEFAULT 0` | `INT UNSIGNED NOT NULL DEFAULT 0` |
125
+ | `Numeric` | `INTEGER NOT NULL DEFAULT 0` | `INT NOT NULL DEFAULT 0` |
126
+ | `Decimal` | `DECIMAL(Size)` | `DECIMAL(Size)` |
127
+ | `String` | `VARCHAR(Size) NOT NULL DEFAULT ''` | `VARCHAR(Size) NOT NULL DEFAULT ''` |
128
+ | `Text` | `TEXT` | `TEXT` |
129
+ | `DateTime` | `TIMESTAMP` | `DATETIME` |
130
+ | `Boolean` | `BOOLEAN NOT NULL DEFAULT false` | `TINYINT NOT NULL DEFAULT 0` |
131
+
132
+ ### Notable Differences
133
+
134
+ - **Auto-increment**: PostgreSQL uses `SERIAL` (sequence-backed); MySQL uses `AUTO_INCREMENT`
135
+ - **Boolean**: PostgreSQL has a native `BOOLEAN` type; MySQL uses `TINYINT`
136
+ - **DateTime**: PostgreSQL uses `TIMESTAMP`; MySQL uses `DATETIME`
137
+ - **Integer signing**: PostgreSQL integers are always signed; MySQL supports `UNSIGNED`
138
+ - **GUID default**: PostgreSQL uses `DEFAULT '0xDe'`; this placeholder is replaced at insertion time by Meadow's auto-GUID mechanism
139
+ - **Identifiers**: PostgreSQL uses double quotes (`"Name"`); MySQL uses backticks (`` `Name` ``)
140
+
141
+ ## Idempotent Schema
142
+
143
+ Table creation is idempotent through two mechanisms:
144
+
145
+ 1. **`CREATE TABLE IF NOT EXISTS`** -- PostgreSQL skips creation if the table exists
146
+ 2. **Error code 42P07** -- the connector catches `duplicate_table` errors and logs a warning instead of failing
147
+
148
+ This makes `createTable()` and `createTables()` safe to call during application startup.
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "meadow-connection-postgresql",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Meadow PostgreSQL Connection Plugin",
5
5
  "main": "source/Meadow-Connection-PostgreSQL.js",
6
6
  "scripts": {
7
7
  "coverage": "npx quack coverage",
8
- "test": "npx quack test"
8
+ "test": "npx quack test",
9
+ "docker-postgresql-start": "bash ./start-postgresql.sh",
10
+ "docker-postgresql-stop": "bash ./stop-postgresql.sh",
11
+ "test-docker": "bash ./start-postgresql.sh && npm test; bash ./stop-postgresql.sh"
9
12
  },
10
13
  "mocha": {
11
14
  "diff": true,
@@ -6,6 +6,8 @@ const libFableServiceProviderBase = require('fable-serviceproviderbase');
6
6
 
7
7
  const libPG = require('pg');
8
8
 
9
+ const libMeadowSchemaPostgreSQL = require('./Meadow-Schema-PostgreSQL.js');
10
+
9
11
  class MeadowConnectionPostgreSQL extends libFableServiceProviderBase
10
12
  {
11
13
  constructor(pFable, pManifest, pServiceHash)
@@ -66,128 +68,105 @@ class MeadowConnectionPostgreSQL extends libFableServiceProviderBase
66
68
  this._ConnectionPool = false;
67
69
  this.connected = false;
68
70
 
71
+ // Schema provider handles DDL operations (create, drop, index, etc.)
72
+ this._SchemaProvider = new libMeadowSchemaPostgreSQL(this.fable, this.options, `${this.Hash}-Schema`);
73
+
69
74
  if (this.options.MeadowConnectionPostgreSQLAutoConnect)
70
75
  {
71
76
  this.connect();
72
77
  }
73
78
  }
74
79
 
80
+ get schemaProvider()
81
+ {
82
+ return this._SchemaProvider;
83
+ }
84
+
75
85
  generateDropTableStatement(pTableName)
76
86
  {
77
- return `DROP TABLE IF EXISTS "${pTableName}";`;
87
+ return this._SchemaProvider.generateDropTableStatement(pTableName);
78
88
  }
79
89
 
80
90
  generateCreateTableStatement(pMeadowTableSchema)
81
91
  {
82
- this.log.info(`--> Building the table create string for ${pMeadowTableSchema} ...`);
92
+ return this._SchemaProvider.generateCreateTableStatement(pMeadowTableSchema);
93
+ }
83
94
 
84
- let tmpPrimaryKey = false;
85
- let tmpCreateTableStatement = `-- [ ${pMeadowTableSchema.TableName} ]`;
95
+ createTables(pMeadowSchema, fCallback)
96
+ {
97
+ return this._SchemaProvider.createTables(pMeadowSchema, fCallback);
98
+ }
86
99
 
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];
100
+ createTable(pMeadowTableSchema, fCallback)
101
+ {
102
+ return this._SchemaProvider.createTable(pMeadowTableSchema, fCallback);
103
+ }
91
104
 
92
- // If we aren't the first column, append a comma.
93
- if (j > 0)
94
- {
95
- tmpCreateTableStatement += `,`;
96
- }
105
+ getIndexDefinitionsFromSchema(pMeadowTableSchema)
106
+ {
107
+ return this._SchemaProvider.getIndexDefinitionsFromSchema(pMeadowTableSchema);
108
+ }
97
109
 
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 );`;
110
+ generateCreateIndexScript(pMeadowTableSchema)
111
+ {
112
+ return this._SchemaProvider.generateCreateIndexScript(pMeadowTableSchema);
113
+ }
142
114
 
143
- return tmpCreateTableStatement;
115
+ generateCreateIndexStatements(pMeadowTableSchema)
116
+ {
117
+ return this._SchemaProvider.generateCreateIndexStatements(pMeadowTableSchema);
144
118
  }
145
119
 
146
- createTables(pMeadowSchema, fCallback)
120
+ createIndex(pIndexStatement, fCallback)
147
121
  {
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
- });
122
+ return this._SchemaProvider.createIndex(pIndexStatement, fCallback);
162
123
  }
163
124
 
164
- createTable(pMeadowTableSchema, fCallback)
125
+ createIndices(pMeadowTableSchema, fCallback)
165
126
  {
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
- });
127
+ return this._SchemaProvider.createIndices(pMeadowTableSchema, fCallback);
128
+ }
129
+
130
+ createAllIndices(pMeadowSchema, fCallback)
131
+ {
132
+ return this._SchemaProvider.createAllIndices(pMeadowSchema, fCallback);
133
+ }
134
+
135
+ // Database Introspection delegation
136
+
137
+ listTables(fCallback)
138
+ {
139
+ return this._SchemaProvider.listTables(fCallback);
140
+ }
141
+
142
+ introspectTableColumns(pTableName, fCallback)
143
+ {
144
+ return this._SchemaProvider.introspectTableColumns(pTableName, fCallback);
145
+ }
146
+
147
+ introspectTableIndices(pTableName, fCallback)
148
+ {
149
+ return this._SchemaProvider.introspectTableIndices(pTableName, fCallback);
150
+ }
151
+
152
+ introspectTableForeignKeys(pTableName, fCallback)
153
+ {
154
+ return this._SchemaProvider.introspectTableForeignKeys(pTableName, fCallback);
155
+ }
156
+
157
+ introspectTableSchema(pTableName, fCallback)
158
+ {
159
+ return this._SchemaProvider.introspectTableSchema(pTableName, fCallback);
160
+ }
161
+
162
+ introspectDatabaseSchema(fCallback)
163
+ {
164
+ return this._SchemaProvider.introspectDatabaseSchema(fCallback);
165
+ }
166
+
167
+ generateMeadowPackageFromTable(pTableName, fCallback)
168
+ {
169
+ return this._SchemaProvider.generateMeadowPackageFromTable(pTableName, fCallback);
191
170
  }
192
171
 
193
172
  connect()
@@ -212,6 +191,7 @@ class MeadowConnectionPostgreSQL extends libFableServiceProviderBase
212
191
  {
213
192
  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
193
  this._ConnectionPool = new libPG.Pool(tmpConnectionSettings);
194
+ this._SchemaProvider.setConnectionPool(this._ConnectionPool);
215
195
  this.connected = true;
216
196
  }
217
197
  }