Skip to content

Commit

Permalink
Add redshift support without changing cli or package.json (#2233)
Browse files Browse the repository at this point in the history
* Add a Redshift dialect that inherits from Postgres.

* Turn .index() and .dropIndex() into no-ops with warnings in the Redshift dialect.

* Update the Redshift dialect to be compatible with master.

* Update package.json

* Disable liftoff cli

* Remove the CLI

* Add lib to the repo

* Allow the escaping of named bindings.

* Update dist

* Update the Redshift dialect’s instantiation of the query and column compilers.

* Update the distribution

* Fix a merge conflict

* Take lib back out

* Trying to bring back in line with tgreisser/knex

* Add npm 5 package-lock

* Bring cli.js back in line

* Bring cli.js back in line

* Progress commit on redshift integration tests

* Revert "Progress commit on redshift integration tests"

This reverts commit 207e316.

* Progress commit

* Working not null on primary columns in createTable

* Working redshift unit tests

* Working unit and integration tests, still need to fix migration tests

* Brought datatypes more in line with what redshift actually supports

* Added query compiler unit tests

* Add a hacky returning clause for redshift ugh

* Working migration integration tests

* Working insert integration tests

* Allow multiple insert returning values

* Working select integration tests

* Working join integration tests

* Working aggregate integration tests

* All integration suite tests working

* Put docker index for reconnect tests back

* Redshift does not support insert...returning, there does not seem to be a way around that, therefore accept it and test accordingly

* Leave redshift integration tests in place, but do not run them by default

* Fix mysql order by test

* Fix more tests

* Change test db name to knex_test for consistency

* Address PR comments
  • Loading branch information
acofer authored and elhigu committed Feb 3, 2018
1 parent bf1fa63 commit 5f81e8a
Show file tree
Hide file tree
Showing 26 changed files with 2,784 additions and 556 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ raw
.DS_Store
.vagrant
node_modules
package-lock.json
test.sqlite3
npm-debug.log
tmp
Expand Down
2 changes: 2 additions & 0 deletions src/dialects/mssql/schema/columncompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ assign(ColumnCompiler_MSSQL.prototype, {

longtext: 'nvarchar(max)',

// TODO: mssql supports check constraints as of SQL Server 2008
// so make enu here more like postgres
enu: 'nvarchar(100)',

uuid: 'uniqueidentifier',
Expand Down
2 changes: 1 addition & 1 deletion src/dialects/postgres/query/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ assign(QueryCompiler_PG.prototype, {
sql += ' and table_schema = ?';
bindings.push(schema);
} else {
sql += ' and table_schema = current_schema';
sql += ' and table_schema = current_schema()';
}

return {
Expand Down
4 changes: 2 additions & 2 deletions src/dialects/postgres/schema/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ SchemaCompiler_PG.prototype.hasTable = function(tableName) {
sql += ' and table_schema = ?';
bindings.push(this.schema);
} else {
sql += ' and table_schema = current_schema';
sql += ' and table_schema = current_schema()';
}

this.pushQuery({
Expand All @@ -40,7 +40,7 @@ SchemaCompiler_PG.prototype.hasColumn = function(tableName, columnName) {
sql += ' and table_schema = ?';
bindings.push(this.schema);
} else {
sql += ' and table_schema = current_schema';
sql += ' and table_schema = current_schema()';
}

this.pushQuery({
Expand Down
74 changes: 74 additions & 0 deletions src/dialects/redshift/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@

// Redshift
// -------
import inherits from 'inherits';
import Client_PG from '../postgres';
import { assign, map, } from 'lodash'

import Transaction from './transaction';
import QueryCompiler from './query/compiler';
import ColumnBuilder from './schema/columnbuilder';
import ColumnCompiler from './schema/columncompiler';
import TableCompiler from './schema/tablecompiler';
import SchemaCompiler from './schema/compiler';

function Client_Redshift(config) {
Client_PG.apply(this, arguments)
}
inherits(Client_Redshift, Client_PG)

assign(Client_Redshift.prototype, {
transaction() {
return new Transaction(this, ...arguments)
},

queryCompiler() {
return new QueryCompiler(this, ...arguments)
},

columnBuilder() {
return new ColumnBuilder(this, ...arguments);
},

columnCompiler() {
return new ColumnCompiler(this, ...arguments)
},

tableCompiler() {
return new TableCompiler(this, ...arguments)
},

schemaCompiler() {
return new SchemaCompiler(this, ...arguments)
},

dialect: 'redshift',

driverName: 'pg-redshift',

_driver() {
return require('pg')
},

// Ensures the response is returned in the same format as other clients.
processResponse(obj, runner) {
const resp = obj.response;
if (obj.output) return obj.output.call(runner, resp);
if (obj.method === 'raw') return resp;
if (resp.command === 'SELECT') {
if (obj.method === 'first') return resp.rows[0];
if (obj.method === 'pluck') return map(resp.rows, obj.pluck);
return resp.rows;
}
if (
resp.command === 'INSERT' ||
resp.command === 'UPDATE' ||
resp.command === 'DELETE'
) {
return resp.rowCount;
}
return resp;
}
})

export default Client_Redshift;
101 changes: 101 additions & 0 deletions src/dialects/redshift/query/compiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@

// Redshift Query Builder & Compiler
// ------
import inherits from 'inherits';

import QueryCompiler from '../../../query/compiler';
import QueryCompiler_PG from '../../postgres/query/compiler';
import * as helpers from '../../../helpers';

import { assign, reduce } from 'lodash';

function QueryCompiler_Redshift(client, builder) {
QueryCompiler_PG.call(this, client, builder);
}
inherits(QueryCompiler_Redshift, QueryCompiler_PG);

assign(QueryCompiler_Redshift.prototype, {
truncate() {
return `truncate ${this.tableName.toLowerCase()}`;
},

// Compiles an `insert` query, allowing for multiple
// inserts using a single query statement.
insert() {
const sql = QueryCompiler.prototype.insert.apply(this, arguments);
if (sql === '') return sql;
this._slightReturn();
return {
sql,
};
},

// Compiles an `update` query, warning on unsupported returning
update() {
const sql = QueryCompiler.prototype.update.apply(this, arguments);
this._slightReturn();
return {
sql,
};
},

// Compiles an `delete` query, warning on unsupported returning
del() {
const sql = QueryCompiler.prototype.del.apply(this, arguments);
this._slightReturn();
return {
sql,
};
},

// simple: if trying to return, warn
_slightReturn(){
if (this.single.isReturning) {
helpers.warn('insert/update/delete returning is not supported by redshift dialect');
}
},

forUpdate() {
helpers.warn('table lock is not supported by redshift dialect');
return '';
},

forShare() {
helpers.warn('lock for share is not supported by redshift dialect');
return '';
},

// Compiles a columnInfo query
columnInfo() {
const column = this.single.columnInfo;

let sql = 'select * from information_schema.columns where table_name = ? and table_catalog = ?';
const bindings = [this.single.table.toLowerCase(), this.client.database().toLowerCase()];

if (this.single.schema) {
sql += ' and table_schema = ?';
bindings.push(this.single.schema);
} else {
sql += ' and table_schema = current_schema()';
}

return {
sql,
bindings,
output(resp) {
const out = reduce(resp.rows, function(columns, val) {
columns[val.column_name] = {
type: val.data_type,
maxLength: val.character_maximum_length,
nullable: (val.is_nullable === 'YES'),
defaultValue: val.column_default
};
return columns;
}, {});
return column && out[column] || out;
}
};
}
})

export default QueryCompiler_Redshift;
23 changes: 23 additions & 0 deletions src/dialects/redshift/schema/columnbuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use strict";

import inherits from 'inherits';
import { warn } from '../../../helpers';
import ColumnBuilder from '../../../schema/columnbuilder';

function ColumnBuilder_Redshift() {
ColumnBuilder.apply(this, arguments);
}
inherits(ColumnBuilder_Redshift, ColumnBuilder);

// primary needs to set not null on non-preexisting columns, or fail
ColumnBuilder_Redshift.prototype.primary = function () {
this.notNullable();
return ColumnBuilder.prototype.primary.apply(this, arguments);
};

ColumnBuilder_Redshift.prototype.index = function () {
warn('Redshift does not support the creation of indexes.');
return this;
}

export default ColumnBuilder_Redshift;
59 changes: 59 additions & 0 deletions src/dialects/redshift/schema/columncompiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

// Redshift Column Compiler
// -------

import inherits from 'inherits';
import ColumnCompiler_PG from '../../postgres/schema/columncompiler';

import { assign } from 'lodash'

function ColumnCompiler_Redshift() {
ColumnCompiler_PG.apply(this, arguments);
}
inherits(ColumnCompiler_Redshift, ColumnCompiler_PG);

assign(ColumnCompiler_Redshift.prototype, {
// Types:
// ------
bigincrements: 'bigint identity(1,1) primary key not null',
binary: 'varchar(max)',
bit(column) {
return column.length !== false ? `char(${column.length})` : 'char(1)';
},
blob: 'varchar(max)',
enu: 'varchar(255)',
enum: 'varchar(255)',
increments: 'integer identity(1,1) primary key not null',
json: 'varchar(max)',
jsonb: 'varchar(max)',
longblob: 'varchar(max)',
mediumblob: 'varchar(16777218)',
set: 'text',
text: 'varchar(max)',
datetime(without) {
return without ? 'timestamp' : 'timestamptz';
},
timestamp(without) {
return without ? 'timestamp' : 'timestamptz';
},
tinyblob: 'varchar(256)',
uuid: 'char(36)',
varbinary: 'varchar(max)',
bigint: 'bigint',
bool: 'boolean',
double: 'double precision',
floating: 'real',
smallint: 'smallint',
tinyint: 'smallint',

// Modifiers:
// ------
comment(comment) {
this.pushAdditional(function() {
this.pushQuery(`comment on column ${this.tableCompiler.tableName()}.` +
this.formatter.wrap(this.args[0]) + " is " + (comment ? `'${comment}'` : 'NULL'));
}, comment);
}
})

export default ColumnCompiler_Redshift;
14 changes: 14 additions & 0 deletions src/dialects/redshift/schema/compiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint max-len: 0 */

// Redshift Table Builder & Compiler
// -------

import inherits from 'inherits';
import SchemaCompiler_PG from '../../postgres/schema/compiler';

function SchemaCompiler_Redshift() {
SchemaCompiler_PG.apply(this, arguments);
}
inherits(SchemaCompiler_Redshift, SchemaCompiler_PG);

export default SchemaCompiler_Redshift;
73 changes: 73 additions & 0 deletions src/dialects/redshift/schema/tablecompiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint max-len: 0 */

// Redshift Table Builder & Compiler
// -------

import { warn } from '../../../helpers';
import inherits from 'inherits';
import { has } from 'lodash';
import TableCompiler_PG from '../../postgres/schema/tablecompiler';

function TableCompiler_Redshift() {
TableCompiler_PG.apply(this, arguments);
}
inherits(TableCompiler_Redshift, TableCompiler_PG);

TableCompiler_Redshift.prototype.index = function(columns, indexName, indexType) {
warn('Redshift does not support the creation of indexes.');
};

TableCompiler_Redshift.prototype.dropIndex = function(columns, indexName) {
warn('Redshift does not support the deletion of indexes.');
};

// TODO: have to disable setting not null on columns that already exist...

// Adds the "create" query to the query sequence.
TableCompiler_Redshift.prototype.createQuery = function(columns, ifNot) {
const createStatement = ifNot ? 'create table if not exists ' : 'create table ';
let sql = createStatement + this.tableName() + ' (' + columns.sql.join(', ') + ')';
if (this.single.inherits) sql += ` like (${this.formatter.wrap(this.single.inherits)})`;
this.pushQuery({
sql,
bindings: columns.bindings
});
const hasComment = has(this.single, 'comment');
if (hasComment) this.comment(this.single.comment);
};

TableCompiler_Redshift.prototype.primary = function(columns, constraintName) {
const self = this;
constraintName = constraintName ? self.formatter.wrap(constraintName) : self.formatter.wrap(`${this.tableNameRaw}_pkey`);
if (columns.constructor !== Array){
columns = [columns];
}
const thiscolumns = self.grouped.columns;

if (thiscolumns) {
for (let i = 0; i < columns.length; i++){
let exists = thiscolumns.find(tcb => tcb.grouping === "columns" &&
tcb.builder &&
tcb.builder._method === "add" &&
tcb.builder._args &&
tcb.builder._args.indexOf(columns[i]) > -1);
if (exists) {
exists = exists.builder;
}
const nullable = !(exists &&
exists._modifiers &&
exists._modifiers["nullable"] &&
exists._modifiers["nullable"][0] === false);
if (nullable){
if (exists){
return warn("Redshift does not allow primary keys to contain nullable columns.");
} else {
return warn("Redshift does not allow primary keys to contain nonexistent columns.");
}
}
}
}
return self.pushQuery(`alter table ${self.tableName()} add constraint ${constraintName} primary key (${self.formatter.columnize(columns)})`);
};

export default TableCompiler_Redshift;
Loading

0 comments on commit 5f81e8a

Please sign in to comment.