-
Notifications
You must be signed in to change notification settings - Fork 183
fix: allow string type attribute to be auto-generated in Postgres #404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| const SG = require('strong-globalize'); | ||
| const g = SG(); | ||
| const async = require('async'); | ||
| const chalk = require('chalk'); | ||
| const debug = require('debug')('loopback:connector:postgresql:migration'); | ||
|
|
||
| module.exports = mixinMigration; | ||
|
|
@@ -295,12 +296,33 @@ function mixinMigration(PostgreSQL) { | |
| const self = this; | ||
| const modelDef = this.getModelDefinition(model); | ||
| const prop = modelDef.properties[propName]; | ||
| let result = self.columnDataType(model, propName); | ||
|
|
||
| // checks if dataType is set to uuid | ||
| let postgDefaultFn; | ||
| let postgType; | ||
| const postgSettings = prop.postgresql; | ||
| if (postgSettings && postgSettings.dataType) { | ||
| postgType = postgSettings.dataType.toUpperCase(); | ||
| } | ||
|
|
||
| if (prop.generated) { | ||
| return 'SERIAL'; | ||
| if (result === 'INTEGER') { | ||
| return 'SERIAL'; | ||
| } else if (postgType === 'UUID') { | ||
| if (postgSettings && postgSettings.defaultFn && postgSettings.extension) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, how do we deal with the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to 'install' the extension to use its functions. e.g, to use foo.bar(), we need to have foo installed first. In here extension is just a name that will be used in postgres query of creating table later. |
||
| // if user provides their own extension and function | ||
| postgDefaultFn = postgSettings.defaultFn; | ||
| return result + ' NOT NULL' + ' DEFAULT ' + postgDefaultFn; | ||
| } | ||
| return result + ' NOT NULL' + ' DEFAULT uuid_generate_v4()'; | ||
| } else { | ||
| console.log(chalk.red('>>> WARNING: ') + | ||
| `auto-generation is not supported for type "${chalk.yellow(prop.type)}". \ | ||
| Please add your own function to the table "${chalk.yellow(model)}".`); | ||
| } | ||
| } | ||
| let result = self.columnDataType(model, propName); | ||
| if (!self.isNullable(prop)) result = result + ' NOT NULL'; | ||
|
|
||
| result += self.columnDbDefault(model, propName); | ||
| return result; | ||
| }; | ||
|
|
@@ -313,32 +335,53 @@ function mixinMigration(PostgreSQL) { | |
| PostgreSQL.prototype.createTable = function(model, cb) { | ||
| const self = this; | ||
| const name = self.tableEscaped(model); | ||
| const modelDef = this.getModelDefinition(model); | ||
|
|
||
| // collects all extensions needed to be created | ||
| let createExtensions; | ||
| Object.keys(this.getModelDefinition(model).properties).forEach(function(propName) { | ||
| const prop = modelDef.properties[propName]; | ||
|
|
||
| // checks if dataType is set to uuid | ||
| const postgSettings = prop.postgresql; | ||
| if (postgSettings && postgSettings.dataType && postgSettings.dataType === 'UUID' | ||
| && postgSettings.defaultFn && postgSettings.extension) { | ||
| createExtensions += 'CREATE EXTENSION IF NOT EXISTS "' + postgSettings.extension + '";'; | ||
| } | ||
| }); | ||
| // default extension | ||
| if (!createExtensions) { | ||
| createExtensions = 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp";'; | ||
| } | ||
|
|
||
| // Please note IF NOT EXISTS is introduced in postgresql v9.3 | ||
| self.execute('CREATE SCHEMA ' + | ||
| self.execute( | ||
| createExtensions + | ||
| 'CREATE SCHEMA ' + | ||
| self.escapeName(self.schema(model)), | ||
| function(err) { | ||
| if (err && err.code !== '42P06') { | ||
| return cb && cb(err); | ||
| } | ||
| self.execute('CREATE TABLE ' + name + ' (\n ' + | ||
| self.propertiesSQL(model) + '\n)', | ||
| function(err, info) { | ||
| if (err) { | ||
| return cb(err, info); | ||
| function(err) { | ||
| if (err && err.code !== '42P06') { | ||
| return cb && cb(err); | ||
nabdelgadir marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| self.addIndexes(model, undefined, function(err) { | ||
| self.execute('CREATE TABLE ' + name + ' (\n ' + | ||
| self.propertiesSQL(model) + '\n)', | ||
| function(err, info) { | ||
| if (err) { | ||
| return cb(err); | ||
| return cb(err, info); | ||
| } | ||
| const fkSQL = self.getForeignKeySQL(model, | ||
| self.getModelDefinition(model).settings.foreignKeys); | ||
| self.addForeignKeys(model, fkSQL, function(err, result) { | ||
| cb(err); | ||
| self.addIndexes(model, undefined, function(err) { | ||
| if (err) { | ||
| return cb(err); | ||
| } | ||
| const fkSQL = self.getForeignKeySQL(model, | ||
| self.getModelDefinition(model).settings.foreignKeys); | ||
| self.addForeignKeys(model, fkSQL, function(err, result) { | ||
| cb(err); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }, | ||
| ); | ||
| }; | ||
|
|
||
| PostgreSQL.prototype.buildIndex = function(model, property) { | ||
|
|
@@ -481,7 +524,7 @@ function mixinMigration(PostgreSQL) { | |
| default: | ||
| case 'String': | ||
| case 'JSON': | ||
| return 'TEXT'; | ||
| case 'Uuid': | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, what's the difference between
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am following the pattern with other type such as
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh I see, the code comparison gave me an impression that they are two cases in the same function, while actually not lol. 👍 |
||
| case 'Text': | ||
| return 'TEXT'; | ||
| case 'Number': | ||
|
|
@@ -645,6 +688,7 @@ function mixinMigration(PostgreSQL) { | |
| case 'CHARACTER': | ||
| case 'CHAR': | ||
| case 'TEXT': | ||
| case 'UUID': | ||
| return 'String'; | ||
|
|
||
| case 'BYTEA': | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,7 @@ describe('migrations', function() { | |
| before(setup); | ||
|
|
||
| it('should run migration', function(done) { | ||
| db.automigrate('UserDataWithIndexes', done); | ||
| db.automigrate(['UserDataWithIndexes', 'OrderData', 'DefaultUuid'], done); | ||
| }); | ||
|
|
||
| it('UserDataWithIndexes should have correct indexes', function(done) { | ||
|
|
@@ -73,6 +73,42 @@ describe('migrations', function() { | |
| done(); | ||
| }); | ||
| }); | ||
|
|
||
| it('OrderData should have correct prop type uuid with custom generation function', function(done) { | ||
nabdelgadir marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| checkColumns('OrderData', function(err, cols) { | ||
| assert.deepEqual(cols, { | ||
| ordercode: | ||
| {column_name: 'ordercode', | ||
| column_default: 'uuid_generate_v1()', | ||
| data_type: 'uuid'}, | ||
| ordername: | ||
| {column_name: 'ordername', | ||
| column_default: null, | ||
| data_type: 'text'}, | ||
| id: | ||
| {column_name: 'id', | ||
| column_default: 'nextval(\'orderdata_id_seq\'::regclass)', | ||
| data_type: 'integer'}, | ||
| }); | ||
| done(); | ||
| }); | ||
| }); | ||
|
|
||
| it('DefaultUuid should have correct id type uuid and default function v4', function(done) { | ||
| checkColumns('DefaultUuid', function(err, cols) { | ||
| assert.deepEqual(cols, { | ||
| defaultcode: | ||
| {column_name: 'defaultcode', | ||
| column_default: 'uuid_generate_v4()', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIUC the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, it should use the default extension and function for this case. And I have |
||
| data_type: 'uuid'}, | ||
| id: | ||
| {column_name: 'id', | ||
| column_default: 'nextval(\'defaultuuid_id_seq\'::regclass)', | ||
| data_type: 'integer'}, | ||
| }); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| function setup(done) { | ||
|
|
@@ -118,6 +154,23 @@ function setup(done) { | |
| }, | ||
| }, | ||
| }); | ||
| const OrderData = db.define('OrderData', { | ||
| ordercode: {type: 'String', required: true, generated: true, useDefaultIdType: false, | ||
| postgresql: { | ||
| dataType: 'uuid', | ||
| defaultFn: 'uuid_generate_v1()', | ||
| extension: 'uuid-ossp', | ||
| }}, | ||
| ordername: {type: 'String'}, | ||
| }); | ||
|
|
||
| const DefaultUuid = db.define('DefaultUuid', { | ||
| defaultCode: {type: 'String', required: true, generated: true, useDefaultIdType: false, | ||
| postgresql: { | ||
| dataType: 'uuid', | ||
| defaultFn: 'uuid_generate_v1()', // lack extension | ||
| }}, | ||
| }); | ||
|
|
||
| done(); | ||
| } | ||
|
|
@@ -161,3 +214,19 @@ function table(model) { | |
| function query(sql, cb) { | ||
| db.adapter.query(sql, cb); | ||
| } | ||
|
|
||
| function checkColumns(table, cb) { | ||
| const tableName = table.toLowerCase(); | ||
| query('SELECT column_name, column_default, data_type FROM information_schema.columns \ | ||
| WHERE(table_schema, table_name) = (\'public\', \'' + tableName + '\');', | ||
| function(err, data) { | ||
| const cols = {}; | ||
| if (!err) { | ||
| data.forEach(function(index) { | ||
| cols[index.column_name] = index; | ||
| delete index.name; | ||
| }); | ||
| } | ||
| cb(err, cols); | ||
| }); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable name here is a bit misleading, maybe usecolumnTypeinstead ofresult?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry never mind, I just realized the code is in a function called
buildColumnDefinition, soresultis better.