diff --git a/plugins/dbgate-plugin-mongo/package.json b/plugins/dbgate-plugin-mongo/package.json index d387ad1f3..742a71711 100644 --- a/plugins/dbgate-plugin-mongo/package.json +++ b/plugins/dbgate-plugin-mongo/package.json @@ -42,6 +42,7 @@ "is-promise": "^4.0.0", "lodash": "^4.17.21", "mongodb": "^6.3.0", + "mongodb-old": "npm:mongodb@^6.16.0", "@mongosh/browser-runtime-electron": "^3.16.4", "@mongosh/service-provider-node-driver": "^3.10.2" }, diff --git a/plugins/dbgate-plugin-mongo/src/backend/driver.js b/plugins/dbgate-plugin-mongo/src/backend/drivers.js similarity index 93% rename from plugins/dbgate-plugin-mongo/src/backend/driver.js rename to plugins/dbgate-plugin-mongo/src/backend/drivers.js index cdbac08ba..ea487d76d 100644 --- a/plugins/dbgate-plugin-mongo/src/backend/driver.js +++ b/plugins/dbgate-plugin-mongo/src/backend/drivers.js @@ -1,10 +1,13 @@ const _ = require('lodash'); const { EventEmitter } = require('events'); const stream = require('stream'); -const driverBase = require('../frontend/driver'); +const driverBases = require('../frontend/drivers'); const Analyser = require('./Analyser'); const isPromise = require('is-promise'); -const { MongoClient, ObjectId, AbstractCursor, Long } = require('mongodb'); +const mongodb = require('mongodb'); +const oldmongodb = require('mongodb-old'); +const { ObjectId } = require('mongodb'); +// { MongoClient, ObjectId, AbstractCursor, Long } const { EJSON } = require('bson'); const { serializeJsTypesForJsonStringify, deserializeJsTypesFromJsonParse, getLogger } = require('dbgate-tools'); const createBulkInsertStream = require('./createBulkInsertStream'); @@ -18,7 +21,8 @@ let isProApp; const logger = getLogger('mongoDriver'); -function serializeMongoData(row) { +function serializeMongoData(row, driverBase) { + const { Long } = driverBase.useLegacyDriver ? oldmongodb : mongodb; return EJSON.serialize( serializeJsTypesForJsonStringify(row, (value) => { if (value instanceof Long) { @@ -33,10 +37,10 @@ function serializeMongoData(row) { ); } -async function readCursor(cursor, options) { +async function readCursor(cursor, options, driverBase) { options.recordset({ __isDynamicStructure: true }); await cursor.forEach((row) => { - options.row(serializeMongoData(row)); + options.row(serializeMongoData(row, driverBase)); }); } @@ -86,8 +90,8 @@ async function getScriptableDb(dbhan) { // } // } -/** @type {import('dbgate-types').EngineDriver} */ -const driver = { +/** @type {import('dbgate-types').EngineDriver} */ +const drivers = driverBases.map((driverBase) => ({ ...driverBase, analyserClass: Analyser, async connect({ server, port, user, password, database, useDatabaseUrl, databaseUrl, ssl, useSshTunnel }) { @@ -120,6 +124,8 @@ const driver = { options.tlsInsecure = !ssl.rejectUnauthorized; } + const { MongoClient } = driverBase.useLegacyDriver ? oldmongodb : mongodb; + const client = new MongoClient(mongoUrl, options); await client.connect(); return { @@ -314,8 +320,10 @@ const driver = { return; } + const { AbstractCursor } = driverBase.useLegacyDriver ? oldmongodb : mongodb; + if (exprValue instanceof AbstractCursor) { - await readCursor(exprValue, options); + await readCursor(exprValue, options, driverBase); } else if (isPromise(exprValue)) { try { const resValue = await exprValue; @@ -427,7 +435,7 @@ const driver = { const cursorStream = exprValue.stream(); cursorStream.on('data', (row) => { - pass.write(serializeMongoData(row)); + pass.write(serializeMongoData(row, driverBase)); }); // propagate error @@ -487,10 +495,12 @@ const driver = { let cursor = await collection.aggregate(deserializeMongoData(convertToMongoAggregate(options.aggregate))); const rows = await cursor.toArray(); return { - rows: rows.map(serializeMongoData).map((x) => ({ - ...x._id, - ..._.omit(x, ['_id']), - })), + rows: rows + .map((row) => serializeMongoData(row, driverBase)) + .map((x) => ({ + ...x._id, + ..._.omit(x, ['_id']), + })), }; } else { // console.log('options.condition', JSON.stringify(options.condition, undefined, 2)); @@ -500,7 +510,7 @@ const driver = { if (options.limit) cursor = cursor.limit(options.limit); const rows = await cursor.toArray(); return { - rows: rows.map(serializeMongoData), + rows: rows.map((row) => serializeMongoData(row, driverBase)), }; } } catch (err) { @@ -613,10 +623,12 @@ const driver = { ]); const rows = await cursor.toArray(); return _.uniqBy( - rows.map(serializeMongoData).map(({ _id }) => { - if (_.isArray(_id) || _.isPlainObject(_id)) return { value: null }; - return { value: _id }; - }), + rows + .map((row) => serializeMongoData(row, driverBase)) + .map(({ _id }) => { + if (_.isArray(_id) || _.isPlainObject(_id)) return { value: null }; + return { value: _id }; + }), (x) => x.value ); } catch (err) { @@ -753,10 +765,10 @@ const driver = { return result; }, -}; +})); -driver.initialize = (dbgateEnv) => { +drivers.initialize = (dbgateEnv) => { isProApp = dbgateEnv.isProApp; }; -module.exports = driver; +module.exports = drivers; diff --git a/plugins/dbgate-plugin-mongo/src/backend/index.js b/plugins/dbgate-plugin-mongo/src/backend/index.js index 7e432dc7c..bc9eae027 100644 --- a/plugins/dbgate-plugin-mongo/src/backend/index.js +++ b/plugins/dbgate-plugin-mongo/src/backend/index.js @@ -1,4 +1,4 @@ -const driver = require('./driver'); +const drivers = require('./drivers'); const { formatProfilerEntry, extractProfileTimestamp, @@ -7,13 +7,13 @@ const { module.exports = { packageName: 'dbgate-plugin-mongo', - drivers: [driver], + drivers, functions: { formatProfilerEntry, extractProfileTimestamp, aggregateProfileChartEntry, }, initialize(dbgateEnv) { - driver.initialize(dbgateEnv); + drivers.initialize(dbgateEnv); }, }; diff --git a/plugins/dbgate-plugin-mongo/src/frontend/driver.js b/plugins/dbgate-plugin-mongo/src/frontend/drivers.js similarity index 91% rename from plugins/dbgate-plugin-mongo/src/frontend/driver.js rename to plugins/dbgate-plugin-mongo/src/frontend/drivers.js index a8163ec89..84db9321c 100644 --- a/plugins/dbgate-plugin-mongo/src/frontend/driver.js +++ b/plugins/dbgate-plugin-mongo/src/frontend/drivers.js @@ -16,9 +16,12 @@ function jsonStringifyWithObjectId(obj) { return JSON.stringify(obj, mongoReplacer, 2) .replace(/\{\s*\"\$oid\"\s*\:\s*\"([0-9a-f]+)\"\s*\}/g, (m, id) => `ObjectId("${id}")`) .replace(/\{\s*\"\$bigint\"\s*\:\s*\"([0-9]+)\"\s*\}/g, (m, num) => `${num}n`) - .replace(/\{\s*"\$binary"\s*:\s*\{\s*"base64"\s*:\s*"([^"]+)"(?:\s*,\s*"subType"\s*:\s*"([0-9a-fA-F]{2})")?\s*\}\s*\}/g, (m, base64, subType) => { - return `BinData(${parseInt(subType || "00", 16)}, "${base64}")`; - }); + .replace( + /\{\s*"\$binary"\s*:\s*\{\s*"base64"\s*:\s*"([^"]+)"(?:\s*,\s*"subType"\s*:\s*"([0-9a-fA-F]{2})")?\s*\}\s*\}/g, + (m, base64, subType) => { + return `BinData(${parseInt(subType || '00', 16)}, "${base64}")`; + } + ); } /** @type {import('dbgate-types').SqlDialect} */ @@ -34,7 +37,7 @@ const dialect = { }; /** @type {import('dbgate-types').EngineDriver} */ -const driver = { +const mongoDriverBase = { ...driverBase, dumperClass: Dumper, databaseEngineTypes: ['document'], @@ -193,4 +196,16 @@ const driver = { }, }; -module.exports = driver; +const mongoDriver = { + ...mongoDriverBase, +}; + +const legacyMongoDriver = { + ...mongoDriverBase, + engine: 'mongo-legacy@dbgate-plugin-mongo', + title: 'MongoDB 4 - Legacy', + premiumOnly: true, + useLegacyDriver: true, +}; + +module.exports = [mongoDriver, legacyMongoDriver]; diff --git a/plugins/dbgate-plugin-mongo/src/frontend/index.js b/plugins/dbgate-plugin-mongo/src/frontend/index.js index da0a77580..fc4bb19fa 100644 --- a/plugins/dbgate-plugin-mongo/src/frontend/index.js +++ b/plugins/dbgate-plugin-mongo/src/frontend/index.js @@ -1,9 +1,9 @@ -import driver from './driver'; +import drivers from './drivers'; import { formatProfilerEntry, extractProfileTimestamp, aggregateProfileChartEntry } from './profilerFunctions'; export default { packageName: 'dbgate-plugin-mongo', - drivers: [driver], + drivers, functions: { formatProfilerEntry, extractProfileTimestamp, diff --git a/yarn.lock b/yarn.lock index df07905f7..70332b719 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2669,6 +2669,13 @@ dependencies: sparse-bitfield "^3.0.3" +"@mongodb-js/saslprep@^1.3.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz#51e5cad2f24b8759702d9cc185da0a3ef3784bad" + integrity sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg== + dependencies: + sparse-bitfield "^3.0.3" + "@mongodb-js/socksv5@^0.0.10": version "0.0.10" resolved "https://registry.yarnpkg.com/@mongodb-js/socksv5/-/socksv5-0.0.10.tgz#d734c9799a5d011caaf43788e16925aca90712d8" @@ -6159,6 +6166,11 @@ dbgate-query-splitter@^4.11.7: resolved "https://registry.yarnpkg.com/dbgate-query-splitter/-/dbgate-query-splitter-4.11.7.tgz#f9d53b3ceafbd76355152677b87ae453598b4a88" integrity sha512-2J0Gc2hyZrHnrLBpI6Tw8MvP7V01eTj+SMHLeG9ggVf6Y8JB/6JBK5kPwhz77SwZw/InhpaweCUBoHFCJUv/kg== +dbgate-query-splitter@^4.11.9: + version "4.11.9" + resolved "https://registry.yarnpkg.com/dbgate-query-splitter/-/dbgate-query-splitter-4.11.9.tgz#1461ec652caf6b44d7b94109610f497695c4aef6" + integrity sha512-WaN9VFgmIpIvpNDUFNV1P0m7TimCAk2Itgk4lKndxC+ixhersHfLfGcea5gcKS1ie8+SRoKlFsfe/MIehvOA9A== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -10685,7 +10697,7 @@ mongodb-connection-string-url@^3.0.0: "@types/whatwg-url" "^11.0.2" whatwg-url "^13.0.0" -mongodb-connection-string-url@^3.0.1: +mongodb-connection-string-url@^3.0.1, mongodb-connection-string-url@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz#e223089dfa0a5fa9bf505f8aedcbc67b077b33e7" integrity sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA== @@ -10698,6 +10710,15 @@ mongodb-ns@^2.4.0: resolved "https://registry.yarnpkg.com/mongodb-ns/-/mongodb-ns-2.4.2.tgz#481592316d3a2be68accbba3aca2cb660eb6da1d" integrity sha512-gYJjEYG4v4a1WSXgUf81OBoBRlj+Z1SlnQVO392fC/4a1CN7CLWDITajZWPFTPh/yRozYk6sHHtZwZmQhodBEA== +"mongodb-old@npm:mongodb@^6.16.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.21.0.tgz#f83355905900f2e7a912593f0315d5e2e0bda576" + integrity sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A== + dependencies: + "@mongodb-js/saslprep" "^1.3.0" + bson "^6.10.4" + mongodb-connection-string-url "^3.0.2" + mongodb-redact@^1.1.5: version "1.1.8" resolved "https://registry.yarnpkg.com/mongodb-redact/-/mongodb-redact-1.1.8.tgz#47323bd7fcdb033d75ccf539e4fa3685fa5221c5"