@bitblit/ratchet-rdbms 6.0.145-alpha → 6.0.147-alpha
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.
- package/lib/rds-data-api/model/rds-data-api-connection-config.d.ts +2 -0
- package/lib/rds-data-api/rds-data-api-database-access.d.ts +3 -0
- package/lib/rds-data-api/rds-data-api-database-access.js +35 -4
- package/lib/rds-data-api/rds-data-api-database-access.js.map +1 -1
- package/package.json +4 -3
- package/src/build/ratchet-rdbms-info.ts +19 -0
- package/src/model/connection-and-tunnel.ts +7 -0
- package/src/model/database-access-provider.ts +11 -0
- package/src/model/database-access.ts +30 -0
- package/src/model/database-config-list.ts +3 -0
- package/src/model/database-request-type.ts +6 -0
- package/src/model/group-by-count-result.ts +4 -0
- package/src/model/modify-results.ts +9 -0
- package/src/model/named-parameter-database-service-config.ts +13 -0
- package/src/model/paginated-results.ts +5 -0
- package/src/model/pagination-bounds.ts +12 -0
- package/src/model/paginator.ts +20 -0
- package/src/model/query-defaults.ts +4 -0
- package/src/model/query-text-provider.ts +4 -0
- package/src/model/request-results.ts +4 -0
- package/src/model/simple-query-text-provider.ts +18 -0
- package/src/model/sort-direction.ts +4 -0
- package/src/model/ssh/ssh-tunnel-config.ts +8 -0
- package/src/model/ssh/ssh-tunnel-container.ts +13 -0
- package/src/model/transaction-isolation-level.ts +4 -0
- package/src/mysql/model/mysql-db-config.ts +14 -0
- package/src/mysql/model/mysql-master-status.ts +6 -0
- package/src/mysql/model/mysql-slave-status.ts +52 -0
- package/src/mysql/mysql-style-database-access.ts +85 -0
- package/src/mysql/rds-mysql-style-connection-provider.ts +265 -0
- package/src/postgres/model/postgres-db-config.ts +8 -0
- package/src/postgres/postgres-style-connection-provider.ts +270 -0
- package/src/postgres/postgres-style-database-access.spec.ts +76 -0
- package/src/postgres/postgres-style-database-access.ts +110 -0
- package/src/query-builder/query-builder-result.ts +21 -0
- package/src/query-builder/query-builder.spec.ts +194 -0
- package/src/query-builder/query-builder.ts +445 -0
- package/src/query-builder/query-util.spec.ts +20 -0
- package/src/query-builder/query-util.ts +162 -0
- package/src/rds-data-api/model/rds-data-api-connection-config.ts +8 -0
- package/src/rds-data-api/rds-data-api-connection-provider.ts +39 -0
- package/src/rds-data-api/rds-data-api-database-access.spec.ts +139 -0
- package/src/rds-data-api/rds-data-api-database-access.ts +209 -0
- package/src/service/named-parameter-database-service.ts +421 -0
- package/src/service/ssh-tunnel-service.ts +62 -0
- package/src/service/transactional-named-parameter-database-service.ts +171 -0
- package/src/sqlite/model/fetch-remote-mode.ts +4 -0
- package/src/sqlite/model/flush-remote-mode.ts +4 -0
- package/src/sqlite/model/sqlite-connection-config-flag.ts +3 -0
- package/src/sqlite/model/sqlite-connection-config.ts +11 -0
- package/src/sqlite/model/sqlite-local-file-config.ts +3 -0
- package/src/sqlite/model/sqlite-remote-file-sync-config.ts +9 -0
- package/src/sqlite/sqlite-database-access.spec.ts +158 -0
- package/src/sqlite/sqlite-database-access.ts +126 -0
- package/src/sqlite/sqlite-remote-sync-database-access.ts +152 -0
- package/src/sqlite/sqlite-style-connection-provider.ts +181 -0
- package/src/util/aws-rds-cert-2023.ts +502 -0
- package/src/util/named-parameter-adapter/named-parameter-adapter.ts +51 -0
- package/src/util/named-parameter-adapter/query-and-params.ts +4 -0
- package/src/util/relational-database-utils.ts +54 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { DatabaseAccess } from '../model/database-access.js';
|
|
2
|
+
import { RequestResults } from '../model/request-results.js';
|
|
3
|
+
import { ModifyResults } from '../model/modify-results.js';
|
|
4
|
+
import { DatabaseRequestType } from '../model/database-request-type.js';
|
|
5
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
6
|
+
import { PostgresDbConfig } from "./model/postgres-db-config.ts";
|
|
7
|
+
import { Client, Result } from 'pg'
|
|
8
|
+
import SqlString from 'sqlstring';
|
|
9
|
+
import { NamedParameterAdapter } from "../util/named-parameter-adapter/named-parameter-adapter.ts";
|
|
10
|
+
import { QueryAndParams } from "../util/named-parameter-adapter/query-and-params.ts";
|
|
11
|
+
|
|
12
|
+
export class PostgresStyleDatabaseAccess implements DatabaseAccess {
|
|
13
|
+
constructor(
|
|
14
|
+
private _connection: Client,
|
|
15
|
+
private _connectionOptions: PostgresDbConfig,
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
public async testConnection(logTestResults?: boolean): Promise<number | null> {
|
|
19
|
+
if (logTestResults) {
|
|
20
|
+
Logger.info('Running connection test');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const res: RequestResults<any> = await this.query(this.testConnectionQueryString(), {});
|
|
24
|
+
const rows = res.results as { test: number }[];
|
|
25
|
+
const timestamp = rows.length === 1 ? rows[0].test : null;
|
|
26
|
+
if (logTestResults) {
|
|
27
|
+
Logger.info('Test returned : %j', timestamp);
|
|
28
|
+
}
|
|
29
|
+
return timestamp;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public getRawDatabase(): Client {
|
|
33
|
+
return this._connection;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public getRawDatabaseConfig(): PostgresDbConfig {
|
|
37
|
+
return this._connectionOptions;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public testConnectionQueryString(): string {
|
|
41
|
+
return 'SELECT 1 AS test';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public async close(): Promise<boolean> {
|
|
45
|
+
return Promise.resolve(false);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public escape(query: any): string {
|
|
49
|
+
return SqlString.escape(query);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public async preQuery(): Promise<void> {
|
|
53
|
+
// Do nothing
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public async query<R>(query, fields): Promise<RequestResults<R>> {
|
|
57
|
+
const qap: QueryAndParams = NamedParameterAdapter.applyNamedValuesToQuery({query: query, params: fields});
|
|
58
|
+
//const formatted: string = SqlString.format(query, fields);
|
|
59
|
+
const formatted: string = SqlString.format(qap.query, {});
|
|
60
|
+
const res:Result = await this._connection.query(formatted);
|
|
61
|
+
|
|
62
|
+
const rval: RequestResults<R> = {
|
|
63
|
+
results: res.rows as R,
|
|
64
|
+
fields: res.fields
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return rval;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public async modify(query: string, fields: Record<string, any>): Promise<RequestResults<ModifyResults>> {
|
|
71
|
+
const qap: QueryAndParams = NamedParameterAdapter.applyNamedValuesToQuery({query: query, params: fields});
|
|
72
|
+
const formatted: string = SqlString.format(qap.query, {});
|
|
73
|
+
const res:Result = await this._connection.query(formatted);
|
|
74
|
+
|
|
75
|
+
let insertId: number = null;
|
|
76
|
+
if (res?.rows?.length) {
|
|
77
|
+
// This means the insert did a "returning" clause
|
|
78
|
+
const r: Record<string,any> = res.rows[0];
|
|
79
|
+
const keyName: string = Object.keys(r)[0];
|
|
80
|
+
insertId = r[keyName];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const mr: ModifyResults = {
|
|
84
|
+
changedRows: res.rowCount,
|
|
85
|
+
insertId: insertId
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const rval: RequestResults<ModifyResults> = {
|
|
89
|
+
results: mr,
|
|
90
|
+
fields: null
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return rval;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public async onRequestSuccessOrFailure(_type: DatabaseRequestType): Promise<void> {
|
|
97
|
+
// Do nothing
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
public async onQueryFailureOnly(): Promise<void> {
|
|
102
|
+
return Promise.resolve(undefined);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public async onQuerySuccessOnly(): Promise<void> {
|
|
106
|
+
return Promise.resolve(undefined);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
*/
|
|
110
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { TransactionIsolationLevel } from '../model/transaction-isolation-level.js';
|
|
2
|
+
import { Paginator } from '../model/paginator.js';
|
|
3
|
+
|
|
4
|
+
export class QueryBuilderResult {
|
|
5
|
+
query: string;
|
|
6
|
+
namedParams: Record<string, unknown>;
|
|
7
|
+
paginator?: Paginator<any>;
|
|
8
|
+
transactionIsolationLevel: TransactionIsolationLevel;
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
query: string,
|
|
12
|
+
namedParams: Record<string, unknown>,
|
|
13
|
+
paginator: Paginator<any> | undefined,
|
|
14
|
+
transactionIsolationLevel: TransactionIsolationLevel,
|
|
15
|
+
) {
|
|
16
|
+
this.query = query;
|
|
17
|
+
this.namedParams = namedParams;
|
|
18
|
+
this.paginator = paginator;
|
|
19
|
+
this.transactionIsolationLevel = transactionIsolationLevel;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { QueryTextProvider } from '../model/query-text-provider.js';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
import { mock } from 'vitest-mock-extended';
|
|
4
|
+
|
|
5
|
+
import { SortDirection } from '../model/sort-direction.js';
|
|
6
|
+
import { NamedParameterDatabaseService } from '../service/named-parameter-database-service.js';
|
|
7
|
+
import { DatabaseAccessProvider } from '../model/database-access-provider.js';
|
|
8
|
+
import { NamedParameterDatabaseServiceConfig } from '../model/named-parameter-database-service-config.js';
|
|
9
|
+
|
|
10
|
+
const prov: QueryTextProvider = {
|
|
11
|
+
fetchQuery(queryPath: string): string {
|
|
12
|
+
return this.fetchAllQueries()[queryPath];
|
|
13
|
+
},
|
|
14
|
+
fetchAllQueries(): Record<string, string> {
|
|
15
|
+
return {
|
|
16
|
+
'named_parameter_tests.named_fragment_1': 'SELECT b.`id`, b.`owner_id`, b.`name` FROM boards b',
|
|
17
|
+
|
|
18
|
+
'named_parameter_tests.named_test_1': '[[named_parameter_tests.named_fragment_1]] WHERE id=:boardId OR owner_id=:ownerId',
|
|
19
|
+
|
|
20
|
+
'named_parameter_tests.conditional_section_test':
|
|
21
|
+
'SELECT b.`id` FROM boards b WHERE b.id < 1000<<COND1>> AND b.owner_id = :ownerId <</COND1>>',
|
|
22
|
+
|
|
23
|
+
'named_parameter_tests.pagination_count_test':
|
|
24
|
+
'SELECT b.`id` ##pagination## FROM boards b WHERE b.id < 1000 UNION (SELECT x.`id` ##pagination## FROM somewherelse WHERE x.`id` = 3)',
|
|
25
|
+
|
|
26
|
+
'named_parameter_tests.negated_conditional_section_test':
|
|
27
|
+
'SELECT b.`id` FROM boards b WHERE b.id < 1000 <<!COND1>> AND b.owner_id = :ownerId <</!COND1>>',
|
|
28
|
+
|
|
29
|
+
'named_parameter_tests.conditional_param_section_test':
|
|
30
|
+
'SELECT b.`id` FROM boards b WHERE b.id < 1000 <<:ownerId>> AND b.owner_id = :ownerId <</:ownerId>>',
|
|
31
|
+
|
|
32
|
+
'named_parameter_tests.new_repeat_section_test': '<repeat count=:clsLength join=AND> repeated ::clsW:: ::clsH:: clause </repeat>',
|
|
33
|
+
|
|
34
|
+
'named_parameter_tests.sql_construct_test': 'GROUP BY x.##sqlConstruct:groupingColumn##',
|
|
35
|
+
|
|
36
|
+
'named_parameter_tests.constant_test': 'SELECT b.`id`, :constantName as constant FROM boards b where b.`id` = 52',
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const mockAccess: DatabaseAccessProvider = mock<DatabaseAccessProvider>();
|
|
42
|
+
const cfg: NamedParameterDatabaseServiceConfig = {
|
|
43
|
+
serviceName: 'testsrv',
|
|
44
|
+
queryProvider: prov,
|
|
45
|
+
connectionProvider: mockAccess,
|
|
46
|
+
queryDefaults: { databaseName: 'test', timeoutMS: 2_000 },
|
|
47
|
+
longQueryTimeMs: 8_500,
|
|
48
|
+
};
|
|
49
|
+
const mariaDb = new NamedParameterDatabaseService(cfg);
|
|
50
|
+
|
|
51
|
+
describe('query-builder', () => {
|
|
52
|
+
test('builds filtered', () => {
|
|
53
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.pagination_count_test');
|
|
54
|
+
const build = queryBuilder.build();
|
|
55
|
+
expect(build.query).toBe(
|
|
56
|
+
'SELECT /* named_parameter_tests.pagination_count_test */b.`id` FROM boards b WHERE b.id < 1000 UNION (SELECT x.`id` FROM somewherelse WHERE x.`id` = 3)',
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('builds unfiltered', () => {
|
|
61
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.pagination_count_test');
|
|
62
|
+
const build = queryBuilder.buildUnfiltered();
|
|
63
|
+
expect(build.query).toBe(
|
|
64
|
+
'SELECT /* named_parameter_tests.pagination_count_test */COUNT(*) FROM boards b WHERE b.id < 1000 UNION (SELECT x.`id` FROM somewherelse WHERE x.`id` = 3)',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('fails if param is missing', () => {
|
|
69
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.pagination_count_test');
|
|
70
|
+
const build = queryBuilder.build();
|
|
71
|
+
expect(build.query).toBe(
|
|
72
|
+
'SELECT /* named_parameter_tests.pagination_count_test */b.`id` FROM boards b WHERE b.id < 1000 UNION (SELECT x.`id` FROM somewherelse WHERE x.`id` = 3)',
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('removes conditional blocks when missing', () => {
|
|
77
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.conditional_section_test');
|
|
78
|
+
const build = queryBuilder.build();
|
|
79
|
+
expect(build.query).toBe('SELECT /* named_parameter_tests.conditional_section_test */b.`id` FROM boards b WHERE b.id < 1000');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('handles negated conditional blocks true state', () => {
|
|
83
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.negated_conditional_section_test');
|
|
84
|
+
queryBuilder.withConditional('COND1');
|
|
85
|
+
const build = queryBuilder.build();
|
|
86
|
+
expect(build.query).toBe('SELECT /* named_parameter_tests.negated_conditional_section_test */b.`id` FROM boards b WHERE b.id < 1000');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('handles negated conditional blocks false state', () => {
|
|
90
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.negated_conditional_section_test');
|
|
91
|
+
queryBuilder.withConditional('COND1', false);
|
|
92
|
+
const build = queryBuilder.build();
|
|
93
|
+
expect(build.query).toBe(
|
|
94
|
+
'SELECT /* named_parameter_tests.negated_conditional_section_test */b.`id` FROM boards b WHERE b.id < 1000 AND b.owner_id = :ownerId',
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('leaves conditional blocks when present', () => {
|
|
99
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.conditional_section_test');
|
|
100
|
+
queryBuilder.withConditional('COND1');
|
|
101
|
+
const build = queryBuilder.build();
|
|
102
|
+
expect(build.query).toBe(
|
|
103
|
+
'SELECT /* named_parameter_tests.conditional_section_test */b.`id` FROM boards b WHERE b.id < 1000 AND b.owner_id = :ownerId',
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('leaves param-conditional blocks when param exists', () => {
|
|
108
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.conditional_param_section_test');
|
|
109
|
+
queryBuilder.withParam('ownerId', 'testvalue');
|
|
110
|
+
const build = queryBuilder.build();
|
|
111
|
+
expect(build.query).toBe(
|
|
112
|
+
'SELECT /* named_parameter_tests.conditional_param_section_test */b.`id` FROM boards b WHERE b.id < 1000 AND b.owner_id = :ownerId',
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('removes param-conditional blocks when param missing', () => {
|
|
117
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.conditional_param_section_test');
|
|
118
|
+
const build = queryBuilder.build();
|
|
119
|
+
expect(build.query).toBe('SELECT /* named_parameter_tests.conditional_param_section_test */b.`id` FROM boards b WHERE b.id < 1000');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('removes param-conditional blocks when param is an empty array', () => {
|
|
123
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.conditional_param_section_test');
|
|
124
|
+
queryBuilder.withParam('ownerId', []);
|
|
125
|
+
const build = queryBuilder.build();
|
|
126
|
+
expect(build.query).toBe('SELECT /* named_parameter_tests.conditional_param_section_test */b.`id` FROM boards b WHERE b.id < 1000');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('applies query fragments', () => {
|
|
130
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.named_test_1');
|
|
131
|
+
const build = queryBuilder.build();
|
|
132
|
+
expect(build.query).toBe(
|
|
133
|
+
'SELECT /* named_parameter_tests.named_test_1 */b.`id`, b.`owner_id`, b.`name` FROM boards b WHERE id=:boardId OR owner_id=:ownerId',
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('applies sql constructs', () => {
|
|
138
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.sql_construct_test');
|
|
139
|
+
queryBuilder.withSqlConstruct('groupingColumn', 'test_column');
|
|
140
|
+
const build = queryBuilder.build();
|
|
141
|
+
expect(build.query).toBe('GROUP /* named_parameter_tests.sql_construct_test */BY x.test_column');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('expands object arrays', () => {
|
|
145
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.new_repeat_section_test');
|
|
146
|
+
queryBuilder.withExpandedParam(
|
|
147
|
+
'cls',
|
|
148
|
+
[
|
|
149
|
+
{ w: 1, h: 1 },
|
|
150
|
+
{ w: 2, h: 2 },
|
|
151
|
+
],
|
|
152
|
+
true,
|
|
153
|
+
);
|
|
154
|
+
expect(queryBuilder.fetchCopyOfParam('clsW0')).toBe(1);
|
|
155
|
+
expect(queryBuilder.fetchCopyOfParam('clsW1')).toBe(2);
|
|
156
|
+
expect(queryBuilder.fetchCopyOfParam('clsH0')).toBe(1);
|
|
157
|
+
expect(queryBuilder.fetchCopyOfParam('clsH1')).toBe(2);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('expands string arrays', () => {
|
|
161
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.new_repeat_section_test');
|
|
162
|
+
queryBuilder.withExpandedParam('cls', ['s0', 's1'], true);
|
|
163
|
+
expect(queryBuilder.fetchCopyOfParam('cls0')).toBe('s0');
|
|
164
|
+
expect(queryBuilder.fetchCopyOfParam('cls1')).toBe('s1');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('applies repeat blocks', () => {
|
|
168
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.new_repeat_section_test');
|
|
169
|
+
queryBuilder.withExpandedParam(
|
|
170
|
+
'cls',
|
|
171
|
+
[
|
|
172
|
+
{ w: 1, h: 1 },
|
|
173
|
+
{ w: 2, h: 2 },
|
|
174
|
+
],
|
|
175
|
+
true,
|
|
176
|
+
);
|
|
177
|
+
const build = queryBuilder.build();
|
|
178
|
+
expect(build.query).toBe(
|
|
179
|
+
'/* named_parameter_tests.new_repeat_section_test */repeated :clsW1 :clsH1 clause AND repeated :clsW0 :clsH0 clause',
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test.skip('applies pagination', () => {
|
|
184
|
+
const queryBuilder = mariaDb.queryBuilder('named_parameter_tests.conditional_param_section_test');
|
|
185
|
+
queryBuilder.withPaginator({ s: SortDirection.Desc, cn: 'b.id', max: 1000, l: 25 });
|
|
186
|
+
const build = queryBuilder.build();
|
|
187
|
+
expect(build.query).toBe(
|
|
188
|
+
'SELECT /* named_parameter_tests.conditional_param_section_test */b.`id` FROM boards b WHERE b.id < 1000 ORDER BY id Desc LIMIT :queryBuilderLimit',
|
|
189
|
+
);
|
|
190
|
+
expect(build.namedParams).toStrictEqual({
|
|
191
|
+
queryBuilderLimit: 20,
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|