mirror of
https://github.com/knex/knex.git
synced 2025-06-26 22:00:25 +00:00
Support of MATERIALIZED and NOT MATERIALIZED with WITH/CTE (#4940)
This commit is contained in:
parent
22f9544356
commit
63980987a6
@ -5,4 +5,34 @@ module.exports = class QueryBuilder_PostgreSQL extends QueryBuilder {
|
||||
this._single.using = tables;
|
||||
return this;
|
||||
}
|
||||
|
||||
withMaterialized(alias, statementOrColumnList, nothingOrStatement) {
|
||||
this._validateWithArgs(
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
'with'
|
||||
);
|
||||
return this.withWrapped(
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
withNotMaterialized(alias, statementOrColumnList, nothingOrStatement) {
|
||||
this._validateWithArgs(
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
'with'
|
||||
);
|
||||
return this.withWrapped(
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
false
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -15,6 +15,7 @@ const TableCompiler = require('./schema/sqlite-tablecompiler');
|
||||
const ViewCompiler = require('./schema/sqlite-viewcompiler');
|
||||
const SQLite3_DDL = require('./schema/ddl');
|
||||
const Formatter = require('../../formatter');
|
||||
const QueryBuilder = require('./query/sqlite-querybuilder');
|
||||
|
||||
class Client_SQLite3 extends Client {
|
||||
constructor(config) {
|
||||
@ -44,6 +45,10 @@ class Client_SQLite3 extends Client {
|
||||
return new SqliteQueryCompiler(this, builder, formatter);
|
||||
}
|
||||
|
||||
queryBuilder() {
|
||||
return new QueryBuilder(this);
|
||||
}
|
||||
|
||||
viewCompiler(builder, formatter) {
|
||||
return new ViewCompiler(this, builder, formatter);
|
||||
}
|
||||
|
33
lib/dialects/sqlite3/query/sqlite-querybuilder.js
Normal file
33
lib/dialects/sqlite3/query/sqlite-querybuilder.js
Normal file
@ -0,0 +1,33 @@
|
||||
const QueryBuilder = require('../../../query/querybuilder.js');
|
||||
|
||||
module.exports = class QueryBuilder_SQLite3 extends QueryBuilder {
|
||||
withMaterialized(alias, statementOrColumnList, nothingOrStatement) {
|
||||
this._validateWithArgs(
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
'with'
|
||||
);
|
||||
return this.withWrapped(
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
withNotMaterialized(alias, statementOrColumnList, nothingOrStatement) {
|
||||
this._validateWithArgs(
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
'with'
|
||||
);
|
||||
return this.withWrapped(
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
false
|
||||
);
|
||||
}
|
||||
};
|
@ -118,25 +118,82 @@ class Builder extends EventEmitter {
|
||||
|
||||
// With
|
||||
// ------
|
||||
|
||||
with(alias, statementOrColumnList, nothingOrStatement) {
|
||||
validateWithArgs(alias, statementOrColumnList, nothingOrStatement, 'with');
|
||||
return this.withWrapped(alias, statementOrColumnList, nothingOrStatement);
|
||||
isValidStatementArg(statement) {
|
||||
return (
|
||||
typeof statement === 'function' ||
|
||||
statement instanceof Builder ||
|
||||
(statement && statement.isRawInstance)
|
||||
);
|
||||
}
|
||||
|
||||
// Helper for compiling any advanced `with` queries.
|
||||
withWrapped(alias, statementOrColumnList, nothingOrStatement) {
|
||||
_validateWithArgs(alias, statementOrColumnList, nothingOrStatement, method) {
|
||||
const [query, columnList] =
|
||||
typeof nothingOrStatement === 'undefined'
|
||||
? [statementOrColumnList, undefined]
|
||||
: [nothingOrStatement, statementOrColumnList];
|
||||
this._statements.push({
|
||||
if (typeof alias !== 'string') {
|
||||
throw new Error(`${method}() first argument must be a string`);
|
||||
}
|
||||
|
||||
if (this.isValidStatementArg(query) && typeof columnList === 'undefined') {
|
||||
// Validated as two-arg variant (alias, statement).
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to interpret as three-arg variant (alias, columnList, statement).
|
||||
const isNonEmptyNameList =
|
||||
Array.isArray(columnList) &&
|
||||
columnList.length > 0 &&
|
||||
columnList.every((it) => typeof it === 'string');
|
||||
if (!isNonEmptyNameList) {
|
||||
throw new Error(
|
||||
`${method}() second argument must be a statement or non-empty column name list.`
|
||||
);
|
||||
}
|
||||
|
||||
if (this.isValidStatementArg(query)) {
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
`${method}() third argument must be a function / QueryBuilder or a raw when its second argument is a column name list`
|
||||
);
|
||||
}
|
||||
|
||||
with(alias, statementOrColumnList, nothingOrStatement) {
|
||||
this._validateWithArgs(
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
'with'
|
||||
);
|
||||
return this.withWrapped(alias, statementOrColumnList, nothingOrStatement);
|
||||
}
|
||||
|
||||
withMaterialized(alias, statementOrColumnList, nothingOrStatement) {
|
||||
throw new Error('With materialized is not supported by this dialect');
|
||||
}
|
||||
|
||||
withNotMaterialized(alias, statementOrColumnList, nothingOrStatement) {
|
||||
throw new Error('With materialized is not supported by this dialect');
|
||||
}
|
||||
|
||||
// Helper for compiling any advanced `with` queries.
|
||||
withWrapped(alias, statementOrColumnList, nothingOrStatement, materialized) {
|
||||
const [query, columnList] =
|
||||
typeof nothingOrStatement === 'undefined'
|
||||
? [statementOrColumnList, undefined]
|
||||
: [nothingOrStatement, statementOrColumnList];
|
||||
const statement = {
|
||||
grouping: 'with',
|
||||
type: 'withWrapped',
|
||||
alias: alias,
|
||||
columnList,
|
||||
value: query,
|
||||
});
|
||||
};
|
||||
if (materialized !== undefined) {
|
||||
statement.materialized = materialized;
|
||||
}
|
||||
this._statements.push(statement);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -144,7 +201,7 @@ class Builder extends EventEmitter {
|
||||
// ------
|
||||
|
||||
withRecursive(alias, statementOrColumnList, nothingOrStatement) {
|
||||
validateWithArgs(
|
||||
this._validateWithArgs(
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
@ -1656,49 +1713,6 @@ class Builder extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
const isValidStatementArg = (statement) =>
|
||||
typeof statement === 'function' ||
|
||||
statement instanceof Builder ||
|
||||
(statement && statement.isRawInstance);
|
||||
|
||||
const validateWithArgs = function (
|
||||
alias,
|
||||
statementOrColumnList,
|
||||
nothingOrStatement,
|
||||
method
|
||||
) {
|
||||
const [query, columnList] =
|
||||
typeof nothingOrStatement === 'undefined'
|
||||
? [statementOrColumnList, undefined]
|
||||
: [nothingOrStatement, statementOrColumnList];
|
||||
if (typeof alias !== 'string') {
|
||||
throw new Error(`${method}() first argument must be a string`);
|
||||
}
|
||||
|
||||
if (isValidStatementArg(query) && typeof columnList === 'undefined') {
|
||||
// Validated as two-arg variant (alias, statement).
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to interpret as three-arg variant (alias, columnList, statement).
|
||||
const isNonEmptyNameList =
|
||||
Array.isArray(columnList) &&
|
||||
columnList.length > 0 &&
|
||||
columnList.every((it) => typeof it === 'string');
|
||||
if (!isNonEmptyNameList) {
|
||||
throw new Error(
|
||||
`${method}() second argument must be a statement or non-empty column name list.`
|
||||
);
|
||||
}
|
||||
|
||||
if (isValidStatementArg(query)) {
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
`${method}() third argument must be a function / QueryBuilder or a raw when its second argument is a column name list`
|
||||
);
|
||||
};
|
||||
|
||||
Builder.prototype.select = Builder.prototype.columns;
|
||||
Builder.prototype.column = Builder.prototype.columns;
|
||||
Builder.prototype.andWhereNot = Builder.prototype.whereNot;
|
||||
|
@ -1170,6 +1170,12 @@ class QueryCompiler {
|
||||
) +
|
||||
')'
|
||||
: '';
|
||||
const materialized =
|
||||
statement.materialized === undefined
|
||||
? ''
|
||||
: statement.materialized
|
||||
? 'materialized '
|
||||
: 'not materialized ';
|
||||
return (
|
||||
(val &&
|
||||
columnize_(
|
||||
@ -1179,7 +1185,9 @@ class QueryCompiler {
|
||||
this.bindingsHolder
|
||||
) +
|
||||
columnList +
|
||||
' as (' +
|
||||
' as ' +
|
||||
materialized +
|
||||
'(' +
|
||||
val +
|
||||
')') ||
|
||||
''
|
||||
|
@ -1255,6 +1255,34 @@ describe('Selects', function () {
|
||||
);
|
||||
});
|
||||
|
||||
describe('with (not) materialized tests', () => {
|
||||
before(async function () {
|
||||
if (!isPostgreSQL(knex) && !isSQLite(knex)) {
|
||||
return this.skip();
|
||||
}
|
||||
await knex('test_default_table').truncate();
|
||||
await knex('test_default_table').insert([
|
||||
{ string: 'something', tinyint: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('with materialized', async function () {
|
||||
const materialized = await knex('t')
|
||||
.withMaterialized('t', knex('test_default_table'))
|
||||
.from('t')
|
||||
.first();
|
||||
expect(materialized.tinyint).to.equal(1);
|
||||
});
|
||||
|
||||
it('with not materialized', async function () {
|
||||
const notMaterialized = await knex('t')
|
||||
.withNotMaterialized('t', knex('test_default_table'))
|
||||
.from('t')
|
||||
.first();
|
||||
expect(notMaterialized.tinyint).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('json selections', () => {
|
||||
before(async () => {
|
||||
await knex.schema.dropTableIfExists('cities');
|
||||
|
@ -628,7 +628,9 @@ describe('knex', () => {
|
||||
await knex('some_nonexisten_table')
|
||||
.select()
|
||||
.catch((err) => {
|
||||
expect(err.stack.split('\n')[1]).to.match(/at createQueryBuilder \(/); // the index 1 might need adjustment if the code is refactored
|
||||
expect(err.stack.split('\n')[1]).to.match(
|
||||
/at Object.queryBuilder \(/
|
||||
); // the index 1 might need adjustment if the code is refactored
|
||||
expect(typeof err.originalStack).to.equal('string');
|
||||
});
|
||||
|
||||
|
@ -9482,6 +9482,40 @@ describe('QueryBuilder', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("wrapped 'withMaterialized' clause update", () => {
|
||||
testsql(
|
||||
qb()
|
||||
.withMaterialized('withClause', function () {
|
||||
this.select('foo').from('users');
|
||||
})
|
||||
.update({ foo: 'updatedFoo' })
|
||||
.where('email', '=', 'foo')
|
||||
.from('users'),
|
||||
{
|
||||
sqlite3:
|
||||
'with `withClause` as materialized (select `foo` from `users`) update `users` set `foo` = ? where `email` = ?',
|
||||
pg: 'with "withClause" as materialized (select "foo" from "users") update "users" set "foo" = ? where "email" = ?',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("wrapped 'withNotMaterialized' clause update", () => {
|
||||
testsql(
|
||||
qb()
|
||||
.withNotMaterialized('withClause', function () {
|
||||
this.select('foo').from('users');
|
||||
})
|
||||
.update({ foo: 'updatedFoo' })
|
||||
.where('email', '=', 'foo')
|
||||
.from('users'),
|
||||
{
|
||||
sqlite3:
|
||||
'with `withClause` as not materialized (select `foo` from `users`) update `users` set `foo` = ? where `email` = ?',
|
||||
pg: 'with "withClause" as not materialized (select "foo" from "users") update "users" set "foo" = ? where "email" = ?',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("wrapped 'with' clause delete", () => {
|
||||
testsql(
|
||||
qb()
|
||||
|
2
types/index.d.ts
vendored
2
types/index.d.ts
vendored
@ -520,6 +520,8 @@ export declare namespace Knex {
|
||||
|
||||
// Withs
|
||||
with: With<TRecord, TResult>;
|
||||
withMaterialized: With<TRecord, TResult>;
|
||||
withNotMaterialized: With<TRecord, TResult>;
|
||||
withRecursive: With<TRecord, TResult>;
|
||||
withRaw: WithRaw<TRecord, TResult>;
|
||||
withSchema: WithSchema<TRecord, TResult>;
|
||||
|
Loading…
x
Reference in New Issue
Block a user