@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.
Files changed (60) hide show
  1. package/lib/rds-data-api/model/rds-data-api-connection-config.d.ts +2 -0
  2. package/lib/rds-data-api/rds-data-api-database-access.d.ts +3 -0
  3. package/lib/rds-data-api/rds-data-api-database-access.js +35 -4
  4. package/lib/rds-data-api/rds-data-api-database-access.js.map +1 -1
  5. package/package.json +4 -3
  6. package/src/build/ratchet-rdbms-info.ts +19 -0
  7. package/src/model/connection-and-tunnel.ts +7 -0
  8. package/src/model/database-access-provider.ts +11 -0
  9. package/src/model/database-access.ts +30 -0
  10. package/src/model/database-config-list.ts +3 -0
  11. package/src/model/database-request-type.ts +6 -0
  12. package/src/model/group-by-count-result.ts +4 -0
  13. package/src/model/modify-results.ts +9 -0
  14. package/src/model/named-parameter-database-service-config.ts +13 -0
  15. package/src/model/paginated-results.ts +5 -0
  16. package/src/model/pagination-bounds.ts +12 -0
  17. package/src/model/paginator.ts +20 -0
  18. package/src/model/query-defaults.ts +4 -0
  19. package/src/model/query-text-provider.ts +4 -0
  20. package/src/model/request-results.ts +4 -0
  21. package/src/model/simple-query-text-provider.ts +18 -0
  22. package/src/model/sort-direction.ts +4 -0
  23. package/src/model/ssh/ssh-tunnel-config.ts +8 -0
  24. package/src/model/ssh/ssh-tunnel-container.ts +13 -0
  25. package/src/model/transaction-isolation-level.ts +4 -0
  26. package/src/mysql/model/mysql-db-config.ts +14 -0
  27. package/src/mysql/model/mysql-master-status.ts +6 -0
  28. package/src/mysql/model/mysql-slave-status.ts +52 -0
  29. package/src/mysql/mysql-style-database-access.ts +85 -0
  30. package/src/mysql/rds-mysql-style-connection-provider.ts +265 -0
  31. package/src/postgres/model/postgres-db-config.ts +8 -0
  32. package/src/postgres/postgres-style-connection-provider.ts +270 -0
  33. package/src/postgres/postgres-style-database-access.spec.ts +76 -0
  34. package/src/postgres/postgres-style-database-access.ts +110 -0
  35. package/src/query-builder/query-builder-result.ts +21 -0
  36. package/src/query-builder/query-builder.spec.ts +194 -0
  37. package/src/query-builder/query-builder.ts +445 -0
  38. package/src/query-builder/query-util.spec.ts +20 -0
  39. package/src/query-builder/query-util.ts +162 -0
  40. package/src/rds-data-api/model/rds-data-api-connection-config.ts +8 -0
  41. package/src/rds-data-api/rds-data-api-connection-provider.ts +39 -0
  42. package/src/rds-data-api/rds-data-api-database-access.spec.ts +139 -0
  43. package/src/rds-data-api/rds-data-api-database-access.ts +209 -0
  44. package/src/service/named-parameter-database-service.ts +421 -0
  45. package/src/service/ssh-tunnel-service.ts +62 -0
  46. package/src/service/transactional-named-parameter-database-service.ts +171 -0
  47. package/src/sqlite/model/fetch-remote-mode.ts +4 -0
  48. package/src/sqlite/model/flush-remote-mode.ts +4 -0
  49. package/src/sqlite/model/sqlite-connection-config-flag.ts +3 -0
  50. package/src/sqlite/model/sqlite-connection-config.ts +11 -0
  51. package/src/sqlite/model/sqlite-local-file-config.ts +3 -0
  52. package/src/sqlite/model/sqlite-remote-file-sync-config.ts +9 -0
  53. package/src/sqlite/sqlite-database-access.spec.ts +158 -0
  54. package/src/sqlite/sqlite-database-access.ts +126 -0
  55. package/src/sqlite/sqlite-remote-sync-database-access.ts +152 -0
  56. package/src/sqlite/sqlite-style-connection-provider.ts +181 -0
  57. package/src/util/aws-rds-cert-2023.ts +502 -0
  58. package/src/util/named-parameter-adapter/named-parameter-adapter.ts +51 -0
  59. package/src/util/named-parameter-adapter/query-and-params.ts +4 -0
  60. package/src/util/relational-database-utils.ts +54 -0
@@ -0,0 +1,139 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { NamedParameterDatabaseService } from "../service/named-parameter-database-service.js";
3
+ import { Logger } from "@bitblit/ratchet-common/logger/logger";
4
+ import { SimpleQueryTextProvider } from "../model/simple-query-text-provider.js";
5
+ import { ModifyResults } from "../model/modify-results.js";
6
+ import { RequestResults } from "../model/request-results.js";
7
+ import { RdsDataApiConnectionProvider } from "./rds-data-api-connection-provider";
8
+ import { RDSDataClient } from "@aws-sdk/client-rds-data";
9
+
10
+ async function buildProvider():Promise<RdsDataApiConnectionProvider> {
11
+ const rdsClient: RDSDataClient = new RDSDataClient({region: 'us-east-2'});
12
+ const prov: RdsDataApiConnectionProvider = new RdsDataApiConnectionProvider(rdsClient, () => {
13
+ return Promise.resolve({
14
+ dbList: [
15
+ {
16
+ label: 'test',
17
+ resourceArn: 'test',
18
+ secretArn: 'test',
19
+ database: 'test'
20
+ },
21
+ ],
22
+ });
23
+ });
24
+ return prov;
25
+ }
26
+
27
+ describe('rds-data-api-database-access', () => {
28
+ const testQueries: SimpleQueryTextProvider = new SimpleQueryTextProvider({
29
+ create: 'create table testable (val varchar(255))',
30
+ singleIns: 'insert into testable (val) values (:val)',
31
+ csvIns: 'insert into testable (val) values (:valCsv)',
32
+ counter: 'select count(1) as cnt from testable',
33
+ multi: 'insert into testable (val) values :multiVal',
34
+ });
35
+
36
+
37
+ test.skip('builds filtered', async () => {
38
+ //process.env['AWS_PROFILE']='test';
39
+ const prov: RdsDataApiConnectionProvider = await buildProvider();
40
+ const ns: NamedParameterDatabaseService = new NamedParameterDatabaseService({
41
+ serviceName: 'Test',
42
+ queryProvider: new SimpleQueryTextProvider({ default: 'select mfa_required, email from people where email=:email' }),
43
+ connectionProvider: prov,
44
+ queryDefaults: { databaseName: 'test', timeoutMS: 20_000 },
45
+ longQueryTimeMs: 8_500,
46
+ });
47
+
48
+ const res: any[] = await ns.buildAndExecute<any>(ns.queryBuilder('default').withParams({ email: 'rachel@viggo.team' }));
49
+
50
+ Logger.info('Get: %j', res);
51
+
52
+ expect(res).not.toBeNull;
53
+ }, 25_000);
54
+
55
+ test.skip('runs test query', async () => {
56
+ try {
57
+ //process.env['AWS_PROFILE']='test';
58
+ const prov: RdsDataApiConnectionProvider = await buildProvider();
59
+ const ns: NamedParameterDatabaseService = new NamedParameterDatabaseService({
60
+ serviceName: 'Test',
61
+ queryProvider: testQueries,
62
+ connectionProvider: prov,
63
+ queryDefaults: { databaseName: 'test', timeoutMS: 20_000 },
64
+ longQueryTimeMs: 8_500,
65
+ });
66
+ const val: any = await ns.testConnection(true);
67
+ Logger.info('Val was : %j', val);
68
+ } catch (err) {
69
+ Logger.error('Test failed',err);
70
+ }
71
+ }, 25_000);
72
+
73
+ test.skip('handles path with sub', async () => {
74
+ //process.env['AWS_PROFILE']='test';
75
+ const prov: RdsDataApiConnectionProvider = await buildProvider();
76
+ const ns: NamedParameterDatabaseService = new NamedParameterDatabaseService({
77
+ serviceName: 'Test',
78
+ queryProvider: testQueries,
79
+ connectionProvider: prov,
80
+
81
+ queryDefaults: { databaseName: 'test', timeoutMS: 20_000 },
82
+ longQueryTimeMs: 8_500,
83
+ });
84
+
85
+ // Create a table
86
+ const _createRes: any = await ns.executeUpdateOrInsertByName('create');
87
+
88
+ const myOb: Record<string, any> = {
89
+ val: ['t1', 't2'],
90
+ };
91
+
92
+ // Test insert
93
+ const singleIns: ModifyResults = await ns.buildAndExecuteUpdateOrInsert(
94
+ ns.queryBuilder('csvIns').withParams(myOb).withParam('valCsv', myOb['val'].join(',')),
95
+ );
96
+ expect(singleIns.affectedRows).toBeGreaterThan(0);
97
+
98
+ const singleCount: RequestResults<any> = await ns.executeQueryByNameSingle('counter', {});
99
+
100
+ expect(singleCount['cnt']).toEqual(1);
101
+
102
+ Logger.info('Get: %j', singleCount);
103
+ }, 30_000);
104
+
105
+ test.skip('handles apostrophes in multi-value inserts', async () => {
106
+ const prov: RdsDataApiConnectionProvider = await buildProvider();
107
+ const ns: NamedParameterDatabaseService = new NamedParameterDatabaseService({
108
+ serviceName: 'Test',
109
+ queryProvider: testQueries,
110
+ connectionProvider: prov,
111
+ queryDefaults: { databaseName: 'test', timeoutMS: 20_000 },
112
+ longQueryTimeMs: 8_500,
113
+ });
114
+
115
+ // Create a table
116
+ const _createRes: any = await ns.executeUpdateOrInsertByName('create');
117
+
118
+ // Test single
119
+ const _singleIns: ModifyResults = await ns.executeUpdateOrInsertByName('singleIns', { val: 'val1' });
120
+
121
+ const singleCount: RequestResults<any> = await ns.executeQueryByNameSingle('counter', {});
122
+
123
+ expect(singleCount['cnt']).toEqual(1);
124
+
125
+ const _multiIns: ModifyResults = await ns.executeUpdateOrInsertByName('multi', { multiVal: [["val's are 2"], ['val3']] });
126
+
127
+ const multiCount: RequestResults<any> = await ns.executeQueryByNameSingle('counter', {});
128
+
129
+ expect(multiCount['cnt']).toEqual(3);
130
+
131
+ const _multiIns2: ModifyResults = await ns.executeUpdateOrInsertByName('multi', { multiVal: [["multi's apo's"]] });
132
+
133
+ const multiCount2: RequestResults<any> = await ns.executeQueryByNameSingle('counter', {});
134
+
135
+ expect(multiCount2['cnt']).toEqual(4);
136
+
137
+ Logger.info('Get: %j', singleCount);
138
+ }, 30_000);
139
+ });
@@ -0,0 +1,209 @@
1
+ import { DatabaseAccess } from "../model/database-access.js";
2
+ import { ModifyResults } from "../model/modify-results.js";
3
+ import { RequestResults } from "../model/request-results.js";
4
+ import {
5
+ BeginTransactionCommand,
6
+ BeginTransactionCommandOutput,
7
+ CommitTransactionCommand, CommitTransactionCommandOutput,
8
+ DatabaseResumingException, ExecuteStatementCommand, ExecuteStatementCommandOutput,
9
+ Field,
10
+ RDSDataClient, RollbackTransactionCommand, RollbackTransactionCommandOutput, SqlParameter
11
+ } from "@aws-sdk/client-rds-data";
12
+ import { Logger } from "@bitblit/ratchet-common/logger/logger";
13
+ import { RdsDataApiConnectionConfig } from "./model/rds-data-api-connection-config.ts";
14
+ import SqlString from 'sqlstring';
15
+ import type { Command } from "@smithy/types";
16
+ import { SmithyResolvedConfiguration } from "@smithy/smithy-client/dist-types/client";
17
+ import { PromiseRatchet } from "@bitblit/ratchet-common/lang/promise-ratchet";
18
+ import { ErrorRatchet } from "@bitblit/ratchet-common/lang/error-ratchet";
19
+
20
+ export class RdsDataApiDatabaseAccess implements DatabaseAccess {
21
+
22
+ private _currentTransactionId: string;
23
+
24
+ constructor(
25
+ private client: RDSDataClient,
26
+ private cfg: RdsDataApiConnectionConfig,
27
+ ) {}
28
+
29
+ private get maxWaitForResumingDatabase(): number {
30
+ return this?.cfg?.maximumWaitForDbResumeInMillis ?? 0; // Default to no waiting
31
+ }
32
+
33
+ private get dbResumePingTimeMillis(): number {
34
+ return this?.cfg?.dbResumePingTimeMillis ?? 1_000; // Default to 1 second
35
+ }
36
+
37
+ public async sendWithDatabaseWait(cmd: any): Promise<any> {
38
+ const startTime: number = Date.now();
39
+ let rval: any;
40
+ do {
41
+ try {
42
+ rval = await this.client.send(cmd);
43
+ } catch (err) {
44
+ if (err instanceof DatabaseResumingException) {
45
+ Logger.debug('Database was resuming - waiting %d ms before retry : %s', this.dbResumePingTimeMillis, err);
46
+ await PromiseRatchet.wait(this.dbResumePingTimeMillis);
47
+ } else {
48
+ throw err;
49
+ }
50
+ }
51
+ } while (!rval && Date.now()-startTime < this.maxWaitForResumingDatabase)
52
+ if (!rval) {
53
+ Logger.error('Timed out waiting for db to start');
54
+ throw ErrorRatchet.fErr('Timed out waiting for db to start');
55
+ }
56
+ return rval;
57
+ }
58
+
59
+
60
+ public async beginTransaction(): Promise<void> {
61
+ const tmp: BeginTransactionCommandOutput = await this.sendWithDatabaseWait(new BeginTransactionCommand({ resourceArn: this.cfg.resourceArn, secretArn: this.cfg.secretArn}))
62
+ this._currentTransactionId = tmp.transactionId;
63
+ Logger.info('Started transaction %s', this._currentTransactionId);
64
+ }
65
+
66
+ public async close(): Promise<boolean> {
67
+ if (!this._currentTransactionId) {
68
+ Logger.info('Close called with transaction still open, rolling back');
69
+ await this.rollbackTransaction();
70
+ }
71
+ return true;
72
+ // TODO: anything else?
73
+ }
74
+
75
+ public async commitTransaction(): Promise<void> {
76
+ if (this._currentTransactionId) {
77
+ const out: CommitTransactionCommandOutput = await this.sendWithDatabaseWait(new CommitTransactionCommand({resourceArn: this.cfg.resourceArn, secretArn: this.cfg.secretArn, transactionId: this._currentTransactionId}))
78
+ Logger.info('Commit transaction %s returned %j', this._currentTransactionId, out);
79
+ this._currentTransactionId = null;
80
+ } else {
81
+ Logger.warn('Commit transaction called while no transaction in session');
82
+ }
83
+ }
84
+
85
+ public escape(value: any): string {
86
+ const rval: string = SqlString.escape(value);
87
+ return rval;
88
+ }
89
+
90
+ public async modify(query: string, fields: Record<string, any>): Promise<RequestResults<ModifyResults>> {
91
+ const params: SqlParameter[] = RdsDataApiDatabaseAccess.toSqlParameters(fields);
92
+ const tmp: ExecuteStatementCommandOutput = await this.sendWithDatabaseWait(new ExecuteStatementCommand({resourceArn: this.cfg.resourceArn, secretArn: this.cfg.secretArn, database: this.cfg.database, sql:query, parameters: params, includeResultMetadata: true}));
93
+ const rval: RequestResults<ModifyResults> = {
94
+ results: {
95
+ changedRows: tmp.numberOfRecordsUpdated,
96
+ //insertId: tmp.generatedFields,
97
+ //fieldCount: tmp.generatedFields.length,
98
+ affectedRows: tmp.numberOfRecordsUpdated
99
+ //info: string;
100
+ //serverStatus?: number;
101
+ //warningStatus?: number;
102
+ },
103
+ fields: tmp.columnMetadata
104
+ };
105
+ return rval;
106
+ }
107
+
108
+
109
+ public static toSqlParameters(record: Record<string, any>): SqlParameter[] {
110
+ return Object.entries(record).map(([key, val]) => {
111
+ let value: any;
112
+ if (val === null) {
113
+ value = { isNull: true };
114
+ } else if (typeof val === "number") {
115
+ // choose longValue or doubleValue depending on your case
116
+ value = { longValue: val };
117
+ } else if (typeof val === "boolean") {
118
+ value = { booleanValue: val };
119
+ } else if (val instanceof Date) {
120
+ value = { stringValue: val.toISOString(), typeHint: "TIMESTAMP" };
121
+ } else {
122
+ // assume string
123
+ value = { stringValue: String(val) };
124
+ }
125
+ return { name: key, value };
126
+ });
127
+ }
128
+
129
+ public static fieldToValue(field: Field): any {
130
+ let rval: any = null;
131
+ if (field.isNull) { rval=null}
132
+ else if (field.stringValue !== undefined) {rval= field.stringValue;}
133
+ else if (field.blobValue !== undefined) {rval= Buffer.from(field.blobValue);}
134
+ else if (field.doubleValue !== undefined) {rval= field.doubleValue;}
135
+ else if (field.longValue !== undefined) {rval= field.longValue;}
136
+ else if (field.booleanValue !== undefined) {rval= field['booleanValue']}; // For some reason shows up as nevertype?}
137
+ return rval;
138
+ }
139
+
140
+ public static parseRecords<T>(
141
+ output: ExecuteStatementCommandOutput
142
+ ): T[] {
143
+ const { records, columnMetadata } = output;
144
+ if (!records || !columnMetadata) return [];
145
+
146
+ const rval: T[] = records.map((row) => {
147
+ const obj: T = {} as T;
148
+ row.forEach((field, i) => {
149
+ const name = columnMetadata[i].name || `col${i}`;
150
+ obj[name] = RdsDataApiDatabaseAccess.fieldToValue(field);
151
+ });
152
+ return obj;
153
+ });
154
+ return rval;
155
+ }
156
+
157
+ public async query<S>(query: string, fields: Record<string, any>): Promise<RequestResults<S>> {
158
+ const params: SqlParameter[] = RdsDataApiDatabaseAccess.toSqlParameters(fields);
159
+
160
+ const _tmp: ExecuteStatementCommandOutput = await this.client.send(new ExecuteStatementCommand({resourceArn: this.cfg.resourceArn, secretArn: this.cfg.secretArn, database: this.cfg.database, sql:query, parameters: params, includeResultMetadata: true}));
161
+ Logger.info('tmp: %j cm: %j', _tmp.records, _tmp.columnMetadata);
162
+ const records: S[] = RdsDataApiDatabaseAccess.parseRecords<S>(_tmp);
163
+
164
+ const rval: RequestResults<S> = {
165
+ results: records as S,
166
+ fields: _tmp.columnMetadata ?? []
167
+ };
168
+ return rval;
169
+ }
170
+
171
+ public async rollbackTransaction(): Promise<void> {
172
+ if (this._currentTransactionId) {
173
+ const out: RollbackTransactionCommandOutput = await this.client.send(new RollbackTransactionCommand({resourceArn: this.cfg.resourceArn, secretArn: this.cfg.secretArn, transactionId: this._currentTransactionId}));
174
+ Logger.info('Rollback transaction %s returned %j', this._currentTransactionId, out);
175
+ this._currentTransactionId = null;
176
+ } else {
177
+ Logger.warn('Rollback transaction called while no transaction in session');
178
+ }
179
+ }
180
+
181
+ public async testConnection(logTestResults?: boolean): Promise<number> {
182
+ const output: RequestResults<any> = await this.query<any>('SELECT 1 AS test_result', {}); // SELECT version()
183
+ if (logTestResults) {
184
+ Logger.info('Test connection returned %j', output);
185
+ }
186
+ const rval: number = output?.results?.length ? output.results[0]['test_result'] : null;
187
+ return rval;
188
+ }
189
+
190
+
191
+ /*
192
+ public async onRequestFailureOnly(type: DatabaseRequestType): Promise<void> {
193
+ return Promise.resolve(undefined);
194
+ }
195
+
196
+ public async onRequestSuccessOnly(type: DatabaseRequestType): Promise<void> {
197
+ return Promise.resolve(undefined);
198
+ }
199
+
200
+ public async onRequestSuccessOrFailure(type: DatabaseRequestType): Promise<void> {
201
+ return Promise.resolve(undefined);
202
+ }
203
+
204
+ public async preQuery(): Promise<void> {
205
+ return Promise.resolve(undefined);
206
+ }
207
+
208
+ */
209
+ }