masterrecord 0.1.4 → 0.2.1

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.
@@ -1,5 +1,5 @@
1
1
 
2
- // version : 0.0.7
2
+ // version : 0.0.9
3
3
  var tools = require('../Tools');
4
4
  class EntityTrackerModel {
5
5
 
@@ -29,6 +29,10 @@ class EntityTrackerModel {
29
29
  set: function(value) {
30
30
  modelClass.__state = "modified";
31
31
  modelClass.__dirtyFields.push(modelField);
32
+ // ensure this entity is tracked on any modification
33
+ if(modelClass.__context && typeof modelClass.__context.__track === 'function'){
34
+ modelClass.__context.__track(modelClass);
35
+ }
32
36
  if(typeof currentEntity[modelField].set === "function"){
33
37
  this["__proto__"]["_" + modelField] = currentEntity[modelField].set(value);
34
38
  }else{
@@ -157,7 +161,7 @@ class EntityTrackerModel {
157
161
  var entityFieldJoinName = currentEntity[entityField].foreignTable === undefined? entityField : currentEntity[entityField].foreignTable;
158
162
  var thirdEntity = this.__context[tools.capitalize(entityFieldJoinName)];
159
163
  var firstJoiningID = joiningEntity.__entity[this.__entity.__name].foreignTable;
160
- var secondJoiningID = joiningEntity.__entity[entityField].foreignTable;
164
+ var secondJoiningID = Object.values(joiningEntity.__entity).find(e => e.foreignTable === ent.__name);
161
165
  if(firstJoiningID && secondJoiningID )
162
166
  {
163
167
  var modelValue = ent.include(`p => p.${entityFieldJoinName}.select(j => j.${joiningEntity.__entity[this.__entity.__name].foreignKey})`).include(`p =>p.${this.__entity.__name}`).where(`r =>r.${this.__entity.__name}.${priKey} = ${this[priKey]}`).toList();
@@ -234,4 +238,4 @@ class EntityTrackerModel {
234
238
 
235
239
  }
236
240
 
237
- module.exports = EntityTrackerModel
241
+ module.exports = EntityTrackerModel;
package/MIGRATIONS.md ADDED
@@ -0,0 +1,178 @@
1
+ ### Migrations and Server Update Guide
2
+
3
+ This project ships a CLI, exposed as `masterrecord`, to manage database migrations. Below are the steps to enable migrations, create migrations, apply them, and update your running server.
4
+
5
+ ### 1) Install the CLI (local repo checkout)
6
+
7
+ - From the project root, install the CLI globally:
8
+ ```bash
9
+ npm install -g ./
10
+ ```
11
+
12
+ After install, the `masterrecord` command becomes available in your shell.
13
+
14
+ ### 2) Prepare your Context and Environment
15
+
16
+ - Ensure your app has a Context class that extends `context` and configures a DB connection (SQLite or MySQL) using either `useSqlite()` or `useMySql()`.
17
+ - Set the environment via the `master` env var when running commands, e.g. `master=development` or `master=production`.
18
+ - Provide environment JSON at `env.<ENV>.json` reachable from your app root, keyed by your Context class name. Example for SQLite:
19
+ ```json
20
+ {
21
+ "AppContext": {
22
+ "type": "better-sqlite3",
23
+ "connection": "/db/app.sqlite"
24
+ }
25
+ }
26
+ ```
27
+ Example for MySQL:
28
+ ```json
29
+ {
30
+ "AppContext": {
31
+ "type": "mysql",
32
+ "host": "localhost",
33
+ "user": "root",
34
+ "password": "secret",
35
+ "database": "app_db"
36
+ }
37
+ }
38
+ ```
39
+
40
+ ### 3) Enable migrations (one-time per Context)
41
+
42
+ - Run from the project root where your Context file lives. Use the Context file name (without extension) as the argument.
43
+ ```bash
44
+ master=development masterrecord enable-migrations AppContext
45
+ ```
46
+ This creates `db/migrations/<context>_contextSnapShot.json` and the `db/migrations` directory.
47
+
48
+ ### 4) Create a migration
49
+
50
+ - After you change your entity models, generate a migration file:
51
+ ```bash
52
+ master=development masterrecord add-migration <MigrationName> AppContext
53
+ ```
54
+ This writes a new file to `db/migrations/<timestamp>_<MigrationName>_migration.js`.
55
+
56
+ ### 5) Apply migrations to the database
57
+
58
+ - Apply only the latest pending migration:
59
+ ```bash
60
+ master=development masterrecord update-database AppContext
61
+ ```
62
+ - Apply all migrations from the beginning (useful for a clean DB):
63
+ ```bash
64
+ master=development masterrecord update-database-restart AppContext
65
+ ```
66
+ - List migration files (debug/inspection):
67
+ ```bash
68
+ master=development masterrecord get-migrations AppContext
69
+ ```
70
+
71
+ Notes:
72
+ - The CLI searches for `<context>_contextSnapShot.json` under `db/migrations` relative to your current working directory.
73
+ - For MySQL, ensure your credentials allow DDL. For SQLite, the data directory is created if missing.
74
+
75
+ ### 6) Updating the running server
76
+
77
+ General flow to roll out schema changes:
78
+ - Stop the server or put it into maintenance mode (optional but recommended for non-backward-compatible changes).
79
+ - Pull the latest code (containing updated models and generated migration files).
80
+ - Run migrations against the target environment:
81
+ ```bash
82
+ master=production masterrecord update-database AppContext
83
+ ```
84
+ - Restart your server/process manager (e.g., `pm2 restart <app>`, `docker compose up -d`, or your platform’s restart command).
85
+
86
+ Backward-compatible rollout tip:
87
+ - If possible, deploy additive changes first (new tables/columns), release app code that begins using them, then later clean up/removal migrations.
88
+
89
+ ### Troubleshooting
90
+
91
+ - Cannot find Context file: ensure you run commands from the app root and pass the correct Context file name used when defining your class (case-insensitive in the snapshot, but supply the same name you used).
92
+ - Cannot connect to DB: confirm `master=<env>` is set and `env.<env>.json` exists with correct credentials and paths.
93
+ - MySQL type mismatches: the migration engine maps MasterRecord types to SQL types; verify your entity field `type` values are correct.
94
+
95
+ ### Recent improvements (2025-09)
96
+
97
+ - Query language and SQL engines:
98
+ - Correct parsing of multi-char operators (>=, <=, ===, !==) and spaced logical operators.
99
+ - Support for grouped OR conditions rendered as parenthesized OR in WHERE across SQLite/MySQL.
100
+ - Resilient fallback for partially parsed expressions.
101
+ - Relationships:
102
+ - `hasManyThrough` supported in insert and delete cascades.
103
+ - Environment file discovery:
104
+ - Context now walks up directories to find `config/environments/env.<env>.json`; fixed error throwing.
105
+ - Migrations (DDL generation):
106
+ - Default values emitted for SQLite/MySQL (including boolean coercion).
107
+ - `CREATE TABLE IF NOT EXISTS` to avoid failures when rerunning.
108
+ - Table introspection added; existing tables are synced: missing columns are added, MySQL applies `ALTER ... MODIFY` for NULL/DEFAULT changes, SQLite rebuilds table when necessary.
109
+ - Migration API additions in `schema.js`:
110
+ - `renameColumn(table)` implemented for SQLite/MySQL.
111
+ - `seed(tableName, rows)` implemented for bulk/single inserts with safe quoting.
112
+
113
+ ### Using renameColumn and seed in migrations
114
+
115
+ Basic migration skeleton (generated by CLI):
116
+ ```js
117
+ var masterrecord = require('masterrecord');
118
+
119
+ class AddSettings extends masterrecord.schema {
120
+ constructor(context){ super(context); }
121
+
122
+ up(table){
123
+ this.init(table);
124
+ // Add a new table
125
+ this.createTable(table.MailSettings);
126
+
127
+ // Rename a column on an existing table
128
+ this.renameColumn({ tableName: 'MailSettings', name: 'from_email', newName: 'reply_to' });
129
+
130
+ // Seed initial data (single row)
131
+ this.seed('MailSettings', {
132
+ from_name: 'System',
133
+ reply_to: 'no-reply@example.com',
134
+ return_path_matches_from: 0,
135
+ weekly_summary_enabled: 0,
136
+ created_at: Date.now(),
137
+ updated_at: Date.now()
138
+ });
139
+
140
+ // Seed multiple rows
141
+ this.seed('MailSettings', [
142
+ { from_name: 'Support', reply_to: 'support@example.com', created_at: Date.now(), updated_at: Date.now() },
143
+ { from_name: 'Marketing', reply_to: 'marketing@example.com', created_at: Date.now(), updated_at: Date.now() }
144
+ ]);
145
+ }
146
+
147
+ down(table){
148
+ this.init(table);
149
+ // Revert the rename
150
+ this.renameColumn({ tableName: 'MailSettings', name: 'reply_to', newName: 'from_email' });
151
+
152
+ // Optionally clean up seeded rows
153
+ // this.context._execute("DELETE FROM MailSettings WHERE reply_to IN ('no-reply@example.com','support@example.com','marketing@example.com')");
154
+
155
+ // Drop table if that was part of up
156
+ // this.dropTable(table.MailSettings);
157
+ }
158
+ }
159
+ module.exports = AddSettings;
160
+ ```
161
+
162
+ Notes:
163
+ - `renameColumn` expects an object: `{ tableName, name, newName }` and works in both SQLite and MySQL.
164
+ - `seed(tableName, rows)` accepts:
165
+ - a single object: `{ col: value, ... }`
166
+ - or an array of objects: `[{...}, {...}]`
167
+ Values are auto-quoted; booleans become 1/0.
168
+ - When a table already exists, `update-database` will sync schema:
169
+ - Add missing columns.
170
+ - MySQL: adjust default/nullability via `ALTER ... MODIFY`.
171
+ - SQLite: rebuilds the table when nullability/default/type changes require it.
172
+
173
+ ### Tips
174
+ - Prefer additive changes (add columns) before destructive changes (drops/renames) to minimize downtime.
175
+ - For large SQLite tables, a rebuild copies data; consider maintenance windows.
176
+ - Use `master=development masterrecord get-migrations AppContext` to inspect migration order.
177
+
178
+
package/Migrations/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // version 0.0.4
3
+ // version 0.0.5
4
4
  // https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/migrations/
5
5
  // how to add environment variables on cli call example - master=development masterrecord add-migration auth authContext
6
6
 
7
- const program = require('commander');
7
+ const { program } = require('commander');
8
8
  let fs = require('fs');
9
9
  let path = require('path');
10
10
  var Migration = require('./migrations');
@@ -16,7 +16,7 @@ const [,, ...args] = process.argv
16
16
 
17
17
 
18
18
  program
19
- .version('0.0.3', '-v, --version', '0.0.3')
19
+ .version('0.0.3')
20
20
  .description('A ORM framework that facilitates the creation and use of business objects whose data requires persistent storage to a database');
21
21
 
22
22
  // Instructions : to run command you must go to main project folder is located and run the command using the context file name.
@@ -1,5 +1,5 @@
1
1
 
2
- // verison 0.0.2
2
+ // verison 0.0.4
3
3
  class migrationMySQLQuery {
4
4
 
5
5
  #tempTableName = "_temp_alter_column_update"
@@ -37,14 +37,24 @@ class migrationMySQLQuery {
37
37
  var unique = table.unique ? " UNIQUE" : "";
38
38
  var type = this.typeManager(table.type);
39
39
  var tableName = table.name;
40
- var defaultValue = "";
41
- if(table.default != null){
42
-
43
- defaultValue = ` DEFAULT ${this.boolType(table.default)}`
44
- }
45
- if(table.relationshipType === 'belongsTo'){
40
+ if(table.relationshipType === 'belongsTo' && table.foreignKey){
46
41
  tableName = table.foreignKey;
47
42
  }
43
+ var defaultValue = "";
44
+ if(table.default !== undefined && table.default !== null){
45
+ let def = table.default;
46
+ if(table.type === 'boolean'){
47
+ def = this.boolType(def);
48
+ defaultValue = ` DEFAULT ${def}`;
49
+ }
50
+ else if(table.type === 'integer' || table.type === 'float' || table.type === 'decimal'){
51
+ defaultValue = ` DEFAULT ${def}`;
52
+ }
53
+ else{
54
+ const esc = String(def).replace(/'/g, "''");
55
+ defaultValue = ` DEFAULT '${esc}'`;
56
+ }
57
+ }
48
58
 
49
59
  return `${tableName} ${type}${nullName}${defaultValue}${unique}${primaryKey}${auto}`;
50
60
  }
@@ -176,7 +186,7 @@ class migrationMySQLQuery {
176
186
  }
177
187
  }
178
188
 
179
- var completeQuery = `CREATE TABLE ${table.__name} (${queryVar.replace(/,\s*$/, "")});`;
189
+ var completeQuery = `CREATE TABLE IF NOT EXISTS ${table.__name} (${queryVar.replace(/,\s*$/, "")});`;
180
190
  return completeQuery;
181
191
 
182
192
  /*
@@ -1,5 +1,5 @@
1
1
 
2
- // verison 0.0.5
2
+ // verison 0.0.7
3
3
  class migrationSQLiteQuery {
4
4
 
5
5
  #tempTableName = "_temp_alter_column_update"
@@ -36,8 +36,28 @@ class migrationSQLiteQuery {
36
36
  var nullName = table.nullable ? "" : " NOT NULL";
37
37
  var unique = table.unique ? " UNIQUE" : "";
38
38
  var type = this.#typeManager(table.type);
39
+ var colName = table.name;
40
+ if(table.relationshipType === 'belongsTo' && table.foreignKey){
41
+ colName = table.foreignKey;
42
+ }
43
+ // DEFAULT clause
44
+ var defaultClause = "";
45
+ if(table.default !== undefined && table.default !== null){
46
+ let def = table.default;
47
+ if(table.type === 'boolean'){
48
+ def = (def === true || def === 'true') ? 1 : 0;
49
+ defaultClause = ` DEFAULT ${def}`;
50
+ }
51
+ else if(table.type === 'integer' || table.type === 'float' || table.type === 'decimal'){
52
+ defaultClause = ` DEFAULT ${def}`;
53
+ }
54
+ else{
55
+ const esc = String(def).replace(/'/g, "''");
56
+ defaultClause = ` DEFAULT '${esc}'`;
57
+ }
58
+ }
39
59
 
40
- return `${table.name} ${type}${nullName}${unique}${primaryKey}${auto}`;
60
+ return `${colName} ${type}${nullName}${defaultClause}${unique}${primaryKey}${auto}`;
41
61
  }
42
62
 
43
63
  #typeManager(type){
@@ -75,6 +95,13 @@ class migrationSQLiteQuery {
75
95
 
76
96
 
77
97
  addColum(table){
98
+ // If a full column spec is provided, map it to a proper SQLite column definition
99
+ if(table.column){
100
+ const def = this.#columnMapping(table.column);
101
+ return `ALTER TABLE ${table.tableName}
102
+ ADD COLUMN ${def}`;
103
+ }
104
+ // Fallback legacy behavior: raw name provided must include full definition if caller wants type/constraints
78
105
  return `ALTER TABLE ${table.tableName}
79
106
  ADD COLUMN ${table.name}`;
80
107
 
@@ -111,7 +138,7 @@ class migrationSQLiteQuery {
111
138
  }
112
139
  }
113
140
 
114
- return `CREATE TABLE ${table.__name} (${queryVar.replace(/,\s*$/, "")});`;
141
+ return `CREATE TABLE IF NOT EXISTS ${table.__name} (${queryVar.replace(/,\s*$/, "")});`;
115
142
 
116
143
  /*
117
144
  INTEGER PRIMARY KEY AUTOINCREMENT
@@ -1,4 +1,4 @@
1
- // version 0.0.4
1
+ // version 0.0.5
2
2
  class schema{
3
3
 
4
4
  constructor(context){
@@ -65,24 +65,175 @@ class schema{
65
65
  createTable(table){
66
66
 
67
67
  if(table){
68
- if(this.context.isSQLite){
69
- var sqliteQuery = require("./migrationSQLiteQuery");
70
- var queryBuilder = new sqliteQuery();
71
- var query = queryBuilder.createTable(table);
72
- this.context._execute(query);
73
- }
68
+ // If table exists, run sync instead of blind create
69
+ const tableName = table.__name;
70
+ if(this.context._SQLEngine.tableExists && this.context._SQLEngine.tableExists(tableName)){
71
+ this.syncTable(table);
72
+ } else {
73
+ if(this.context.isSQLite){
74
+ var sqliteQuery = require("./migrationSQLiteQuery");
75
+ var queryBuilder = new sqliteQuery();
76
+ var query = queryBuilder.createTable(table);
77
+ this.context._execute(query);
78
+ }
74
79
 
75
- if(this.context.isMySQL){
76
- var sqlquery = require("./migrationMySQLQuery");
77
- var queryBuilder = new sqlquery();
78
- var query = queryBuilder.createTable(table);
79
- this.context._execute(query);
80
+ if(this.context.isMySQL){
81
+ var sqlquery = require("./migrationMySQLQuery");
82
+ var queryBuilder = new sqlquery();
83
+ var query = queryBuilder.createTable(table);
84
+ this.context._execute(query);
85
+ }
80
86
  }
81
87
  }else{
82
88
  console.log("Table that your trying to create is undefined. PLease check if there are any changes that need to be made");
83
89
  }
84
90
  }
85
91
 
92
+ // Compute diffs and apply minimal changes
93
+ syncTable(table){
94
+ const engine = this.context._SQLEngine;
95
+ const tableName = table.__name;
96
+ const existing = engine.getTableInfo ? engine.getTableInfo(tableName) : [];
97
+ // Build a set of existing columns (sqlite: name, mysql: name)
98
+ const existingNames = new Set((existing || []).map(c => (c.name || c.COLUMN_NAME))); // both engines map to name
99
+ // Add missing columns only (safe path)
100
+ for (var key in table) {
101
+ if(typeof table[key] === 'object'){
102
+ const col = table[key];
103
+ // Skip relationships
104
+ if(col.type === 'hasOne' || col.type === 'hasMany' || col.type === 'hasManyThrough') continue;
105
+ const colName = (col.relationshipType === 'belongsTo' && col.foreignKey) ? col.foreignKey : col.name;
106
+ if(!existingNames.has(colName)){
107
+ // add column
108
+ const newCol = {
109
+ tableName: tableName,
110
+ name: colName,
111
+ type: col.type
112
+ };
113
+ // MySQL path uses addColum with realDataType
114
+ if(this.context.isSQLite){
115
+ var sqliteQuery = require("./migrationSQLiteQuery");
116
+ var queryBuilder = new sqliteQuery();
117
+ // Build a conservative column add (no NOT NULL without default)
118
+ const add = queryBuilder.addColum({ tableName, name: colName });
119
+ this.context._execute(add);
120
+ }
121
+ if(this.context.isMySQL){
122
+ var sqlquery = require("./migrationMySQLQuery");
123
+ var queryBuilder = new sqlquery();
124
+ newCol.realDataType = queryBuilder.typeManager(col.type);
125
+ const query = queryBuilder.addColum(newCol);
126
+ this.context._execute(query);
127
+ }
128
+ }
129
+ }
130
+ }
131
+ // Detect modifications (nullable/default/type)
132
+ const desiredCols = [];
133
+ for (var key in table) {
134
+ if(typeof table[key] === 'object'){
135
+ const col = table[key];
136
+ if(col.type === 'hasOne' || col.type === 'hasMany' || col.type === 'hasManyThrough') continue;
137
+ const colName = (col.relationshipType === 'belongsTo' && col.foreignKey) ? col.foreignKey : col.name;
138
+ desiredCols.push({ name: colName, col });
139
+ }
140
+ }
141
+
142
+ const needRebuildSQLite = () => {
143
+ if(!this.context.isSQLite) return false;
144
+ const byName = {};
145
+ for(const row of existing){ byName[row.name] = row; }
146
+ for(const d of desiredCols){
147
+ const row = byName[d.name];
148
+ if(!row) continue;
149
+ const notnull = row.notnull === 1;
150
+ const desiredNotNull = d.col.nullable === false;
151
+ const desiredType = d.col.type;
152
+ const existingType = (row.type || '').toLowerCase();
153
+ // compare default (normalize quotes)
154
+ const exDefRaw = row.dflt_value == null ? null : String(row.dflt_value);
155
+ let exDef = exDefRaw;
156
+ if(typeof exDef === 'string' && exDef.length >= 2 && exDef.startsWith("'") && exDef.endsWith("'")){
157
+ exDef = exDef.slice(1, -1);
158
+ }
159
+ const dsDef = d.col.default == null ? null : String(d.col.default);
160
+ if(desiredNotNull !== notnull) return true;
161
+ if(exDef !== dsDef) return true;
162
+ // rough type differences that require rebuild
163
+ if((desiredType === 'boolean' && existingType !== 'integer') ||
164
+ (desiredType === 'string' && existingType !== 'text') ||
165
+ (desiredType === 'integer' && existingType !== 'integer')){
166
+ return true;
167
+ }
168
+ }
169
+ return false;
170
+ };
171
+
172
+ if(this.context.isMySQL){
173
+ // Apply MODIFY for defaults/nullability
174
+ var sqlquery = require("./migrationMySQLQuery");
175
+ var queryBuilder = new sqlquery();
176
+ const byName = {};
177
+ for(const row of existing){ byName[row.name || row.COLUMN_NAME] = row; }
178
+ for(const d of desiredCols){
179
+ const row = byName[d.name];
180
+ if(!row) continue;
181
+ const desiredNotNull = d.col.nullable === false;
182
+ const existingNullable = (row.is_nullable || row.IS_NULLABLE || '').toString().toUpperCase() === 'YES';
183
+ // default normalize
184
+ const dsDef = d.col.default;
185
+ let exDef2 = row.dflt_value || row.COLUMN_DEFAULT;
186
+ if(typeof exDef2 === 'string' && exDef2.length >= 2 && exDef2.startsWith("'") && exDef2.endsWith("'")){
187
+ exDef2 = exDef2.slice(1, -1);
188
+ }
189
+ const differsNull = (desiredNotNull === true && existingNullable === true) || (desiredNotNull !== true && existingNullable === false);
190
+ const differsDef = (dsDef ?? null) !== (exDef2 ?? null);
191
+ if(differsNull || differsDef){
192
+ const type = queryBuilder.typeManager(d.col.type);
193
+ const nullPart = desiredNotNull ? 'NOT NULL' : 'NULL';
194
+ let defPart = '';
195
+ if(dsDef !== undefined && dsDef !== null){
196
+ if(d.col.type === 'boolean'){
197
+ defPart = ` DEFAULT ${queryBuilder.boolType(dsDef)}`;
198
+ } else if(d.col.type === 'integer' || d.col.type === 'float' || d.col.type === 'decimal'){
199
+ defPart = ` DEFAULT ${dsDef}`;
200
+ } else {
201
+ const esc = String(dsDef).replace(/'/g, "''");
202
+ defPart = ` DEFAULT '${esc}'`;
203
+ }
204
+ } else {
205
+ defPart = ' DEFAULT NULL';
206
+ }
207
+ const alter = `ALTER TABLE ${tableName} MODIFY COLUMN ${d.name} ${type} ${nullPart}${defPart}`;
208
+ this.context._execute(alter);
209
+ }
210
+ }
211
+ }
212
+
213
+ if(needRebuildSQLite()){
214
+ var sqliteQuery = require("./migrationSQLiteQuery");
215
+ var queryBuilder = new sqliteQuery();
216
+ // rename old table
217
+ const rename = queryBuilder.renameTable({ tableName, newName: "_temp_alter_column_update" });
218
+ this.context._execute(rename);
219
+ // create new with desired schema
220
+ const create = queryBuilder.createTable(table);
221
+ this.context._execute(create);
222
+ // compute common columns
223
+ const oldInfo = engine.getTableInfo(tableName.replace(/.*/, '_temp_alter_column_update')) || engine.getTableInfo("_temp_alter_column_update");
224
+ const oldNames = new Set((oldInfo || existing).map(r => r.name));
225
+ const newNames = desiredCols.map(d => d.name);
226
+ const common = newNames.filter(n => oldNames.has(n));
227
+ if(common.length > 0){
228
+ const cols = common.join(',');
229
+ const insert = `INSERT INTO ${tableName} (${cols}) SELECT ${cols} FROM _temp_alter_column_update`;
230
+ this.context._execute(insert);
231
+ }
232
+ const drop = queryBuilder.dropTable("_temp_alter_column_update");
233
+ this.context._execute(drop);
234
+ }
235
+ }
236
+
86
237
 
87
238
  dropTable(table){
88
239
  if(table){
@@ -130,12 +281,46 @@ class schema{
130
281
  }
131
282
  }
132
283
 
133
- renameColumn(){
134
- // TODO
284
+ renameColumn(table){
285
+ if(table){
286
+ if(this.context.isSQLite){
287
+ var sqliteQuery = require("./migrationSQLiteQuery");
288
+ var queryBuilder = new sqliteQuery();
289
+ var query = queryBuilder.renameColumn(table);
290
+ this.context._execute(query);
291
+ }
292
+
293
+ if(this.context.isMySQL){
294
+ var sqlquery = require("./migrationMySQLQuery");
295
+ var queryBuilder = new sqlquery();
296
+ var query = queryBuilder.renameColumn(table);
297
+ this.context._execute(query);
298
+ }
299
+ }
135
300
  }
136
301
 
137
- seed(){
138
- // TODO
302
+ seed(tableName, rows){
303
+ if(!tableName || !rows){ return; }
304
+ const items = Array.isArray(rows) ? rows : [rows];
305
+ for(const row of items){
306
+ const cols = Object.keys(row);
307
+ if(cols.length === 0){ continue; }
308
+ const colList = cols.map(c => this.context.isSQLite ? `[${c}]` : `${c}`).join(", ");
309
+ const vals = cols.map(k => {
310
+ const v = row[k];
311
+ if(v === null || v === undefined){ return 'NULL'; }
312
+ if(typeof v === 'boolean'){
313
+ return this.context.isSQLite ? (v ? 1 : 0) : (v ? 1 : 0);
314
+ }
315
+ if(typeof v === 'number'){
316
+ return String(v);
317
+ }
318
+ const esc = String(v).replace(/'/g, "''");
319
+ return `'${esc}'`;
320
+ }).join(", ");
321
+ const sql = `INSERT INTO ${tableName} (${colList}) VALUES (${vals})`;
322
+ this.context._execute(sql);
323
+ }
139
324
  }
140
325
 
141
326
  }