View support (#1626) (#4748)

This commit is contained in:
Olivier Cavadenti 2021-10-20 22:23:29 +02:00 committed by GitHub
parent 2f85fde189
commit 60db2d25a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1722 additions and 21 deletions

View File

@ -28,6 +28,8 @@ const Ref = require('./ref');
const Formatter = require('./formatter');
const Logger = require('./logger');
const { POOL_CONFIG_OPTIONS } = require('./constants');
const ViewBuilder = require('./schema/viewbuilder.js');
const ViewCompiler = require('./schema/viewcompiler.js');
const debug = require('debug')('knex:client');
@ -102,10 +104,18 @@ class Client extends EventEmitter {
return new TableBuilder(this, type, tableName, tableNameLike, fn);
}
viewBuilder(type, viewBuilder, fn) {
return new ViewBuilder(this, type, viewBuilder, fn);
}
tableCompiler(tableBuilder) {
return new TableCompiler(this, tableBuilder);
}
viewCompiler(viewCompiler) {
return new ViewCompiler(this, viewCompiler);
}
columnBuilder(tableBuilder, type, args) {
return new ColumnBuilder(this, tableBuilder, type, args);
}

View File

@ -0,0 +1,15 @@
const ViewCompiler_PG = require('../postgres/schema/pg-viewcompiler.js');
class ViewCompiler_CRDB extends ViewCompiler_PG {
renameColumn(from, to) {
throw new Error('rename column of views is not supported by this dialect.');
}
defaultTo(column, defaultValue) {
throw new Error(
'change default values of views is not supported by this dialect.'
);
}
}
module.exports = ViewCompiler_CRDB;

View File

@ -4,6 +4,7 @@ const Client_PostgreSQL = require('../postgres');
const Transaction = require('../postgres/execution/pg-transaction');
const QueryCompiler = require('./crdb-querycompiler');
const TableCompiler = require('./crdb-tablecompiler');
const ViewCompiler = require('./crdb-viewcompiler');
// Always initialize with the "QueryBuilder" and "QueryCompiler"
// objects, which extend the base 'lib/query/builder' and
@ -21,6 +22,10 @@ class Client_CockroachDB extends Client_PostgreSQL {
return new TableCompiler(this, ...arguments);
}
viewCompiler() {
return new ViewCompiler(this, ...arguments);
}
_parseVersion(versionString) {
return versionString.split(' ')[2];
}

View File

@ -11,6 +11,7 @@ const Transaction = require('./transaction');
const QueryCompiler = require('./query/mssql-querycompiler');
const SchemaCompiler = require('./schema/mssql-compiler');
const TableCompiler = require('./schema/mssql-tablecompiler');
const ViewCompiler = require('./schema/mssql-viewcompiler');
const ColumnCompiler = require('./schema/mssql-columncompiler');
const QueryBuilder = require('../../query/querybuilder');
@ -112,6 +113,9 @@ class Client_MSSQL extends Client {
return new TableCompiler(this, ...arguments);
}
viewCompiler() {
return new ViewCompiler(this, ...arguments);
}
queryBuilder() {
const b = new QueryBuilder(this);
return b;

View File

@ -14,6 +14,13 @@ class SchemaCompiler_MSSQL extends SchemaCompiler {
);
}
dropViewIfExists(viewName) {
const name = this.formatter.wrap(prefixedTableName(this.schema, viewName));
this.pushQuery(
`if object_id('${name}', 'V') is not null DROP VIEW ${name}`
);
}
// Rename a table on the schema.
renameTable(tableName, to) {
this.pushQuery(
@ -25,6 +32,16 @@ class SchemaCompiler_MSSQL extends SchemaCompiler {
);
}
renameView(viewTable, to) {
this.pushQuery(
`exec sp_rename ${this.client.parameter(
prefixedTableName(this.schema, viewTable),
this.builder,
this.bindingsHolder
)}, ${this.client.parameter(to, this.builder, this.bindingsHolder)}`
);
}
// Check whether a table exists on the query.
hasTable(tableName) {
const formattedTable = this.client.parameter(

View File

@ -0,0 +1,41 @@
/* eslint max-len: 0 */
const ViewCompiler = require('../../../schema/viewcompiler.js');
class ViewCompiler_MSSQL extends ViewCompiler {
constructor(client, viewCompiler) {
super(client, viewCompiler);
}
createQuery(columns, selectQuery, materialized, replace) {
const createStatement = 'CREATE ' + (replace ? 'OR ALTER ' : '') + 'VIEW ';
let sql = createStatement + this.viewName();
sql += ' (' + columns.join(', ') + ')';
sql += ' AS ';
sql += selectQuery.toString();
this.pushQuery({
sql,
});
}
renameColumn(from, to) {
this.pushQuery(
`exec sp_rename ${this.client.parameter(
this.viewName() + '.' + from,
this.viewBuilder,
this.bindingsHolder
)}, ${this.client.parameter(
to,
this.viewBuilder,
this.bindingsHolder
)}, 'COLUMN'`
);
}
createOrReplace() {
this.createQuery(this.columns, this.selectQuery, false, true);
}
}
module.exports = ViewCompiler_MSSQL;

View File

@ -12,6 +12,8 @@ const TableCompiler = require('./schema/mysql-tablecompiler');
const ColumnCompiler = require('./schema/mysql-columncompiler');
const { makeEscape } = require('../../util/string');
const ViewCompiler = require('./schema/mysql-viewcompiler');
const ViewBuilder = require('./schema/mysql-viewbuilder');
// Always initialize with the "QueryBuilder" and "QueryCompiler"
// objects, which extend the base 'lib/query/builder' and
@ -33,6 +35,14 @@ class Client_MySQL extends Client {
return new TableCompiler(this, ...arguments);
}
viewCompiler() {
return new ViewCompiler(this, ...arguments);
}
viewBuilder() {
return new ViewBuilder(this, ...arguments);
}
columnCompiler() {
return new ColumnCompiler(this, ...arguments);
}

View File

@ -16,6 +16,10 @@ class SchemaCompiler_MySQL extends SchemaCompiler {
);
}
renameView(from, to) {
this.renameTable(from, to);
}
// Check whether a table exists on the query.
hasTable(tableName) {
let sql = 'select * from information_schema.tables where table_name = ?';

View File

@ -0,0 +1,21 @@
const ViewBuilder = require('../../../schema/viewbuilder.js');
class ViewBuilder_MySQL extends ViewBuilder {
constructor() {
super(...arguments);
}
checkOption() {
this._single.checkOption = 'default_option';
}
localCheckOption() {
this._single.checkOption = 'local';
}
cascadedCheckOption() {
this._single.checkOption = 'cascaded';
}
}
module.exports = ViewBuilder_MySQL;

View File

@ -0,0 +1,15 @@
/* eslint max-len: 0 */
const ViewCompiler = require('../../../schema/viewcompiler.js');
class ViewCompiler_MySQL extends ViewCompiler {
constructor(client, viewCompiler) {
super(client, viewCompiler);
}
createOrReplace() {
this.createQuery(this.columns, this.selectQuery, false, true);
}
}
module.exports = ViewCompiler_MySQL;

View File

@ -77,16 +77,45 @@ class SchemaCompiler_Oracle extends SchemaCompiler {
}
dropTableIfExists(tableName) {
this.dropObject(tableName, 'table');
}
dropViewIfExists(viewName) {
this.dropObject(viewName, 'view');
}
dropObject(objectName, type) {
const prefix = this.schema ? `"${this.schema}".` : '';
let errorCode = -942;
if (type === 'materialized view') {
// https://stackoverflow.com/a/1801453
errorCode = -12003;
}
this.pushQuery(
utils.wrapSqlWithCatch(
`drop table ${prefix}${this.formatter.wrap(tableName)}`,
-942
`drop ${type} ${prefix}${this.formatter.wrap(objectName)}`,
errorCode
)
);
// removing the sequence that was possibly generated by increments() column
this._dropRelatedSequenceIfExists(tableName);
this._dropRelatedSequenceIfExists(objectName);
}
refreshMaterializedView(viewName) {
return this.pushQuery({
sql: `BEGIN DBMS_MVIEW.REFRESH('${
this.schemaNameRaw ? this.schemaNameRaw + '.' : ''
}${viewName}'); END;`,
});
}
dropMaterializedView(viewName) {
this._dropView(viewName, false, true);
}
dropMaterializedViewIfExists(viewName) {
this.dropObject(viewName, 'materialized view');
}
}

View File

@ -12,6 +12,8 @@ const Formatter = require('../../formatter');
const QueryCompiler = require('./query/oracledb-querycompiler');
const TableCompiler = require('./schema/oracledb-tablecompiler');
const ColumnCompiler = require('./schema/oracledb-columncompiler');
const ViewCompiler = require('./schema/oracledb-viewcompiler');
const ViewBuilder = require('./schema/oracledb-viewbuilder');
const { BlobHelper, ReturningHelper, isConnectionError } = require('./utils');
const Transaction = require('./transaction');
const Client_Oracle = require('../oracle');
@ -67,6 +69,14 @@ class Client_Oracledb extends Client_Oracle {
return new ColumnCompiler(this, ...arguments);
}
viewBuilder() {
return new ViewBuilder(this, ...arguments);
}
viewCompiler() {
return new ViewCompiler(this, ...arguments);
}
formatter(builder) {
return new Formatter(this, builder);
}

View File

@ -0,0 +1,13 @@
const ViewBuilder = require('../../../schema/viewbuilder.js');
class ViewBuilder_Oracledb extends ViewBuilder {
constructor() {
super(...arguments);
}
checkOption() {
this._single.checkOption = 'default_option';
}
}
module.exports = ViewBuilder_Oracledb;

View File

@ -0,0 +1,19 @@
/* eslint max-len: 0 */
const ViewCompiler = require('../../../schema/viewcompiler.js');
class ViewCompiler_Oracledb extends ViewCompiler {
constructor(client, viewCompiler) {
super(client, viewCompiler);
}
createOrReplace() {
this.createQuery(this.columns, this.selectQuery, false, true);
}
createMaterializedView() {
this.createQuery(this.columns, this.selectQuery, true);
}
}
module.exports = ViewCompiler_Oracledb;

View File

@ -9,6 +9,8 @@ const Transaction = require('./execution/pg-transaction');
const QueryCompiler = require('./query/pg-querycompiler');
const ColumnCompiler = require('./schema/pg-columncompiler');
const TableCompiler = require('./schema/pg-tablecompiler');
const ViewCompiler = require('./schema/pg-viewcompiler');
const ViewBuilder = require('./schema/pg-viewbuilder');
const SchemaCompiler = require('./schema/pg-compiler');
const { makeEscape } = require('../../util/string');
const { isString } = require('../../util/is');
@ -44,6 +46,14 @@ class Client_PG extends Client {
return new TableCompiler(this, ...arguments);
}
viewCompiler() {
return new ViewCompiler(this, ...arguments);
}
viewBuilder() {
return new ViewBuilder(this, ...arguments);
}
_driver() {
return require('pg');
}

View File

@ -110,6 +110,27 @@ class SchemaCompiler_PG extends SchemaCompiler {
`create extension if not exists ${this.formatter.wrap(extensionName)}`
);
}
renameView(from, to) {
this.pushQuery(
this.alterViewPrefix +
`${this.formatter.wrap(from)} rename to ${this.formatter.wrap(to)}`
);
}
refreshMaterializedView(viewName) {
this.pushQuery({
sql: `refresh materialized view ${this.formatter.wrap(viewName)}`,
});
}
dropMaterializedView(viewName) {
this._dropView(viewName, false, true);
}
dropMaterializedViewIfExists(viewName) {
this._dropView(viewName, true, true);
}
}
module.exports = SchemaCompiler_PG;

View File

@ -0,0 +1,21 @@
const ViewBuilder = require('../../../schema/viewbuilder.js');
class ViewBuilder_PG extends ViewBuilder {
constructor() {
super(...arguments);
}
checkOption() {
this._single.checkOption = 'default_option';
}
localCheckOption() {
this._single.checkOption = 'local';
}
cascadedCheckOption() {
this._single.checkOption = 'cascaded';
}
}
module.exports = ViewBuilder_PG;

View File

@ -0,0 +1,35 @@
/* eslint max-len: 0 */
const ViewCompiler = require('../../../schema/viewcompiler.js');
class ViewCompiler_PG extends ViewCompiler {
constructor(client, viewCompiler) {
super(client, viewCompiler);
}
renameColumn(from, to) {
return this.pushQuery({
sql: `alter view ${this.viewName()} rename ${this.formatter.wrap(
from
)} to ${this.formatter.wrap(to)}`,
});
}
defaultTo(column, defaultValue) {
return this.pushQuery({
sql: `alter view ${this.viewName()} alter ${this.formatter.wrap(
column
)} set default ${defaultValue}`,
});
}
createOrReplace() {
this.createQuery(this.columns, this.selectQuery, false, true);
}
createMaterializedView() {
this.createQuery(this.columns, this.selectQuery, true);
}
}
module.exports = ViewCompiler_PG;

View File

@ -9,6 +9,7 @@ const ColumnBuilder = require('./schema/redshift-columnbuilder');
const ColumnCompiler = require('./schema/redshift-columncompiler');
const TableCompiler = require('./schema/redshift-tablecompiler');
const SchemaCompiler = require('./schema/redshift-compiler');
const ViewCompiler = require('./schema/redshift-viewcompiler');
class Client_Redshift extends Client_PG {
transaction() {
@ -35,6 +36,10 @@ class Client_Redshift extends Client_PG {
return new SchemaCompiler(this, ...arguments);
}
viewCompiler() {
return new ViewCompiler(this, ...arguments);
}
_driver() {
return require('pg');
}

View File

@ -0,0 +1,11 @@
/* eslint max-len: 0 */
const ViewCompiler_PG = require('../../postgres/schema/pg-viewcompiler.js');
class ViewCompiler_Redshift extends ViewCompiler_PG {
constructor(client, viewCompiler) {
super(client, viewCompiler);
}
}
module.exports = ViewCompiler_Redshift;

View File

@ -12,6 +12,7 @@ const SqliteQueryCompiler = require('./query/sqlite-querycompiler');
const SchemaCompiler = require('./schema/sqlite-compiler');
const ColumnCompiler = require('./schema/sqlite-columncompiler');
const TableCompiler = require('./schema/sqlite-tablecompiler');
const ViewCompiler = require('./schema/sqlite-viewcompiler');
const SQLite3_DDL = require('./schema/ddl');
const Formatter = require('../../formatter');
@ -43,6 +44,10 @@ class Client_SQLite3 extends Client {
return new SqliteQueryCompiler(this, builder, formatter);
}
viewCompiler(builder, formatter) {
return new ViewCompiler(this, builder, formatter);
}
columnCompiler() {
return new ColumnCompiler(this, ...arguments);
}

View File

@ -0,0 +1,11 @@
/* eslint max-len: 0 */
const ViewCompiler = require('../../../schema/viewcompiler.js');
class ViewCompiler_PG extends ViewCompiler {
constructor(client, viewCompiler) {
super(client, viewCompiler);
}
}
module.exports = ViewCompiler_PG;

View File

@ -46,6 +46,14 @@ class SchemaBuilder extends EventEmitter {
'createTable',
'createTableIfNotExists',
'createTableLike',
'createView',
'createViewOrReplace',
'createMaterializedView',
'refreshMaterializedView',
'dropView',
'dropViewIfExists',
'dropMaterializedView',
'dropMaterializedViewIfExists',
'createSchema',
'createSchemaIfNotExists',
'dropSchema',
@ -56,10 +64,13 @@ class SchemaBuilder extends EventEmitter {
'dropExtensionIfExists',
'table',
'alterTable',
'view',
'alterView',
'hasTable',
'hasColumn',
'dropTable',
'renameTable',
'renameView',
'dropTableIfExists',
'raw',
].forEach(function (method) {
@ -76,6 +87,7 @@ class SchemaBuilder extends EventEmitter {
);
}
if (method === 'table') method = 'alterTable';
if (method === 'view') method = 'alterView';
this._sequence.push({
method,
args: toArray(arguments),

View File

@ -52,6 +52,40 @@ class SchemaCompiler {
);
}
dropView(viewName) {
this._dropView(viewName, false, false);
}
dropViewIfExists(viewName) {
this._dropView(viewName, true, false);
}
dropMaterializedView(viewName) {
throw new Error('materialized views are not supported by this dialect.');
}
dropMaterializedViewIfExists(viewName) {
throw new Error('materialized views are not supported by this dialect.');
}
renameView(from, to) {
throw new Error(
'rename view is not supported by this dialect (instead drop then create another view).'
);
}
refreshMaterializedView() {
throw new Error('materialized views are not supported by this dialect.');
}
_dropView(viewName, ifExists, materialized) {
this.pushQuery(
(materialized ? this.dropMaterializedViewPrefix : this.dropViewPrefix) +
(ifExists ? 'if exists ' : '') +
this.formatter.wrap(prefixedTableName(this.schema, viewName))
);
}
raw(sql, bindings) {
this.sequence.push(this.client.raw(sql, bindings).toSQL());
}
@ -78,32 +112,42 @@ class SchemaCompiler {
}
SchemaCompiler.prototype.dropTablePrefix = 'drop table ';
SchemaCompiler.prototype.dropViewPrefix = 'drop view ';
SchemaCompiler.prototype.dropMaterializedViewPrefix = 'drop materialized view ';
SchemaCompiler.prototype.alterViewPrefix = 'alter view ';
SchemaCompiler.prototype.alterTable = buildTable('alter');
SchemaCompiler.prototype.createTable = buildTable('create');
SchemaCompiler.prototype.createTableIfNotExists = buildTable('createIfNot');
SchemaCompiler.prototype.createTableLike = buildTable('createLike');
SchemaCompiler.prototype.createView = buildView('create');
SchemaCompiler.prototype.createViewOrReplace = buildView('createOrReplace');
SchemaCompiler.prototype.createMaterializedView = buildView(
'createMaterializedView'
);
SchemaCompiler.prototype.alterView = buildView('alter');
SchemaCompiler.prototype.pushQuery = pushQuery;
SchemaCompiler.prototype.pushAdditional = pushAdditional;
SchemaCompiler.prototype.unshiftQuery = unshiftQuery;
function buildTable(type) {
function build(builder) {
// pass queryContext down to tableBuilder but do not overwrite it if already set
const queryContext = this.builder.queryContext();
if (queryContext !== undefined && builder.queryContext() === undefined) {
builder.queryContext(queryContext);
}
builder.setSchema(this.schema);
const sql = builder.toSQL();
for (let i = 0, l = sql.length; i < l; i++) {
this.sequence.push(sql[i]);
}
function build(builder) {
// pass queryContext down to tableBuilder but do not overwrite it if already set
const queryContext = this.builder.queryContext();
if (queryContext !== undefined && builder.queryContext() === undefined) {
builder.queryContext(queryContext);
}
builder.setSchema(this.schema);
const sql = builder.toSQL();
for (let i = 0, l = sql.length; i < l; i++) {
this.sequence.push(sql[i]);
}
}
function buildTable(type) {
if (type === 'createLike') {
return function (tableName, tableNameLike, fn) {
const builder = this.client.tableBuilder(
@ -122,6 +166,13 @@ function buildTable(type) {
}
}
function buildView(type) {
return function (viewName, fn) {
const builder = this.client.viewBuilder(type, viewName, fn);
build.call(this, builder);
};
}
function prefixedTableName(prefix, table) {
return prefix ? `${prefix}.${table}` : table;
}

81
lib/schema/viewbuilder.js Normal file
View File

@ -0,0 +1,81 @@
const helpers = require('../util/helpers');
const extend = require('lodash/extend');
class ViewBuilder {
constructor(client, method, viewName, fn) {
this.client = client;
this._method = method;
this._schemaName = undefined;
this._columns = undefined;
this._fn = fn;
this._viewName = viewName;
this._statements = [];
this._single = {};
}
setSchema(schemaName) {
this._schemaName = schemaName;
}
columns(columns) {
this._columns = columns;
}
as(selectQuery) {
this._selectQuery = selectQuery;
}
checkOption() {
throw new Error(
'check option definition is not supported by this dialect.'
);
}
localCheckOption() {
throw new Error(
'check option definition is not supported by this dialect.'
);
}
cascadedCheckOption() {
throw new Error(
'check option definition is not supported by this dialect.'
);
}
toSQL() {
if (this._method === 'alter') {
extend(this, AlterMethods);
}
this._fn.call(this, this);
return this.client.viewCompiler(this).toSQL();
}
}
const AlterMethods = {
column(column) {
const self = this;
return {
rename: function (newName) {
self._statements.push({
grouping: 'alterView',
method: 'renameColumn',
args: [column, newName],
});
return this;
},
defaultTo: function (defaultValue) {
self._statements.push({
grouping: 'alterView',
method: 'defaultTo',
args: [column, defaultValue],
});
return this;
},
};
},
};
helpers.addQueryContext(ViewBuilder);
module.exports = ViewBuilder;

135
lib/schema/viewcompiler.js Normal file
View File

@ -0,0 +1,135 @@
/* eslint max-len:0 */
// View Compiler
// -------
const { pushQuery } = require('./internal/helpers');
const groupBy = require('lodash/groupBy');
const { columnize: columnize_ } = require('../formatter/wrappingFormatter');
class ViewCompiler {
constructor(client, viewBuilder) {
this.client = client;
this.viewBuilder = viewBuilder;
this._commonBuilder = this.viewBuilder;
this.method = viewBuilder._method;
this.schemaNameRaw = viewBuilder._schemaName;
this.viewNameRaw = viewBuilder._viewName;
this.single = viewBuilder._single;
this.selectQuery = viewBuilder._selectQuery;
this.columns = viewBuilder._columns;
this.grouped = groupBy(viewBuilder._statements, 'grouping');
this.formatter = client.formatter(viewBuilder);
this.bindings = [];
this.formatter.bindings = this.bindings;
this.bindingsHolder = this;
this.sequence = [];
}
// Convert the tableCompiler toSQL
toSQL() {
this[this.method]();
return this.sequence;
}
// Column Compilation
// -------
create() {
this.createQuery(this.columns, this.selectQuery);
}
createOrReplace() {
throw new Error('replace views is not supported by this dialect.');
}
createMaterializedView() {
throw new Error('materialized views are not supported by this dialect.');
}
createQuery(columns, selectQuery, materialized, replace) {
const createStatement =
'create ' +
(materialized ? 'materialized ' : '') +
'view ' +
(replace ? 'or replace ' : '');
const formatColumns = [];
for (const c of columns) {
formatColumns.push(
columnize_(c, this.viewBuilder, this.client, this.bindingsHolder)
);
}
let sql =
createStatement + this.viewName() + ' (' + formatColumns.join(', ') + ')';
sql += ' as ';
sql += selectQuery.toString();
switch (this.single.checkOption) {
case 'default_option':
sql += ' with check option';
break;
case 'local':
sql += ' with local check option';
break;
case 'cascaded':
sql += ' with cascaded check option';
break;
default:
break;
}
this.pushQuery({
sql,
});
}
renameView(from, to) {
throw new Error(
'rename view is not supported by this dialect (instead drop, then create another view).'
);
}
refreshMaterializedView() {
throw new Error('materialized views are not supported by this dialect.');
}
alter() {
this.alterView();
}
alterView() {
const alterView = this.grouped.alterView || [];
for (let i = 0, l = alterView.length; i < l; i++) {
const statement = alterView[i];
if (this[statement.method]) {
this[statement.method].apply(this, statement.args);
} else {
this.client.logger.error(`Debug: ${statement.method} does not exist`);
}
}
for (const item in this.single) {
if (typeof this[item] === 'function') this[item](this.single[item]);
}
}
renameColumn(from, to) {
throw new Error('rename column of views is not supported by this dialect.');
}
defaultTo(column, defaultValue) {
throw new Error(
'change default values of views is not supported by this dialect.'
);
}
viewName() {
const name = this.schemaNameRaw
? `${this.schemaNameRaw}.${this.viewNameRaw}`
: this.viewNameRaw;
return this.formatter.wrap(name);
}
}
ViewCompiler.prototype.pushQuery = pushQuery;
module.exports = ViewCompiler;

View File

@ -0,0 +1,275 @@
'use strict';
const { expect } = require('chai');
require('lodash');
const { isOracle, isSQLite, isMssql } = require('../../util/db-helpers');
const { getAllDbs, getKnexForDb } = require('../util/knex-instance-provider');
const logger = require('../../integration/logger');
const { isMysql, isCockroachDB } = require('../../util/db-helpers.js');
const { assertNumber } = require('../../util/assertHelper');
describe('Views', () => {
getAllDbs().forEach((db) => {
describe(db, () => {
let knex;
before(async () => {
knex = logger(getKnexForDb(db));
});
after(async () => {
return knex.destroy();
});
describe('view', () => {
beforeEach(async () => {
await knex.schema.dropViewIfExists('view_test');
await knex.schema.dropTableIfExists('table_view');
await knex.schema.createTable('table_view', (t) => {
t.string('a');
t.integer('b');
});
await knex('table_view').insert([
{ a: 'test', b: 5 },
{ a: 'test2', b: 12 },
{ a: 'test3', b: 45 },
]);
});
afterEach(async () => {
await knex.schema.dropViewIfExists('view_test');
await knex.schema.dropViewIfExists('new_view');
if (!isMssql(knex) && !isSQLite(knex) && !isMysql(knex)) {
await knex.schema.dropMaterializedViewIfExists('mat_view');
}
await knex.schema.dropTableIfExists('table_view');
});
it('create view', async () => {
await knex.schema
.createView('view_test', function (view) {
view.columns(['a', 'b']);
view.as(
knex('table_view').select('a', 'b').where('b', '>', '10')
);
})
.testSql((tester) => {
tester(
['pg', 'pg-redshift', 'cockroachdb', 'oracledb'],
[
'create view "view_test" ("a", "b") as select "a", "b" from "table_view" where "b" > \'10\'',
]
);
tester(
['sqlite3', 'mysql'],
[
"create view `view_test` (`a`, `b`) as select `a`, `b` from `table_view` where `b` > '10'",
]
);
tester('mssql', [
"CREATE VIEW [view_test] (a, b) AS select [a], [b] from [table_view] where [b] > '10'",
]);
});
// We test if the select on the view work and if results are good
await knex
.select(['a', 'b'])
.from('view_test')
.then(function (results) {
assertNumber(knex, results[0].b, 12);
assertNumber(knex, results[1].b, 45);
expect(results[0].a).to.be.equal('test2');
expect(results[1].a).to.be.equal('test3');
});
});
it('create materialized view', async function () {
if (isMssql(knex) || isSQLite(knex) || isMysql(knex)) {
return this.skip();
}
await knex.schema
.createMaterializedView('mat_view', function (view) {
view.columns(['a', 'b']);
view.as(
knex('table_view').select('a', 'b').where('b', '>', '10')
);
})
.testSql((tester) => {
tester(
['pg', 'cockroachdb', 'pg-redshift', 'oracledb'],
[
'create materialized view "mat_view" ("a", "b") as select "a", "b" from "table_view" where "b" > \'10\'',
]
);
});
await knex
.select(['a', 'b'])
.from('mat_view')
.then(function (results) {
expect(results[0].a).to.be.equal('test2');
expect(results[1].a).to.be.equal('test3');
assertNumber(knex, results[0].b, 12);
assertNumber(knex, results[1].b, 45);
});
await knex('table_view').insert([{ a: 'test', b: 32 }]);
// We test we have same values, because the view is not refreshed
await knex
.select(['a', 'b'])
.from('mat_view')
.then(function (results) {
expect(results[0].a).to.be.equal('test2');
expect(results[1].a).to.be.equal('test3');
assertNumber(knex, results[0].b, 12);
assertNumber(knex, results[1].b, 45);
});
await knex.schema.refreshMaterializedView('mat_view');
// Materialized view is refreshed
await knex
.select(['a', 'b'])
.from('mat_view')
.then(function (results) {
expect(results[0].a).to.be.equal('test2');
expect(results[1].a).to.be.equal('test3');
expect(results[2].a).to.be.equal('test');
assertNumber(knex, results[0].b, 12);
assertNumber(knex, results[1].b, 45);
assertNumber(knex, results[2].b, 32);
});
await knex.schema.dropMaterializedView('mat_view');
});
it('alter column view', async function () {
if (
isOracle(knex) ||
isSQLite(knex) ||
isMysql(knex) ||
isCockroachDB(knex)
) {
return this.skip();
}
await knex.schema.createView('view_test', function (view) {
view.columns(['a', 'b']);
view.as(knex('table_view').select('a', 'b').where('b', '>', '10'));
});
await knex.schema.alterView('view_test', function (view) {
view.column('a').rename('new_a');
});
await knex
.select(['new_a', 'b'])
.from('view_test')
.then(function (results) {
expect(results[0].new_a).to.be.equal('test2');
expect(results[1].new_a).to.be.equal('test3');
assertNumber(knex, results[0].b, 12);
assertNumber(knex, results[1].b, 45);
});
});
it('alter view rename', async function () {
if (isOracle(knex) || isSQLite(knex)) {
return this.skip();
}
await knex.schema.createView('view_test', function (view) {
view.columns(['a', 'b']);
view.as(knex('table_view').select('a', 'b').where('b', '>', '10'));
});
await knex.schema.renameView('view_test', 'new_view');
await knex
.select(['a', 'b'])
.from('new_view')
.then(function (results) {
expect(results[0].a).to.be.equal('test2');
expect(results[1].a).to.be.equal('test3');
assertNumber(knex, results[0].b, 12);
assertNumber(knex, results[1].b, 45);
});
await knex.schema.dropView('new_view');
});
it('create view with check options', async function () {
if (isMssql(knex) || isCockroachDB(knex) || isSQLite(knex)) {
return this.skip();
}
await knex.schema
.createView('view_test', function (view) {
view.columns(['a', 'b']);
view.as(
knex('table_view').select('a', 'b').where('b', '>', '10')
);
view.checkOption();
})
.testSql((tester) => {
tester(
['oracledb'],
[
'create view "view_test" ("a", "b") as select "a", "b" from "table_view" where "b" > \'10\' with check option',
]
);
});
if (isOracle(knex)) {
return this.skip();
}
await knex.schema.dropView('view_test');
await knex.schema
.createView('view_test', function (view) {
view.columns(['a', 'b']);
view.as(
knex('table_view').select('a', 'b').where('b', '>', '10')
);
view.localCheckOption();
})
.testSql((tester) => {
tester(
['pg', 'cockroachdb', 'pg-redshift'],
[
'create view "view_test" ("a", "b") as select "a", "b" from "table_view" where "b" > \'10\' with local check option',
]
);
tester(
['mysql'],
[
"create view `view_test` (`a`, `b`) as select `a`, `b` from `table_view` where `b` > '10' with local check option",
]
);
});
await knex.schema.dropView('view_test');
await knex.schema
.createView('view_test', function (view) {
view.columns(['a', 'b']);
view.as(
knex('table_view').select('a', 'b').where('b', '>', '10')
);
view.cascadedCheckOption();
})
.testSql((tester) => {
tester(
['pg', 'cockroachdb', 'pg-redshift'],
[
'create view "view_test" ("a", "b") as select "a", "b" from "table_view" where "b" > \'10\' with cascaded check option',
]
);
tester(
['mysql'],
[
"create view `view_test` (`a`, `b`) as select `a`, `b` from `table_view` where `b` > '10' with cascaded check option",
]
);
});
});
});
});
});
});

View File

@ -3,6 +3,7 @@
const expect = require('chai').expect;
const sinon = require('sinon');
const MSSQL_Client = require('../../../lib/dialects/mssql');
const knex = require('../../../knex');
const client = new MSSQL_Client({ client: 'mssql' });
describe('MSSQL SchemaBuilder', function () {
@ -49,6 +50,119 @@ describe('MSSQL SchemaBuilder', function () {
);
});
describe('views', function () {
let knexMssql;
before(function () {
knexMssql = knex({
client: 'mssql',
connection: {},
});
});
it('basic create view', async function () {
const viewSql = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexMssql('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
"CREATE VIEW [adults] (name) AS select [name] from [users] where [age] > '18'"
);
});
it('create view or replace', async function () {
const viewSql = client
.schemaBuilder()
.createViewOrReplace('adults', function (view) {
view.columns(['name']);
view.as(knexMssql('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
"CREATE OR ALTER VIEW [adults] (name) AS select [name] from [users] where [age] > '18'"
);
});
it('create view with check options', async function () {
expect(() => {
client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexMssql('users').select('name').where('age', '>', '18'));
view.localCheckOption();
})
.toSQL();
}).to.throw('check option definition is not supported by this dialect.');
});
it('drop view', function () {
tableSql = client.schemaBuilder().dropView('users').toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view [users]');
});
it('drop view with schema', function () {
tableSql = client
.schemaBuilder()
.withSchema('myschema')
.dropView('users')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view [myschema].[users]');
});
it('rename column of view', function () {
tableSql = client
.schemaBuilder()
.view('users', function (view) {
view.column('oldName').rename('newName');
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal("exec sp_rename ?, ?, 'COLUMN'");
expect(tableSql[0].bindings[0]).to.equal('[users].oldName');
expect(tableSql[0].bindings[1]).to.equal('newName');
});
it('rename view', function () {
tableSql = client
.schemaBuilder()
.renameView('old_view', 'new_view')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('exec sp_rename ?, ?');
expect(tableSql[0].bindings[0]).to.equal('old_view');
expect(tableSql[0].bindings[1]).to.equal('new_view');
});
it('create materialized view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.createMaterializedView('mat_view', function (view) {
view.columns(['name']);
view.as(knexMssql('users').select('name').where('age', '>', '18'));
})
.toSQL();
}).to.throw('materialized views are not supported by this dialect.');
});
it('refresh view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.refreshMaterializedView('view_to_refresh')
.toSQL();
}).to.throw('materialized views are not supported by this dialect.');
});
});
it('test basic create table with incrementing without primary key', function () {
tableSql = client.schemaBuilder().createTable('users', function (table) {
table.increments('id', { primaryKey: false });

View File

@ -3,6 +3,7 @@ const { expect } = require('chai');
const sinon = require('sinon');
const MySQL_Client = require('../../../lib/dialects/mysql');
const MySQL2_Client = require('../../../lib/dialects/mysql2');
const knex = require('../../../knex');
module.exports = function (dialect) {
describe(dialect + ' SchemaBuilder', function () {
@ -92,6 +93,134 @@ module.exports = function (dialect) {
);
});
describe('views', function () {
let knexMysql;
before(function () {
knexMysql = knex({
client: 'mysql2',
connection: {},
});
});
it('basic create view', async function () {
const viewSql = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexMysql('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
"create view `adults` (`name`) as select `name` from `users` where `age` > '18'"
);
});
it('create view or replace', async function () {
const viewSql = client
.schemaBuilder()
.createViewOrReplace('adults', function (view) {
view.columns(['name']);
view.as(knexMysql('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
"create view or replace `adults` (`name`) as select `name` from `users` where `age` > '18'"
);
});
it('create view with check options', async function () {
const viewSqlLocalCheck = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexMysql('users').select('name').where('age', '>', '18'));
view.localCheckOption();
})
.toSQL();
equal(1, viewSqlLocalCheck.length);
expect(viewSqlLocalCheck[0].sql).to.equal(
"create view `adults` (`name`) as select `name` from `users` where `age` > '18' with local check option"
);
const viewSqlCascadedCheck = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexMysql('users').select('name').where('age', '>', '18'));
view.cascadedCheckOption();
})
.toSQL();
equal(1, viewSqlCascadedCheck.length);
expect(viewSqlCascadedCheck[0].sql).to.equal(
"create view `adults` (`name`) as select `name` from `users` where `age` > '18' with cascaded check option"
);
});
it('drop view', function () {
tableSql = client.schemaBuilder().dropView('users').toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view `users`');
});
it('drop view with schema', function () {
tableSql = client
.schemaBuilder()
.withSchema('myschema')
.dropView('users')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view `myschema`.`users`');
});
it('rename and change default of column of view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.view('users', function (view) {
view.column('oldName').rename('newName').defaultTo('10');
})
.toSQL();
}).to.throw('rename column of views is not supported by this dialect.');
});
it('rename view', function () {
tableSql = client
.schemaBuilder()
.renameView('old_view', 'new_view')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'rename table `old_view` to `new_view`'
);
});
it('create materialized view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.createMaterializedView('mat_view', function (view) {
view.columns(['name']);
view.as(
knexMysql('users').select('name').where('age', '>', '18')
);
})
.toSQL();
}).to.throw('materialized views are not supported by this dialect.');
});
it('refresh view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.refreshMaterializedView('view_to_refresh')
.toSQL();
}).to.throw('materialized views are not supported by this dialect.');
});
});
it('adding json', function () {
tableSql = client
.schemaBuilder()

View File

@ -4,6 +4,7 @@ const { expect } = require('chai');
const sinon = require('sinon');
const Oracle_Client = require('../../../lib/dialects/oracledb');
const knex = require('../../../knex');
const client = new Oracle_Client({ client: 'oracledb' });
describe('OracleDb SchemaBuilder', function () {
@ -34,6 +35,136 @@ describe('OracleDb SchemaBuilder', function () {
);
});
describe('views', function () {
let knexOracleDb;
before(function () {
knexOracleDb = knex({
client: 'oracledb',
connection: {},
});
});
it('basic create view', async function () {
const viewSql = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexOracleDb('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
'create view "adults" ("name") as select "name" from "users" where "age" > \'18\''
);
});
it('create view or replace', async function () {
const viewSql = client
.schemaBuilder()
.createViewOrReplace('adults', function (view) {
view.columns(['name']);
view.as(knexOracleDb('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
'create view or replace "adults" ("name") as select "name" from "users" where "age" > \'18\''
);
});
it('create view with check options', async function () {
tableSql = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexOracleDb('users').select('name').where('age', '>', '18'));
view.checkOption();
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'create view "adults" ("name") as select "name" from "users" where "age" > \'18\' with check option'
);
expect(() => {
client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(
knexOracleDb('users').select('name').where('age', '>', '18')
);
view.cascadedCheckOption();
})
.toSQL();
}).to.throw('check option definition is not supported by this dialect.');
});
it('drop view', function () {
tableSql = client.schemaBuilder().dropView('users').toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view "users"');
});
it('drop view with schema', function () {
tableSql = client
.schemaBuilder()
.withSchema('myschema')
.dropView('users')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view "myschema"."users"');
});
it('rename and change default of column of view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.view('users', function (view) {
view.column('oldName').rename('newName').defaultTo('10');
})
.toSQL();
}).to.throw('rename column of views is not supported by this dialect.');
});
it('rename view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.renameView('old_view', 'new_view')
.toSQL();
}).to.throw(
'rename view is not supported by this dialect (instead drop then create another view).'
);
});
it('create materialized view', function () {
tableSql = client
.schemaBuilder()
.createMaterializedView('mat_view', function (view) {
view.columns(['name']);
view.as(knexOracleDb('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'create materialized view "mat_view" ("name") as select "name" from "users" where "age" > \'18\''
);
});
it('refresh view', function () {
tableSql = client
.schemaBuilder()
.refreshMaterializedView('view_to_refresh')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
"BEGIN DBMS_MVIEW.REFRESH('view_to_refresh'); END;"
);
});
});
it('test basic create table if not exists', function () {
tableSql = client
.schemaBuilder()

View File

@ -193,6 +193,144 @@ describe('PostgreSQL SchemaBuilder', function () {
expect(tableSql[0].sql).to.equal('drop table "users"');
});
describe('views', function () {
let knexPg;
before(function () {
knexPg = knex({
client: 'postgresql',
version: '10.5',
connection: {
pool: {},
},
});
});
it('basic create view', async function () {
const viewSql = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexPg('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
'create view "adults" ("name") as select "name" from "users" where "age" > \'18\''
);
});
it('create view or replace', async function () {
const viewSql = client
.schemaBuilder()
.createViewOrReplace('adults', function (view) {
view.columns(['name']);
view.as(knexPg('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
'create view or replace "adults" ("name") as select "name" from "users" where "age" > \'18\''
);
});
it('create view with check options', async function () {
const viewSqlLocalCheck = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexPg('users').select('name').where('age', '>', '18'));
view.localCheckOption();
})
.toSQL();
equal(1, viewSqlLocalCheck.length);
expect(viewSqlLocalCheck[0].sql).to.equal(
'create view "adults" ("name") as select "name" from "users" where "age" > \'18\' with local check option'
);
const viewSqlCascadedCheck = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexPg('users').select('name').where('age', '>', '18'));
view.cascadedCheckOption();
})
.toSQL();
equal(1, viewSqlCascadedCheck.length);
expect(viewSqlCascadedCheck[0].sql).to.equal(
'create view "adults" ("name") as select "name" from "users" where "age" > \'18\' with cascaded check option'
);
});
it('drop view', function () {
tableSql = client.schemaBuilder().dropView('users').toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view "users"');
});
it('drop view with schema', function () {
tableSql = client
.schemaBuilder()
.withSchema('myschema')
.dropView('users')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view "myschema"."users"');
});
it('rename and change default of column of view', function () {
tableSql = client
.schemaBuilder()
.view('users', function (view) {
view.column('oldName').rename('newName').defaultTo('10');
})
.toSQL();
equal(2, tableSql.length);
expect(tableSql[0].sql).to.equal(
'alter view "users" rename "oldName" to "newName"'
);
expect(tableSql[1].sql).to.equal(
'alter view "users" alter "oldName" set default 10'
);
});
it('rename view', function () {
tableSql = client
.schemaBuilder()
.renameView('old_view', 'new_view')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'alter view "old_view" rename to "new_view"'
);
});
it('create materialized view', function () {
tableSql = client
.schemaBuilder()
.createMaterializedView('mat_view', function (view) {
view.columns(['name']);
view.as(knexPg('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'create materialized view "mat_view" ("name") as select "name" from "users" where "age" > \'18\''
);
});
it('refresh view', function () {
tableSql = client
.schemaBuilder()
.refreshMaterializedView('view_to_refresh')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'refresh materialized view "view_to_refresh"'
);
});
});
it('drop table with schema', function () {
tableSql = client
.schemaBuilder()

View File

@ -6,6 +6,7 @@ const { expect } = require('chai');
let tableSql;
const Redshift_Client = require('../../../lib/dialects/redshift');
const knex = require('../../../knex');
const client = new Redshift_Client({ client: 'redshift' });
const equal = require('assert').equal;
@ -54,6 +55,141 @@ describe('Redshift SchemaBuilder', function () {
);
});
describe('views', function () {
let knexRedShift;
before(function () {
knexRedShift = knex({
client: 'redshift',
connection: {},
});
});
it('basic create view', async function () {
const viewSql = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexRedShift('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
'create view "adults" ("name") as select "name" from "users" where "age" > \'18\''
);
});
it('create view or replace', async function () {
const viewSql = client
.schemaBuilder()
.createViewOrReplace('adults', function (view) {
view.columns(['name']);
view.as(knexRedShift('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
'create view or replace "adults" ("name") as select "name" from "users" where "age" > \'18\''
);
});
it('create view with check options', async function () {
const viewSqlLocalCheck = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexRedShift('users').select('name').where('age', '>', '18'));
view.localCheckOption();
})
.toSQL();
equal(1, viewSqlLocalCheck.length);
expect(viewSqlLocalCheck[0].sql).to.equal(
'create view "adults" ("name") as select "name" from "users" where "age" > \'18\' with local check option'
);
const viewSqlCascadedCheck = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexRedShift('users').select('name').where('age', '>', '18'));
view.cascadedCheckOption();
})
.toSQL();
equal(1, viewSqlCascadedCheck.length);
expect(viewSqlCascadedCheck[0].sql).to.equal(
'create view "adults" ("name") as select "name" from "users" where "age" > \'18\' with cascaded check option'
);
});
it('drop view', function () {
tableSql = client.schemaBuilder().dropView('users').toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view "users"');
});
it('drop view with schema', function () {
tableSql = client
.schemaBuilder()
.withSchema('myschema')
.dropView('users')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view "myschema"."users"');
});
it('rename and change default of column of view', function () {
tableSql = client
.schemaBuilder()
.view('users', function (view) {
view.column('oldName').rename('newName').defaultTo('10');
})
.toSQL();
equal(2, tableSql.length);
expect(tableSql[0].sql).to.equal(
'alter view "users" rename "oldName" to "newName"'
);
expect(tableSql[1].sql).to.equal(
'alter view "users" alter "oldName" set default 10'
);
});
it('rename view', function () {
tableSql = client
.schemaBuilder()
.renameView('old_view', 'new_view')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'alter view "old_view" rename to "new_view"'
);
});
it('create materialized view', function () {
tableSql = client
.schemaBuilder()
.createMaterializedView('mat_view', function (view) {
view.columns(['name']);
view.as(knexRedShift('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'create materialized view "mat_view" ("name") as select "name" from "users" where "age" > \'18\''
);
});
it('refresh view', function () {
tableSql = client
.schemaBuilder()
.refreshMaterializedView('view_to_refresh')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'refresh materialized view "view_to_refresh"'
);
});
});
it('alter table with schema', function () {
tableSql = client
.schemaBuilder()

View File

@ -18,6 +18,7 @@ const {
const _ = require('lodash');
const { equal, deepEqual } = require('assert');
const knex = require('../../../knex');
describe('SQLite SchemaBuilder', function () {
it('basic create table', function () {
@ -49,6 +50,120 @@ describe('SQLite SchemaBuilder', function () {
);
});
describe('views', function () {
let knexSqlite3;
before(function () {
knexSqlite3 = knex({
client: 'sqlite3',
connection: {
filename: '',
},
});
});
it('basic create view', async function () {
const viewSql = client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(knexSqlite3('users').select('name').where('age', '>', '18'));
})
.toSQL();
equal(1, viewSql.length);
expect(viewSql[0].sql).to.equal(
"create view `adults` (`name`) as select `name` from `users` where `age` > '18'"
);
});
it('create view or replace', async function () {
expect(() => {
tableSql = client
.schemaBuilder()
.view('users', function (view) {
view.column('oldName').rename('newName').defaultTo('10');
})
.toSQL();
}).to.throw('rename column of views is not supported by this dialect.');
});
it('create view with check options', async function () {
expect(() => {
client
.schemaBuilder()
.createView('adults', function (view) {
view.columns(['name']);
view.as(
knexSqlite3('users').select('name').where('age', '>', '18')
);
view.localCheckOption();
})
.toSQL();
}).to.throw('check option definition is not supported by this dialect.');
});
it('drop view', function () {
tableSql = client.schemaBuilder().dropView('users').toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view `users`');
});
it('drop view with schema', function () {
tableSql = client
.schemaBuilder()
.withSchema('myschema')
.dropView('users')
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal('drop view `myschema`.`users`');
});
it('rename and change default of column of view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.view('users', function (view) {
view.column('oldName').rename('newName').defaultTo('10');
})
.toSQL();
}).to.throw('rename column of views is not supported by this dialect.');
});
it('rename view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.renameView('old_view', 'new_view')
.toSQL();
}).to.throw(
'rename view is not supported by this dialect (instead drop then create another view).'
);
});
it('create materialized view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.createMaterializedView('mat_view', function (view) {
view.columns(['name']);
view.as(
knexSqlite3('users').select('name').where('age', '>', '18')
);
})
.toSQL();
}).to.throw('materialized views are not supported by this dialect.');
});
it('refresh view', function () {
expect(() => {
tableSql = client
.schemaBuilder()
.refreshMaterializedView('view_to_refresh')
.toSQL();
}).to.throw('materialized views are not supported by this dialect.');
});
});
it('create json table', function () {
tableSql = client
.schemaBuilder()

60
types/index.d.ts vendored
View File

@ -1833,6 +1833,35 @@ export declare namespace Knex {
//
interface SchemaBuilder extends ChainableInterface<void> {
// Views
createView(
viewName: string,
callback: (viewBuilder: ViewBuilder) => any
): SchemaBuilder;
createViewOrReplace(
viewName: string,
callback: (viewBuilder: ViewBuilder) => any
): SchemaBuilder;
createMaterializedView(
viewName: string,
callback: (viewBuilder: ViewBuilder) => any
): SchemaBuilder;
refreshMaterializedView(viewName: string): SchemaBuilder;
dropView(viewName: string): SchemaBuilder;
dropViewIfExists(viewName: string): SchemaBuilder;
dropMaterializedView(viewName: string): SchemaBuilder;
dropMaterializedViewIfExists(viewName: string): SchemaBuilder;
renameView(oldViewName: string, newViewName: string): Promise<void>;
view(
viewName: string,
callback: (viewBuilder: AlterViewBuilder) => any
): Promise<void>;
alterView(
viewName: string,
callback: (tableBuilder: AlterViewBuilder) => any
): SchemaBuilder;
// Tables
createTable(
tableName: string,
callback: (tableBuilder: CreateTableBuilder) => any
@ -1846,8 +1875,6 @@ export declare namespace Knex {
tableNameLike: string,
callback: (tableBuilder: CreateTableBuilder) => any
): SchemaBuilder;
createSchema(schemaName: string): SchemaBuilder;
createSchemaIfNotExists(schemaName: string): SchemaBuilder;
alterTable(
tableName: string,
callback: (tableBuilder: CreateTableBuilder) => any
@ -1855,16 +1882,22 @@ export declare namespace Knex {
renameTable(oldTableName: string, newTableName: string): Promise<void>;
dropTable(tableName: string): SchemaBuilder;
hasTable(tableName: string): Promise<boolean>;
hasColumn(tableName: string, columnName: string): Promise<boolean>;
table(
tableName: string,
callback: (tableBuilder: AlterTableBuilder) => any
): Promise<void>;
dropTableIfExists(tableName: string): SchemaBuilder;
// Schema
createSchema(schemaName: string): SchemaBuilder;
createSchemaIfNotExists(schemaName: string): SchemaBuilder;
dropSchema(schemaName: string, cascade?: boolean): SchemaBuilder;
dropSchemaIfExists(schemaName: string, cascade?: boolean): SchemaBuilder;
raw(statement: string): SchemaBuilder;
withSchema(schemaName: string): SchemaBuilder;
// Others
hasColumn(tableName: string, columnName: string): Promise<boolean>;
raw(statement: string): SchemaBuilder;
queryContext(context: any): SchemaBuilder;
toString(): string;
toSQL(): Sql;
@ -1956,6 +1989,15 @@ export declare namespace Knex {
queryContext(context: any): TableBuilder;
}
interface ViewBuilder<TRecord extends {} = any, TResult = any> {
columns(columns: any): ViewBuilder;
as(selectQuery: QueryBuilder): ViewBuilder;
checkOption(): Promise<void>;
localCheckOption(): Promise<void>;
cascadedCheckOption(): Promise<void>;
queryContext(context: any): ViewBuilder;
}
interface CreateTableBuilder extends TableBuilder {
engine(val: string): CreateTableBuilder;
charset(val: string): CreateTableBuilder;
@ -1964,6 +2006,16 @@ export declare namespace Knex {
}
interface AlterTableBuilder extends TableBuilder {}
interface AlterColumnView extends ViewBuilder {
rename(newName: string): AlterColumnView;
defaultTo(defaultValue: string): AlterColumnView;
}
interface AlterViewBuilder extends ViewBuilder {
column(column: string): AlterColumnView;
}
type deferrableType = 'not deferrable' | 'immediate' | 'deferred';
interface ColumnBuilder {
index(indexName?: string): ColumnBuilder;