@mcpher/gas-fakes 2.3.4 → 2.3.6
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/README.md +1 -0
- package/appsscript.json +1 -0
- package/main.js +3 -5
- package/package.json +4 -1
- package/skills-lock.json +10 -0
- package/src/cli/app.js +18 -0
- package/src/cli/setup.js +47 -4
- package/src/cli/utils.js +3 -4
- package/src/index.js +2 -0
- package/src/services/jdbc/app.js +9 -0
- package/src/services/jdbc/fakejdbcarray.js +37 -0
- package/src/services/jdbc/fakejdbcbigdecimal.js +63 -0
- package/src/services/jdbc/fakejdbcblob.js +67 -0
- package/src/services/jdbc/fakejdbcclob.js +67 -0
- package/src/services/jdbc/fakejdbcconnection.js +66 -0
- package/src/services/jdbc/fakejdbcdatabasemetadata.js +492 -0
- package/src/services/jdbc/fakejdbcpreparedstatement.js +130 -0
- package/src/services/jdbc/fakejdbcresultset.js +292 -0
- package/src/services/jdbc/fakejdbcresultsetmetadata.js +47 -0
- package/src/services/jdbc/fakejdbcservice.js +255 -0
- package/src/services/jdbc/fakejdbcstatement.js +185 -0
- package/src/support/fakeinputstream.js +42 -0
- package/src/support/metadata.js +16 -0
- package/src/support/sxauth.js +26 -0
- package/src/support/sxjdbc.js +234 -0
- package/src/support/syncit.js +47 -1
- package/src/support/workersync/sxfunctions.js +1 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Proxies } from '../../support/proxies.js';
|
|
2
|
+
import { Syncit } from '../../support/syncit.js';
|
|
3
|
+
import { newFakeJdbcResultSet } from './fakejdbcresultset.js';
|
|
4
|
+
|
|
5
|
+
class FakeJdbcStatement {
|
|
6
|
+
constructor(connection, connectionId) {
|
|
7
|
+
this.__fakeObjectType = 'JdbcStatement';
|
|
8
|
+
this._connection = connection;
|
|
9
|
+
this._connectionId = connectionId;
|
|
10
|
+
this._isClosed = false;
|
|
11
|
+
this._batch = [];
|
|
12
|
+
this._fetchSize = 0;
|
|
13
|
+
this._maxRows = 0;
|
|
14
|
+
this._queryTimeout = 0;
|
|
15
|
+
this._maxFieldSize = 0;
|
|
16
|
+
this._fetchDirection = 1000; // FETCH_FORWARD
|
|
17
|
+
this._isPoolable = false;
|
|
18
|
+
this._lastUpdateCount = -1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
addBatch(sql) {
|
|
22
|
+
if (this._isClosed) throw new Error('Statement is closed.');
|
|
23
|
+
this._batch.push(sql);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
executeBatch() {
|
|
27
|
+
if (this._isClosed) throw new Error('Statement is closed.');
|
|
28
|
+
const results = [];
|
|
29
|
+
for (const sql of this._batch) {
|
|
30
|
+
const result = Syncit.fxJdbcQuery(this._connectionId, sql);
|
|
31
|
+
results.push(result.rowCount || 0);
|
|
32
|
+
}
|
|
33
|
+
this.clearBatch();
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
clearBatch() {
|
|
38
|
+
this._batch = [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
executeQuery(sql) {
|
|
42
|
+
if (this._isClosed) throw new Error('Statement is closed.');
|
|
43
|
+
|
|
44
|
+
// Fetch result synchronously from worker
|
|
45
|
+
const result = Syncit.fxJdbcQuery(this._connectionId, sql);
|
|
46
|
+
this._lastUpdateCount = -1;
|
|
47
|
+
this._lastResultSet = newFakeJdbcResultSet(result, this);
|
|
48
|
+
return this._lastResultSet;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
execute(sql, autoGeneratedKeysOrIndexesOrNames) {
|
|
52
|
+
if (this._isClosed) throw new Error('Statement is closed.');
|
|
53
|
+
|
|
54
|
+
const result = Syncit.fxJdbcQuery(this._connectionId, sql);
|
|
55
|
+
this._lastUpdateCount = result.rowCount || 0;
|
|
56
|
+
if (result.fields && result.fields.length > 0) {
|
|
57
|
+
this._lastResultSet = newFakeJdbcResultSet(result, this);
|
|
58
|
+
return true;
|
|
59
|
+
} else {
|
|
60
|
+
this._lastResultSet = null;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
executeUpdate(sql, autoGeneratedKeysOrIndexesOrNames) {
|
|
66
|
+
if (this._isClosed) throw new Error('Statement is closed.');
|
|
67
|
+
const result = Syncit.fxJdbcQuery(this._connectionId, sql);
|
|
68
|
+
this._lastUpdateCount = result.rowCount;
|
|
69
|
+
this._lastResultSet = null;
|
|
70
|
+
return result.rowCount;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
cancel() {
|
|
74
|
+
// Stub: No-op for fake
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
clearWarnings() {
|
|
78
|
+
// Stub: No-op for fake
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getConnection() {
|
|
82
|
+
return this._connection;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getFetchDirection() {
|
|
86
|
+
return this._fetchDirection;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getFetchSize() {
|
|
90
|
+
return this._fetchSize;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getGeneratedKeys() {
|
|
94
|
+
// Return empty results as we don't currently track generated auto-increment IDs
|
|
95
|
+
return newFakeJdbcResultSet({ fields: [], rows: [] }, this);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getMaxFieldSize() {
|
|
99
|
+
return this._maxFieldSize;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getMaxRows() {
|
|
103
|
+
return this._maxRows;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getMoreResults(current) {
|
|
107
|
+
// We only support one result set at a time per statement in this fake
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getQueryTimeout() {
|
|
112
|
+
return this._queryTimeout;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
getResultSetConcurrency() {
|
|
116
|
+
return 1007; // CONCUR_READ_ONLY
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getResultSetHoldability() {
|
|
120
|
+
return 1; // HOLD_CURSORS_OVER_COMMIT
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getResultSetType() {
|
|
124
|
+
return 1003; // TYPE_FORWARD_ONLY
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getWarnings() {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
isPoolable() {
|
|
132
|
+
return this._isPoolable;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setCursorName(name) {
|
|
136
|
+
// Stub: No-op for fake
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setEscapeProcessing(enable) {
|
|
140
|
+
// Stub: No-op for fake
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setFetchDirection(direction) {
|
|
144
|
+
this._fetchDirection = direction;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
setFetchSize(rows) {
|
|
148
|
+
this._fetchSize = rows;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setMaxFieldSize(max) {
|
|
152
|
+
this._maxFieldSize = max;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
setMaxRows(max) {
|
|
156
|
+
this._maxRows = max;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
setPoolable(poolable) {
|
|
160
|
+
this._isPoolable = poolable;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
setQueryTimeout(seconds) {
|
|
164
|
+
this._queryTimeout = seconds;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
getUpdateCount() {
|
|
168
|
+
return this._lastUpdateCount || 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getResultSet() {
|
|
172
|
+
return this._lastResultSet;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
close() {
|
|
176
|
+
this._isClosed = true;
|
|
177
|
+
this._lastResultSet = null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
isClosed() {
|
|
181
|
+
return this._isClosed;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export const newFakeJdbcStatement = (...args) => Proxies.guard(new FakeJdbcStatement(...args));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Proxies } from './proxies.js';
|
|
2
|
+
|
|
3
|
+
class FakeInputStream {
|
|
4
|
+
constructor(data) {
|
|
5
|
+
this.__fakeObjectType = 'InputStream';
|
|
6
|
+
this._data = data instanceof Uint8Array ? data : Buffer.from(data || '');
|
|
7
|
+
this._pos = 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
read() {
|
|
11
|
+
if (this._pos >= this._data.length) return -1;
|
|
12
|
+
return this._data[this._pos++];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
available() {
|
|
16
|
+
return this._data.length - this._pos;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
close() {
|
|
20
|
+
this._pos = this._data.length;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class FakeReader {
|
|
25
|
+
constructor(data) {
|
|
26
|
+
this.__fakeObjectType = 'Reader';
|
|
27
|
+
this._data = String(data || '');
|
|
28
|
+
this._pos = 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
read() {
|
|
32
|
+
if (this._pos >= this._data.length) return -1;
|
|
33
|
+
return this._data.charCodeAt(this._pos++);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
close() {
|
|
37
|
+
this._pos = this._data.length;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const newFakeInputStream = (...args) => Proxies.guard(new FakeInputStream(...args));
|
|
42
|
+
export const newFakeReader = (...args) => Proxies.guard(new FakeReader(...args));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Initializes the global GasFakes metadata object.
|
|
6
|
+
* @param {string} packageJsonPath The relative path to package.json from this file.
|
|
7
|
+
* @returns {object} The GasFakes global object.
|
|
8
|
+
*/
|
|
9
|
+
export function initMetadata(packageJsonPath = '../../package.json') {
|
|
10
|
+
// add the version number to gasfakes metadata
|
|
11
|
+
if (!globalThis.GasFakes) globalThis.GasFakes = {};
|
|
12
|
+
const pjson = require(packageJsonPath);
|
|
13
|
+
if (!globalThis.GasFakes.metadata) globalThis.GasFakes.metadata = {}
|
|
14
|
+
globalThis.GasFakes.metadata.version = pjson.version
|
|
15
|
+
return globalThis.GasFakes;
|
|
16
|
+
}
|
package/src/support/sxauth.js
CHANGED
|
@@ -128,6 +128,32 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, cachePath,
|
|
|
128
128
|
|
|
129
129
|
} catch (err) {
|
|
130
130
|
syncWarn(`Google authentication failed: ${err.message}`);
|
|
131
|
+
|
|
132
|
+
// Provide guidance for Domain Wide Delegation issues
|
|
133
|
+
if (err.message.includes('unauthorized_client')) {
|
|
134
|
+
const clientId = Auth.getClientId();
|
|
135
|
+
const msg = [
|
|
136
|
+
"",
|
|
137
|
+
"=".repeat(80),
|
|
138
|
+
"GOOGLE AUTHENTICATION ERROR: unauthorized_client",
|
|
139
|
+
"This usually means Domain-Wide Delegation (DWD) is missing for one or more scopes.",
|
|
140
|
+
"",
|
|
141
|
+
`Your Service Account Client ID is: ${clientId || 'unknown (check your service account JSON file)'}`,
|
|
142
|
+
"",
|
|
143
|
+
"The following scopes should be authorized in the Google Admin Console:",
|
|
144
|
+
finalScopes.join(","),
|
|
145
|
+
"",
|
|
146
|
+
"To fix this:",
|
|
147
|
+
"1. Go to https://admin.google.com",
|
|
148
|
+
"2. Security -> Access and data control -> API controls",
|
|
149
|
+
"3. Manage Domain Wide Delegation",
|
|
150
|
+
"4. Find/Add your Client ID and ensure the list of scopes above matches exactly.",
|
|
151
|
+
"=".repeat(80),
|
|
152
|
+
""
|
|
153
|
+
].join("\n");
|
|
154
|
+
console.error(msg);
|
|
155
|
+
}
|
|
156
|
+
|
|
131
157
|
if (!platforms.includes('ksuite') && !platforms.includes('msgraph')) throw err;
|
|
132
158
|
}
|
|
133
159
|
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
import mysql from 'mysql2/promise';
|
|
3
|
+
|
|
4
|
+
const connections = new Map();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates and stores a connection to the database.
|
|
8
|
+
* @param {import('./auth.js').Auth} Auth
|
|
9
|
+
* @param {object} params
|
|
10
|
+
* @param {string} params.url JDBC connection URL
|
|
11
|
+
* @param {string} params.user Optional username
|
|
12
|
+
* @param {string} params.password Optional password
|
|
13
|
+
* @returns {object} Connection details/ID
|
|
14
|
+
*/
|
|
15
|
+
export const sxJdbcConnect = async (Auth, { url, user, password }) => {
|
|
16
|
+
let connectionString = url;
|
|
17
|
+
let type = 'pg';
|
|
18
|
+
|
|
19
|
+
if (url.startsWith('jdbc:postgresql:')) {
|
|
20
|
+
connectionString = url.replace('jdbc:postgresql:', 'postgresql:');
|
|
21
|
+
type = 'pg';
|
|
22
|
+
} else if (url.startsWith('jdbc:mysql:')) {
|
|
23
|
+
connectionString = url.replace('jdbc:mysql:', 'mysql:');
|
|
24
|
+
type = 'mysql';
|
|
25
|
+
} else if (url.startsWith('jdbc:google:mysql:')) {
|
|
26
|
+
connectionString = url.replace('jdbc:google:mysql:', 'mysql:');
|
|
27
|
+
type = 'mysql';
|
|
28
|
+
} else if (url.startsWith('mysql:')) {
|
|
29
|
+
type = 'mysql';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Parse custom flags
|
|
33
|
+
const disableSsl = connectionString.includes('ssl=false') || connectionString.includes('127.0.0.1');
|
|
34
|
+
|
|
35
|
+
let client;
|
|
36
|
+
const id = `conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
37
|
+
|
|
38
|
+
if (type === 'pg') {
|
|
39
|
+
// Strip ssl parameters to avoid conflict with explicit ssl object
|
|
40
|
+
let pgConnectionString = connectionString.replace(/([?&])ssl=[^&]*(&|$)/g, '$1').replace(/[?&]$/, '');
|
|
41
|
+
|
|
42
|
+
// Neon postgres usually requires ssl
|
|
43
|
+
if (pgConnectionString.includes('sslmode=require') && !pgConnectionString.includes('uselibpqcompat')) {
|
|
44
|
+
const separator = pgConnectionString.includes('?') ? '&' : '?';
|
|
45
|
+
pgConnectionString += `${separator}uselibpqcompat=true`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const clientConfig = {
|
|
49
|
+
connectionString: pgConnectionString,
|
|
50
|
+
ssl: disableSsl ? false : { rejectUnauthorized: false }
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (user !== null && typeof user !== 'undefined' && password !== null && typeof password !== 'undefined') {
|
|
54
|
+
client = new pg.Client({
|
|
55
|
+
...clientConfig,
|
|
56
|
+
user: String(user),
|
|
57
|
+
password: String(password)
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
client = new pg.Client(clientConfig);
|
|
61
|
+
}
|
|
62
|
+
await client.connect();
|
|
63
|
+
} else if (type === 'mysql') {
|
|
64
|
+
let cleanUrl = connectionString.replace(/^jdbc:/, '');
|
|
65
|
+
if (!cleanUrl.startsWith('mysql://')) {
|
|
66
|
+
cleanUrl = 'mysql://' + cleanUrl;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let host = '127.0.0.1';
|
|
70
|
+
let port = 3306;
|
|
71
|
+
let database = '';
|
|
72
|
+
let urlUser;
|
|
73
|
+
let urlPassword;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const parsedUrl = new URL(cleanUrl);
|
|
77
|
+
host = parsedUrl.hostname;
|
|
78
|
+
port = parsedUrl.port ? parseInt(parsedUrl.port, 10) : 3306;
|
|
79
|
+
database = parsedUrl.pathname.replace(/^\//, '');
|
|
80
|
+
urlUser = parsedUrl.username ? decodeURIComponent(parsedUrl.username) : parsedUrl.searchParams.get('user');
|
|
81
|
+
urlPassword = parsedUrl.password ? decodeURIComponent(parsedUrl.password) : parsedUrl.searchParams.get('password');
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// Fallback for weird formats (like Cloud SQL instance names without resolved IPs)
|
|
84
|
+
const match = cleanUrl.match(/^mysql:\/\/(?:([^:]+):([^@]+)@)?([^/]+?)(?::(\d+))?(?:\/([^?]+))?(?:\?(.*))?$/);
|
|
85
|
+
if (match) {
|
|
86
|
+
urlUser = match[1] ? decodeURIComponent(match[1]) : undefined;
|
|
87
|
+
urlPassword = match[2] ? decodeURIComponent(match[2]) : undefined;
|
|
88
|
+
host = match[3];
|
|
89
|
+
port = match[4] ? parseInt(match[4], 10) : 3306;
|
|
90
|
+
database = match[5] || '';
|
|
91
|
+
} else {
|
|
92
|
+
host = cleanUrl.replace(/^mysql:\/\//, '');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const finalUser = (user !== null && typeof user !== 'undefined') ? String(user) : urlUser;
|
|
97
|
+
const finalPassword = (password !== null && typeof password !== 'undefined') ? String(password) : urlPassword;
|
|
98
|
+
|
|
99
|
+
const mysqlConfig = {
|
|
100
|
+
host: host,
|
|
101
|
+
port: port,
|
|
102
|
+
database: database,
|
|
103
|
+
user: finalUser,
|
|
104
|
+
password: finalPassword,
|
|
105
|
+
ssl: disableSsl ? undefined : { rejectUnauthorized: false }
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
client = await mysql.createConnection(mysqlConfig);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
connections.set(id, { client, type });
|
|
112
|
+
return { id };
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Executes a query on a given connection.
|
|
117
|
+
* @param {import('./auth.js').Auth} Auth
|
|
118
|
+
* @param {object} params
|
|
119
|
+
* @param {string} params.connectionId The stored connection ID
|
|
120
|
+
* @param {string} params.sql The SQL query to execute
|
|
121
|
+
* @returns {object} The query result (rows, fields, etc.)
|
|
122
|
+
*/
|
|
123
|
+
export const sxJdbcQuery = async (Auth, { connectionId, sql }) => {
|
|
124
|
+
const entry = connections.get(connectionId);
|
|
125
|
+
if (!entry) throw new Error('Invalid or closed JDBC connection.');
|
|
126
|
+
|
|
127
|
+
const { client, type } = entry;
|
|
128
|
+
|
|
129
|
+
if (type === 'pg') {
|
|
130
|
+
const result = await client.query(sql);
|
|
131
|
+
const rows = Array.isArray(result) ? result[result.length - 1].rows : result.rows;
|
|
132
|
+
const fields = Array.isArray(result) ? result[result.length - 1].fields : result.fields;
|
|
133
|
+
const rowCount = Array.isArray(result) ? result[result.length - 1].rowCount : result.rowCount;
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
rows: rows || [],
|
|
137
|
+
fields: fields || [],
|
|
138
|
+
rowCount: rowCount || 0,
|
|
139
|
+
};
|
|
140
|
+
} else if (type === 'mysql') {
|
|
141
|
+
// mysql2 returns [rows, fields]
|
|
142
|
+
const [rows, fields] = await client.query(sql);
|
|
143
|
+
return {
|
|
144
|
+
rows: rows || [],
|
|
145
|
+
fields: fields || [], // mysql2 fields are objects with name, columnType, etc.
|
|
146
|
+
rowCount: (rows && rows.insertId) ? 1 : (rows && rows.affectedRows) || (rows && rows.length) || 0,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Executes a prepared statement on a given connection.
|
|
153
|
+
* @param {import('./auth.js').Auth} Auth
|
|
154
|
+
* @param {object} params
|
|
155
|
+
* @param {string} params.connectionId The stored connection ID
|
|
156
|
+
* @param {string} params.sql The SQL query with placeholders
|
|
157
|
+
* @param {any[]} params.values The parameter values
|
|
158
|
+
* @returns {object} The query result
|
|
159
|
+
*/
|
|
160
|
+
export const sxJdbcExecutePrepared = async (Auth, { connectionId, sql, values }) => {
|
|
161
|
+
const entry = connections.get(connectionId);
|
|
162
|
+
if (!entry) throw new Error('Invalid or closed JDBC connection.');
|
|
163
|
+
|
|
164
|
+
const { client, type } = entry;
|
|
165
|
+
|
|
166
|
+
if (type === 'pg') {
|
|
167
|
+
// pg uses $1, $2, etc. so we must convert ? to $n
|
|
168
|
+
let index = 1;
|
|
169
|
+
const pgSql = sql.replace(/\?/g, () => `$${index++}`);
|
|
170
|
+
const result = await client.query(pgSql, values);
|
|
171
|
+
return {
|
|
172
|
+
rows: result.rows || [],
|
|
173
|
+
fields: result.fields || [],
|
|
174
|
+
rowCount: result.rowCount || 0,
|
|
175
|
+
};
|
|
176
|
+
} else if (type === 'mysql') {
|
|
177
|
+
// mysql2 natively supports ? placeholders
|
|
178
|
+
const [rows, fields] = await client.execute(sql, values);
|
|
179
|
+
return {
|
|
180
|
+
rows: rows || [],
|
|
181
|
+
fields: fields || [],
|
|
182
|
+
rowCount: (rows && (rows.affectedRows || rows.length)) || 0,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const sxJdbcCommit = async (Auth, { connectionId }) => {
|
|
188
|
+
return sxJdbcQuery(Auth, { connectionId, sql: 'COMMIT' });
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const sxJdbcRollback = async (Auth, { connectionId }) => {
|
|
192
|
+
return sxJdbcQuery(Auth, { connectionId, sql: 'ROLLBACK' });
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Sets auto-commit mode for the connection.
|
|
197
|
+
* Note: Node drivers are technically always in a state where BEGIN is needed for transactions.
|
|
198
|
+
* @param {import('./auth.js').Auth} Auth
|
|
199
|
+
* @param {object} params
|
|
200
|
+
* @param {string} params.connectionId The stored connection ID
|
|
201
|
+
* @param {boolean} params.autoCommit Whether to enable auto-commit
|
|
202
|
+
*/
|
|
203
|
+
export const sxJdbcSetAutoCommit = async (Auth, { connectionId, autoCommit }) => {
|
|
204
|
+
const entry = connections.get(connectionId);
|
|
205
|
+
if (!entry) throw new Error('Invalid or closed JDBC connection.');
|
|
206
|
+
|
|
207
|
+
if (!autoCommit) {
|
|
208
|
+
// If turning off auto-commit, start a transaction
|
|
209
|
+
await sxJdbcQuery(Auth, { connectionId, sql: 'BEGIN' });
|
|
210
|
+
}
|
|
211
|
+
entry.autoCommit = autoCommit;
|
|
212
|
+
return true;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Closes a given connection.
|
|
217
|
+
* @param {import('./auth.js').Auth} Auth
|
|
218
|
+
* @param {object} params
|
|
219
|
+
* @param {string} params.connectionId The stored connection ID
|
|
220
|
+
* @returns {boolean} True if closed successfully
|
|
221
|
+
*/
|
|
222
|
+
export const sxJdbcClose = async (Auth, { connectionId }) => {
|
|
223
|
+
const entry = connections.get(connectionId);
|
|
224
|
+
if (entry) {
|
|
225
|
+
const { client, type } = entry;
|
|
226
|
+
if (type === 'pg') {
|
|
227
|
+
await client.end();
|
|
228
|
+
} else if (type === 'mysql') {
|
|
229
|
+
await client.end();
|
|
230
|
+
}
|
|
231
|
+
connections.delete(connectionId);
|
|
232
|
+
}
|
|
233
|
+
return true;
|
|
234
|
+
};
|
package/src/support/syncit.js
CHANGED
|
@@ -412,6 +412,45 @@ const fxTestRetry = (errorMessage) => {
|
|
|
412
412
|
return safeCallSync("sxTestRetry", { errorMessage });
|
|
413
413
|
};
|
|
414
414
|
|
|
415
|
+
const fxJdbcConnect = (url, user, password) => {
|
|
416
|
+
const args = { url };
|
|
417
|
+
if (typeof user === 'object' && user !== null) {
|
|
418
|
+
// Handling info object
|
|
419
|
+
args.user = user.user || user.userName;
|
|
420
|
+
args.password = user.password;
|
|
421
|
+
// We could pass the whole object if the worker was ready for it,
|
|
422
|
+
// but for now let's just stick to user/pass.
|
|
423
|
+
} else {
|
|
424
|
+
if (user !== null && typeof user !== 'undefined') args.user = user;
|
|
425
|
+
if (password !== null && typeof password !== 'undefined') args.password = password;
|
|
426
|
+
}
|
|
427
|
+
return safeCallSync("sxJdbcConnect", args);
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const fxJdbcQuery = (connectionId, sql) => {
|
|
431
|
+
return safeCallSync("sxJdbcQuery", { connectionId, sql });
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const fxJdbcExecutePrepared = (connectionId, sql, values) => {
|
|
435
|
+
return safeCallSync("sxJdbcExecutePrepared", { connectionId, sql, values });
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const fxJdbcCommit = (connectionId) => {
|
|
439
|
+
return safeCallSync("sxJdbcCommit", { connectionId });
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const fxJdbcRollback = (connectionId) => {
|
|
443
|
+
return safeCallSync("sxJdbcRollback", { connectionId });
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const fxJdbcSetAutoCommit = (connectionId, autoCommit) => {
|
|
447
|
+
return safeCallSync("sxJdbcSetAutoCommit", { connectionId, autoCommit });
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const fxJdbcClose = (connectionId) => {
|
|
451
|
+
return safeCallSync("sxJdbcClose", { connectionId });
|
|
452
|
+
};
|
|
453
|
+
|
|
415
454
|
const fxSheets = (args) =>
|
|
416
455
|
fxGeneric({
|
|
417
456
|
...args,
|
|
@@ -479,5 +518,12 @@ export const Syncit = {
|
|
|
479
518
|
fxGetAccessToken,
|
|
480
519
|
fxGetAccessTokenInfo,
|
|
481
520
|
fxGetSourceAccessTokenInfo,
|
|
482
|
-
fxTestRetry
|
|
521
|
+
fxTestRetry,
|
|
522
|
+
fxJdbcConnect,
|
|
523
|
+
fxJdbcQuery,
|
|
524
|
+
fxJdbcExecutePrepared,
|
|
525
|
+
fxJdbcCommit,
|
|
526
|
+
fxJdbcRollback,
|
|
527
|
+
fxJdbcSetAutoCommit,
|
|
528
|
+
fxJdbcClose
|
|
483
529
|
}
|