├── .eslintrc.js
├── .gitignore
├── .nvmrc
├── README.md
├── admin.js
├── index.js
├── package-lock.json
├── package.json
├── test
└── util.js
├── util.js
└── wireprotocol.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "node": true,
4 | "mocha": true
5 | },
6 | "extends": "eslint:recommended",
7 | "parserOptions": {
8 | "ecmaVersion": 2017,
9 | },
10 | "rules": {
11 | "no-console": "off",
12 | "indent": [
13 | "error",
14 | 2,
15 | { "SwitchCase": 1 }
16 | ],
17 | "linebreak-style": [
18 | "error",
19 | "unix"
20 | ],
21 | "quotes": [
22 | "error",
23 | "single"
24 | ],
25 | "semi": [
26 | "error",
27 | "never"
28 | ]
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | .idea
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 10.4.0
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Replace MongoDB with PosgreSQL
4 |
5 |
6 | ## What is pgmongo?
7 | - **Drop-in replacement** Applications do not need code changes. pgmongo appears to your app as a MongoDB server.
8 | - **Stateless proxy** pgmongo rewrites queries and proxies them to a Postgres database.
9 | - **JSON** Primarily supports regular JSON data. Advanced BSON types like binary data, JavaSrcipt, ints and timestamps are not well supported.
10 |
11 | This implements the [MongoDB wire protocol](https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/) and adapts queries to work with a PostgreSQL database using jsonb fields.
12 | I've tested it with [Keystone.js](http://keystonejs.com/) and it seemed to work reasonably well.
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Getting Started
20 | pgmongo requires node 8 or newer and Postgres 9.4+. Then run the following.
21 | ```bash
22 | npm install -g pgmongo
23 | pgmongo
24 | ```
25 | This will start a mongo-like server on port 27017. If you already have mongo running on your machine you can start it on a different port with the following.
26 | ```bash
27 | pgmongo localhost 27018
28 | ```
29 |
30 | ## Supported Features
31 | * listing/creating/dropping collections
32 | * find (including sorting, skip and offset)
33 | * count, distinct
34 | * update (including support for upserting)
35 | * insert (including batch insertion)
36 | * deletion
37 | * creating and listing basic indexes
38 | * most custom parameters like $gt, $exists, $regex, $push, $set, $unset, etc.
39 | See [this repo](https://github.com/thomas4019/mongo-query-to-postgres-jsonb) for the full list
40 | * admin commands (returns mostly stubbed/fake data)
41 |
42 | # Current status
43 | It's not production ready yet, but definitely working enough to play around with or use in basic apps.
44 | Currently passes 190 of the 916 core mongo [jstests](https://github.com/mongodb/mongo/tree/master/jstests/core).
45 |
46 | ## Example Query Conversions
47 | ```
48 | db.createCollection('users') -> CREATE TABLE IF NOT EXISTS "users" (data jsonb)
49 | db.users.find({ lastLogin: { $lte: '2016' } }) -> SELECT data FROM "users" WHERE data->>'lastLogin'<='2016'
50 | db.users.update({}, { $set: { active: true } }) -> UPDATE "users" SET data = jsonb_set(data,'{active}','true'::jsonb)
51 | db.users.find({}, { firstName: 1 } ) -> SELECT jsonb_build_object('firstName', data->'firstName', '_id', data->'_id') as data FROM "users"
52 | db.blogs.insert({ title: 'first post', comments: [] }) -> INSERT INTO "blogs" VALUES ('{"_id":"5b45b641eb4bd93896d57888","title":"first post","comments":[]}'::jsonb)
53 | db.blogs.remove({ 'state.trash': true }) -> DELETE FROM "blogs" WHERE data->'state'->'trash'='true'::jsonb
54 | ```
55 |
56 | ## Missing Features / Roadmap (ordered by priority)
57 | Note: contributions/PRs are very much welcome.
58 | * Support for findandmodify
59 | * Better for queries matching array elements
60 | * Preserve BSON (Dates, ObjectIDs, other than _id)
61 | * Cursors (currently all data is returned in first result)
62 | * Better Indexes support (not sure if compound indexes are possible)
63 | * [min](https://docs.mongodb.com/manual/reference/method/cursor.min/) and max
64 | * Support numeric object keys (currently numbers are assumed to be an array index)
65 | * Capped collections
66 | * geo support
67 | * explain queries
68 | * aggregation framework/map reduce queries
69 |
70 | ### Likely Cannot support
71 | * NaN and Infinity
72 | * Preserve the initial order of object keys
73 | * $eval and $where
74 |
75 | ## Resources
76 | * [mongo-query-to-postgres-jsonb](https://github.com/thomas4019/mongo-query-to-postgres-jsonb) (heavily used as a dependency)
77 | * [Mongo Admin Commands](https://docs.mongodb.com/manual/reference/command/nav-administration/)
78 | * [JSONB Index Performance](http://bitnine.net/blog-postgresql/postgresql-internals-jsonb-type-and-its-indexes/)
79 |
80 | ## Want to contribute?
81 | Anyone can help make this project better. Feel free to open an issue to discuss what you want to work on.
82 |
83 | ## Related Projects
84 | [Mongres](https://github.com/umitanuki/mongres),
85 | [mongolike](https://github.com/JerrySievert/mongolike),
86 | [plv8](https://github.com/plv8/plv8),
87 | [ToroDB](https://news.ycombinator.com/item?id=8527013)
88 |
--------------------------------------------------------------------------------
/admin.js:
--------------------------------------------------------------------------------
1 | function getIsMasterReply() {
2 | return {
3 | 'ismaster': true,
4 | 'maxBsonObjectSize': 16777216,
5 | 'maxMessageSizeBytes': 48000000,
6 | 'maxWriteBatchSize': 1000,
7 | 'localTime': new Date('2018-06-01T07:08:25.204Z'),
8 | 'maxWireVersion': 5,
9 | 'minWireVersion': 0,
10 | 'readOnly': false,
11 | 'ok': 1
12 | }
13 | }
14 |
15 | function getBuildInfo() {
16 | return {
17 | 'version': '3.4.9',
18 | 'modules': [],
19 | 'allocator': 'system',
20 | 'javascriptEngine': 'mozjs',
21 | 'sysInfo': 'deprecated',
22 | 'versionArray': [3, 4, 9, 0],
23 | 'openssl': { 'running': 'OpenSSL 1.0.2n 7 Dec 2017', 'compiled': 'OpenSSL 1.0.2l 25 May 2017' },
24 | 'buildEnvironment': {
25 | 'distmod': '',
26 | 'distarch': 'x86_64',
27 | 'cc': '/usr/bin/clang: Apple LLVM version 8.1.0 (clang-802.0.42)',
28 | 'ccflags': '-I/usr/local/opt/openssl/include -fno-omit-frame-pointer -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -O2 -Wno-unused-local-typedefs -Wno-unused-function -Wno-unused-private-field -Wno-deprecated-declarations -Wno-tautological-constant-out-of-range-compare -Wno-unused-const-variable -Wno-missing-braces -Wno-inconsistent-missing-override -Wno-potentially-evaluated-expression -fstack-protector-strong -Wno-null-conversion -mmacosx-version-min=10.12 -fno-builtin-memcmp',
29 | 'cxx': '/usr/bin/clang++: Apple LLVM version 8.1.0 (clang-802.0.42)',
30 | 'cxxflags': '-Woverloaded-virtual -Wpessimizing-move -Wredundant-move -Wno-undefined-var-template -std=c++11',
31 | 'linkflags': '-L/usr/local/opt/openssl/lib -pthread -Wl,-bind_at_load -fstack-protector-strong -mmacosx-version-min=10.12',
32 | 'target_arch': 'x86_64',
33 | 'target_os': 'osx'
34 | },
35 | 'bits': 64,
36 | 'debug': false,
37 | 'maxBsonObjectSize': 16777216,
38 | 'storageEngines': ['devnull', 'ephemeralForTest', 'mmapv1', 'wiredTiger'],
39 | 'ok': 1
40 | }
41 | }
42 |
43 | function getServerStatus() {
44 | return {
45 | 'host' : 'Thomass-MBP.hsd1.ca.comcast.net',
46 | 'version' : '3.4.9',
47 | 'process' : 'mongod',
48 | 'pid' : 70211,
49 | 'uptime' : 1835,
50 | 'uptimeMillis' : 1834724,
51 | 'uptimeEstimate' : 1834,
52 | 'localTime' : new Date('2018-06-05T07:44:43.503Z')
53 | }
54 | }
55 |
56 | function replSetGetStatus() {
57 | return {
58 | 'ok': 0,
59 | 'errmsg': 'not running with --replSet',
60 | 'code': 76,
61 | 'codeName': 'NoReplicationEnabled'
62 | }
63 | }
64 |
65 | exports = module.exports = {
66 | getIsMasterReply,
67 | getBuildInfo,
68 | getServerStatus,
69 | replSetGetStatus
70 | }
71 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const net = require('net')
3 | const BSON = require('bson-ext')
4 | const mongoToPostgres = require('mongo-query-to-postgres-jsonb')
5 | const debug = require('debug')
6 | const _ = require('lodash')
7 |
8 | const util = require('./util')
9 | const wire = require('./wireprotocol')
10 | const adminReplies = require('./admin')
11 |
12 | const args = process.argv.slice(2)
13 | if (args.length < 1) {
14 | console.error('node index.js [] []')
15 | return
16 | }
17 |
18 | const createMissingDatabases = true
19 | const pgHost = args[0] || 'localhost'
20 | const mongoPort = parseInt(args[1] || '27018')
21 | const { Client } = require('pg')
22 |
23 | // database -> client
24 | const clients = {}
25 |
26 | let lastResult = {
27 | n: 0,
28 | connectionId : 1789,
29 | updatedExisting: true,
30 | errMsg: '',
31 | syncMillis: 0,
32 | writtenTo: null,
33 | ok: 1
34 | }
35 |
36 | async function createDatabase(databaseName) {
37 | await doQuery('postgres', 'CREATE DATABASE ' + databaseName)
38 | }
39 |
40 | async function doQuery(database, pgQuery) {
41 | if (!clients[database]) {
42 | try {
43 | const client = new Client({ database, host: pgHost })
44 | clients[database] = client
45 | await client.connect()
46 | } catch (e2) {
47 | if (database !== 'postgres' && createMissingDatabases) {
48 | await createDatabase(database);
49 | const client = new Client({ database, host: pgHost })
50 | clients[database] = client
51 | await client.connect()
52 | }
53 | }
54 | }
55 |
56 | debug('pgmongo:pgquery')(pgQuery)
57 | return await clients[database].query(pgQuery)
58 | }
59 |
60 | async function createTable(databaseName, collectionName) {
61 | await doQuery(databaseName, `CREATE TABLE IF NOT EXISTS ${collectionName} (data jsonb)`)
62 | }
63 |
64 | async function tryOrCreateTable(action, databaseName, collectionName) {
65 | try {
66 | return await action()
67 | } catch (e) {
68 | if (e.message.includes('does not exist')) {
69 | await createTable(collectionName)
70 | return await action()
71 | } else {
72 | throw e
73 | }
74 | }
75 | }
76 |
77 | var server = net.createServer(function (socket) {
78 | socket.on('data', (data) => processRecord(socket, data))
79 | })
80 |
81 | const convertToBSON = function(doc) {
82 | if (doc && doc._id && doc._id.length === 24) {
83 | doc._id = BSON.ObjectID(doc._id)
84 | }
85 | return doc
86 | }
87 |
88 | let commands = ['find', 'count', 'update', 'insert', 'create', 'delete', 'drop', 'validate',
89 | 'listIndexes', 'createIndexes', 'deleteIndexes', 'renameCollection']
90 |
91 | const arrayPathsMap = {}
92 |
93 | function getArrayPaths(collectionName) {
94 | debug('pgmongo:arraypaths')(collectionName + ' ' + arrayPathsMap[collectionName])
95 | return arrayPathsMap[collectionName] || []
96 | }
97 |
98 | function safeWhereConversion(filter, collectionName) {
99 | try {
100 | return mongoToPostgres('data', filter, getArrayPaths(collectionName))
101 | } catch (err) {
102 | const data = {
103 | errmsg: 'in must be an array',
104 | code: 2,
105 | codeName: 'BadValue',
106 | ok: 0
107 | }
108 | console.error('query error')
109 | console.error(err)
110 | throw data
111 | }
112 | }
113 |
114 | async function crud(socket, reqId, databaseName, commandName, doc, build) {
115 | const normalizedCommandName = commandName.toLowerCase()
116 | let rawCollectionName = doc[commandName] || doc[normalizedCommandName]
117 | let collectionName = '"' + rawCollectionName + '"'
118 | if (commandName === 'distinct') {
119 | if ((doc.query && typeof doc.query !== 'object') || typeof doc.key !== 'string') {
120 | socket.write(build(reqId, { ok: 0, errmsg: '"query" had the wrong type. Expected object or null,', code: 14 }, {}))
121 | //throw new Error('\\"query\\" had the wrong type. Expected object or null, found ' + typeof doc.query)
122 | return true
123 | }
124 | const filter = doc.query || {}
125 | let where = safeWhereConversion(filter, rawCollectionName)
126 | const distinctField = mongoToPostgres.pathToText(['data'].concat(doc.key.split('.')), false)
127 | const arrayCondition = `jsonb_typeof(${distinctField})='array'`
128 | const query1 = `SELECT DISTINCT ${distinctField} AS data FROM ${collectionName} WHERE ${where} AND NOT ${arrayCondition}`
129 | const query2 = `SELECT DISTINCT jsonb_array_elements(${distinctField}) AS data FROM ${collectionName} WHERE ${where} AND ${arrayCondition}`
130 | const query = `${query1} UNION ${query2}`
131 | const res = await tryOrCreateTable(async () => await doQuery(databaseName, query), databaseName, collectionName)
132 | const rows = res.rows.map((row) => convertToBSON(row.data))
133 | debug('pgmongo:rows')(rows)
134 | const data = { values: rows, ok: 1 }
135 | socket.write(build(reqId, data, {}))
136 | return true
137 | }
138 | if (commandName === 'find') {
139 | const filter = doc.filter
140 | const where = safeWhereConversion(filter, rawCollectionName)
141 | let select = mongoToPostgres.convertSelect('data', doc.projection)
142 |
143 | let query = `SELECT ${select} FROM ${collectionName}`
144 | if (where !== 'TRUE') {
145 | query += ` WHERE ${where}`
146 | }
147 | if (doc.sort) {
148 | query += ' ORDER BY ' + mongoToPostgres.convertSort('data', doc.sort, doc.collation && doc.collation.numericOrdering)
149 | }
150 | if (doc.limit) {
151 | query += ' LIMIT ' + Math.abs(doc.limit)
152 | }
153 | if (doc.skip) {
154 | if (doc.skip < 0) {
155 | socket.write(build(reqId, { ok: 0, errmsg: 'negative skip not allowed' }, {}))
156 | return true
157 | }
158 | query += ' OFFSET ' + doc.skip
159 | }
160 | let res
161 | try {
162 | res = await tryOrCreateTable(async () => await doQuery(databaseName, query), databaseName, collectionName)
163 | } catch (e) {
164 | console.error(e)
165 | res = { rows: [] }
166 | }
167 | const rows = res.rows.map((row) => convertToBSON(row.data))
168 | debug('pgmongo:rows')(rows)
169 | const data = util.createCursor(databaseName + '.' + collectionName, rows)
170 | socket.write(build(reqId, data, {}))
171 | return true
172 | }
173 | if (commandName === 'count') {
174 | if (doc.skip && doc.skip < 0) {
175 | socket.write(build(reqId, { ok: 0, errmsg: 'negative skip not allowed' }, {}))
176 | return true
177 | }
178 | const where = mongoToPostgres('data', doc.query, getArrayPaths(rawCollectionName))
179 | const data = { n: 0, ok: 1 }
180 | try {
181 | const res = await doQuery(databaseName, `SELECT COUNT(*) FROM ${collectionName} WHERE ${where}`)
182 | data.n = res.rows[0].count
183 | if (doc.skip) {
184 | data.n = Math.max(0, data.n - doc.skip)
185 | }
186 | if (doc.limit) {
187 | data.n = Math.min(Math.abs(doc.limit), data.n)
188 | }
189 | } catch (e) {
190 | console.error(e)
191 | }
192 | socket.write(build(reqId, data, data))
193 | return true
194 | }
195 | if (commandName === 'update') {
196 | const update = doc.updates[0]
197 | const where = mongoToPostgres('data', update.q, getArrayPaths(rawCollectionName))
198 | let res
199 | try {
200 | const newValue = mongoToPostgres.convertUpdate('data', update.u)
201 | // TODO (handle multi)
202 | await createTable(collectionName)
203 | res = await doQuery(databaseName, `UPDATE ${collectionName} SET data = ${newValue} WHERE ${where}`)
204 | if (res.rowCount === 0 && update.upsert) {
205 | const changes = update.u || {}
206 | if (mongoToPostgres.countUpdateSpecialKeys(changes) === 0) {
207 | // TODO: expand dot notation
208 | _.assign(changes, update.q)
209 | } else {
210 | changes['$set'] = changes['$set'] || {}
211 | _.assign(changes['$set'], update.q)
212 | }
213 | const newValue = mongoToPostgres.convertUpdate('data', changes, true)
214 | async function insert() {
215 | const res = await doQuery(databaseName, `INSERT INTO ${collectionName} VALUES (${newValue})`)
216 | const data = { n: res.rowCount, nInserted: res.rowCount, updatedExisting: false, ok: 1 }
217 | socket.write(build(reqId, data, {}))
218 | }
219 |
220 | await tryOrCreateTable(insert, databaseName, collectionName)
221 | return true
222 | } else {
223 | lastResult.n = res.rowCount
224 | socket.write(build(reqId, lastResult, lastResult))
225 | return true
226 | }
227 | } catch (e) {
228 | console.error(e)
229 | if (e.message.includes('_id')) {
230 | const data = {
231 | errmsg: e.message,
232 | ok: 0
233 | }
234 | socket.write(build(reqId, data, data))
235 | return true
236 | }
237 | // Can also upsert, fallback
238 | const isChangingId = typeof update.u._id !== 'undefined'
239 | if (isChangingId) {
240 | console.error('failed to update, falling back to insert')
241 | commandName = 'insert'
242 | doc.documents = [update.u]
243 | }
244 | }
245 | }
246 | if (commandName === 'insert') {
247 | arrayPathsMap[rawCollectionName] = _.union(arrayPathsMap[rawCollectionName], util.getArrayPaths(doc.documents[0]))
248 | const newValues = doc.documents.map((values) => '(' + mongoToPostgres.convertUpdate('data', values) + ')')
249 | async function insert() {
250 | const res = await doQuery(databaseName, `INSERT INTO ${collectionName} VALUES ${newValues.join(',')}`)
251 | const data = { n: res.rowCount, nInserted: res.rowCount, updatedExisting: false, ok: 1 }
252 | socket.write(build(reqId, data, {}))
253 | }
254 |
255 | await tryOrCreateTable(insert, databaseName, collectionName)
256 | return true
257 | }
258 | if (commandName === 'create') {
259 | lastResult.ok = 1
260 | await createTable(collectionName)
261 | }
262 | if (commandName === 'delete') {
263 | async function del() {
264 | const where = mongoToPostgres('data', doc.deletes[0].q, getArrayPaths(rawCollectionName))
265 | // TODO (handle multi)
266 | let query = `DELETE FROM ${collectionName} WHERE ${where}`
267 | if (doc.deletes[0].limit) {
268 | // TODO: handle limits on deletion
269 | //query += ' LIMIT ' + doc.deletes[0].limit
270 | }
271 | const res = await doQuery(databaseName, query)
272 | lastResult.n = res.rowCount
273 | return socket.write(build(reqId, lastResult, lastResult))
274 | }
275 | await tryOrCreateTable(del, databaseName, collectionName)
276 | return true
277 | }
278 | if (commandName === 'drop') {
279 | try {
280 | await doQuery(databaseName, `DROP TABLE ${collectionName}`)
281 | } catch (e) {
282 | // often not an error if already doesn't exist
283 | }
284 | socket.write(build(reqId, lastResult, lastResult))
285 | return true
286 | }
287 | if (commandName === 'validate') {
288 | const countRes = await doQuery(`SELECT COUNT(*) FROM ${collectionName}`)
289 | const data = {
290 | 'ns' : databaseName + '.' + collectionName,
291 | 'nrecords' : countRes.rows[0].count,
292 | 'nIndexes' : 0,
293 | 'keysPerIndex' : {},
294 | 'valid' : true,
295 | 'warnings' : ['Some checks omitted for speed. use {full:true} option to do more thorough scan.'],
296 | 'errors' : [ ],
297 | 'ok' : 1
298 | }
299 | return socket.write(build(reqId, data, data))
300 | }
301 | if (normalizedCommandName === 'listindexes') {
302 | const listIQuery = util.listIndicesQuery('data', rawCollectionName)
303 | const indexRes = await doQuery(databaseName, listIQuery)
304 | const indices = indexRes.rows.map((row) => ({
305 | v: 2,
306 | key: { _id: 1 },
307 | name: row.index_name,
308 | ns: databaseName + '.' + collectionName
309 | }))
310 | const data = util.createCursor(databaseName + '.' + collectionName, indices)
311 | socket.write(build(reqId, data, data))
312 | return true
313 | }
314 | if (normalizedCommandName === 'createindexes') {
315 | rawCollectionName = rawCollectionName || doc['createIndexes']
316 | collectionName = '"' + rawCollectionName + '"'
317 | const data = {
318 | createIndexes: {
319 | numIndexesBefore: 0,
320 | numIndexesAfter: 0,
321 | ok: 1
322 | },
323 | ok: 1
324 | }
325 | const listIQuery = util.listIndicesQuery('data', rawCollectionName)
326 | const indexRes = await doQuery(databaseName, listIQuery)
327 | const indices = indexRes.rows.map((row) => row.index_name)
328 | data.createIndexes.numIndexesBefore = indices.length
329 | for (const index of doc.indexes) {
330 | if (indices.includes(index.name)) {
331 | continue
332 | }
333 | const keys = Object.keys(index.key)
334 | /*if (keys.length > 1) {
335 | console.error(keys)
336 | throw new Error('compound indices not supported');
337 | }*/
338 | const pgPath = keys.map(function(key) {
339 | const path = ['data'].concat(key.split('.'))
340 | return mongoToPostgres.pathToText(path, false)
341 | }).join(', ')
342 | let indexQuery = `CREATE INDEX "${index.name}" ON ${collectionName} USING gin ((${pgPath}));`
343 | try {
344 | await doQuery(databaseName, indexQuery)
345 | } catch (e) {
346 | console.error('failed to create index')
347 | //console.error(e)
348 | }
349 | data.createIndexes.numIndexesAfter++
350 | }
351 | if (normalizedCommandName === 'deleteindexes') {
352 | // Todo
353 | // Also handle case where index is "*" and all need to be dropped.
354 | }
355 | socket.write(build(reqId, data, data))
356 | return true
357 | }
358 | if (normalizedCommandName === 'renameCollection') {
359 | const data = { ok: 1 }
360 | collectionName = doc[commandName].split('.', 2)[1]
361 | const newName = doc.to.split('.', 2)[1]
362 | let query = `ALTER TABLE ${collectionName} RENAME TO "${newName}"`
363 | query = doc.dropTarget ? `DROP TABLE IF EXISTS ${newName}; ${query}` : query
364 | try {
365 | await doQuery(databaseName, query)
366 | } catch (e) {
367 | data.ok = 0
368 | console.error(e)
369 | }
370 | socket.write(build(reqId, data, data))
371 | return true
372 | }
373 | }
374 |
375 | async function processAdmin(databaseName, socket, reqId, commandName, doc) {
376 | let res
377 | let reply
378 | switch (commandName) {
379 | case 'listdatabases':
380 | try {
381 | res = await doQuery('postgres', 'SELECT datname AS name FROM pg_database WHERE datistemplate = false')
382 | } catch (e) {
383 | console.error(e)
384 | res = { rows: [] }
385 | }
386 | reply = { 'databases': res.rows, 'ok': 1 }
387 | socket.write(wire.createCommandReply(reqId, reply, reply))
388 | break
389 | case 'whatsmyuri':
390 | socket.write(wire.createCommandReply(reqId, {}, { 'you': '127.0.0.1:56709', 'ok': 1 }))
391 | break
392 | case 'getlog':
393 | if (doc.getLog === 'startupWarnings') {
394 | const log = {
395 | 'totalLinesWritten': 1,
396 | 'log': ['This is a Postgres database using the Mongo wrapper.'],
397 | 'ok': 1
398 | }
399 | socket.write(wire.createCommandReply(reqId, log, log))
400 | } else {
401 | return false
402 | }
403 | break
404 | case 'replsetgetstatus':
405 | socket.write(wire.createCommandReply(reqId, {}, adminReplies.replSetGetStatus()))
406 | break
407 | case 'serverstatus':
408 | socket.write(wire.createCommandReply(reqId, adminReplies.getServerStatus(), {}))
409 | break
410 | case 'currentop':
411 | reply = {
412 | inprog: [],
413 | ok: 1
414 | }
415 | socket.write(wire.createCommandReply(reqId, reply, reply))
416 | break
417 | default:
418 | return false
419 | }
420 | return true
421 | }
422 |
423 | let previousData
424 |
425 | async function processRecord(socket, data) {
426 | if (previousData) {
427 | data = Buffer.concat([previousData, data])
428 | previousData = null
429 | }
430 | const { msgLength, reqId, opCode } = wire.parseHeader(data)
431 |
432 | if (data.length < msgLength - 1) {
433 | previousData = data
434 | return console.log('partial data received')
435 | }
436 |
437 | try {
438 | await handleRecord(socket, data, opCode, reqId)
439 | } catch (e) {
440 | console.error(e)
441 | if (e.code) {
442 | return socket.write(wire.createCommandReply(reqId, e, {}))
443 | }
444 | return socket.write(wire.createCommandReply(reqId, { errmsg: e.message, ok: 0 }, { ok: 0 }))
445 | }
446 | }
447 | async function handleRecord(socket, data, opCode, reqId) {
448 | if (opCode === wire.OP_QUERY) {
449 | const { doc, collectionName } = wire.parseQuery(data)
450 | if (collectionName === 'admin.$cmd' && (doc.isMaster || doc.ismaster)) {
451 | return socket.write(wire.createResponse(reqId, adminReplies.getIsMasterReply()))
452 | }
453 | const parts = collectionName.split('.')
454 | const databaseName = parts[0]
455 | for (const command of commands) {
456 | if (doc[command] || doc[command.toLowerCase()]) {
457 | if (await crud(socket, reqId, databaseName, command, doc, wire.createResponse))
458 | return
459 | }
460 | }
461 | console.dir({ err: 'UNHANDLED REQUEST OP_QUERY', collectionName, doc })
462 | return socket.write(wire.createCommandReply(reqId, { ok: 0 }, { ok: 0 }))
463 | } else if (opCode === wire.OP_COMMAND) {
464 | const { databaseName, commandName, doc } = wire.parseCommand(data)
465 | if (await crud(socket, reqId, databaseName, commandName, doc, wire.createCommandReply))
466 | return
467 |
468 | if (databaseName === 'admin' || databaseName === 'db') {
469 | if (await processAdmin(databaseName, socket, reqId, commandName, doc))
470 | return
471 | }
472 |
473 | let res
474 | switch (commandName) {
475 | case 'ismaster':
476 | return socket.write(wire.createCommandReply(reqId, adminReplies.getIsMasterReply(), adminReplies.getIsMasterReply()))
477 | case 'buildinfo':
478 | return socket.write(wire.createCommandReply(reqId, adminReplies.getBuildInfo(), {}))
479 | case 'listcollections':
480 | res = await doQuery(databaseName, 'SELECT table_name FROM information_schema.tables WHERE table_schema=\'public\' AND table_type=\'BASE TABLE\';')
481 | const tables = res.rows.map((row) => ({ name: row.table_name, type: 'collection', options: {}, info: { readOnly: false } }))
482 | const data = util.createCursor(`${databaseName}.$cmd.listCollections`, tables)
483 | return socket.write(wire.createCommandReply(reqId, data, data))
484 | case 'ping':
485 | return socket.write(wire.createCommandReply(reqId, {}, { 'ok': 1 }))
486 | case 'getlasterror':
487 | return socket.write(wire.createCommandReply(reqId, lastResult, lastResult))
488 | case 'dropdatabase':
489 | res = await doQuery(databaseName, 'select string_agg(\'drop table "\' || tablename || \'" cascade\', \'; \') from pg_tables where schemaname = \'public\'')
490 | await doQuery(databaseName, res.rows[0].string_agg)
491 | return socket.write(wire.createCommandReply(reqId, {}, { 'ok': 1 }))
492 | }
493 |
494 | console.dir({ err: 'UNHANDLED REQUEST', commandName, databaseName, doc })
495 | return socket.write(wire.createCommandReply(reqId, { ok: 0 }, { ok: 0 }))
496 | }
497 | }
498 |
499 | server.listen(mongoPort, '127.0.0.1')
500 | console.log(`Connecting to postgres at ${pgHost}:5432`)
501 | console.log(`Serving Mongo on port ${mongoPort}`)
502 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pgmongo",
3 | "version": "0.0.3",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "assertion-error": {
8 | "version": "1.1.0",
9 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
10 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
11 | "dev": true
12 | },
13 | "balanced-match": {
14 | "version": "1.0.0",
15 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
16 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
17 | "dev": true
18 | },
19 | "bindings": {
20 | "version": "1.3.0",
21 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz",
22 | "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw=="
23 | },
24 | "brace-expansion": {
25 | "version": "1.1.11",
26 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
27 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
28 | "dev": true,
29 | "requires": {
30 | "balanced-match": "^1.0.0",
31 | "concat-map": "0.0.1"
32 | }
33 | },
34 | "browser-stdout": {
35 | "version": "1.3.1",
36 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
37 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
38 | "dev": true
39 | },
40 | "bson": {
41 | "version": "2.0.8",
42 | "resolved": "https://registry.npmjs.org/bson/-/bson-2.0.8.tgz",
43 | "integrity": "sha512-0F0T3gHeOwJzHWcN60BZomqj5hCBDRk4b3fANuruvDTnyJJ8sggABKSaePM2F34THNZZSIlB2P1mk2nQWgBr9w=="
44 | },
45 | "bson-ext": {
46 | "version": "2.0.0",
47 | "resolved": "https://registry.npmjs.org/bson-ext/-/bson-ext-2.0.0.tgz",
48 | "integrity": "sha512-iP6gYML/9BjYHVKnvxMzfGcnOxDeFOMJwFW5bbNnSD1iOhM9McdCsvMacPFmrX6ZeOKXui0cqXDTmB+EpLg5xQ==",
49 | "requires": {
50 | "bindings": "^1.3.0",
51 | "bson": "^2.0.2",
52 | "nan": "^2.9.2"
53 | }
54 | },
55 | "buffer-writer": {
56 | "version": "1.0.1",
57 | "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz",
58 | "integrity": "sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg="
59 | },
60 | "builtin-modules": {
61 | "version": "1.1.1",
62 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
63 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
64 | "dev": true
65 | },
66 | "chai": {
67 | "version": "4.1.2",
68 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz",
69 | "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=",
70 | "dev": true,
71 | "requires": {
72 | "assertion-error": "^1.0.1",
73 | "check-error": "^1.0.1",
74 | "deep-eql": "^3.0.0",
75 | "get-func-name": "^2.0.0",
76 | "pathval": "^1.0.0",
77 | "type-detect": "^4.0.0"
78 | }
79 | },
80 | "check-error": {
81 | "version": "1.0.2",
82 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
83 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
84 | "dev": true
85 | },
86 | "commander": {
87 | "version": "2.15.1",
88 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
89 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
90 | "dev": true
91 | },
92 | "concat-map": {
93 | "version": "0.0.1",
94 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
95 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
96 | "dev": true
97 | },
98 | "contains-path": {
99 | "version": "0.1.0",
100 | "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
101 | "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
102 | "dev": true
103 | },
104 | "debug": {
105 | "version": "3.1.0",
106 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
107 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
108 | "requires": {
109 | "ms": "2.0.0"
110 | }
111 | },
112 | "deep-eql": {
113 | "version": "3.0.1",
114 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
115 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
116 | "dev": true,
117 | "requires": {
118 | "type-detect": "^4.0.0"
119 | }
120 | },
121 | "diff": {
122 | "version": "3.5.0",
123 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
124 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
125 | "dev": true
126 | },
127 | "doctrine": {
128 | "version": "1.5.0",
129 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
130 | "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
131 | "dev": true,
132 | "requires": {
133 | "esutils": "^2.0.2",
134 | "isarray": "^1.0.0"
135 | }
136 | },
137 | "error-ex": {
138 | "version": "1.3.1",
139 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
140 | "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
141 | "dev": true,
142 | "requires": {
143 | "is-arrayish": "^0.2.1"
144 | }
145 | },
146 | "escape-string-regexp": {
147 | "version": "1.0.5",
148 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
149 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
150 | "dev": true
151 | },
152 | "eslint-config-airbnb-base": {
153 | "version": "12.1.0",
154 | "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz",
155 | "integrity": "sha512-/vjm0Px5ZCpmJqnjIzcFb9TKZrKWz0gnuG/7Gfkt0Db1ELJR51xkZth+t14rYdqWgX836XbuxtArbIHlVhbLBA==",
156 | "dev": true,
157 | "requires": {
158 | "eslint-restricted-globals": "^0.1.1"
159 | }
160 | },
161 | "eslint-import-resolver-node": {
162 | "version": "0.3.2",
163 | "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz",
164 | "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==",
165 | "dev": true,
166 | "requires": {
167 | "debug": "^2.6.9",
168 | "resolve": "^1.5.0"
169 | },
170 | "dependencies": {
171 | "debug": {
172 | "version": "2.6.9",
173 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
174 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
175 | "dev": true,
176 | "requires": {
177 | "ms": "2.0.0"
178 | }
179 | }
180 | }
181 | },
182 | "eslint-module-utils": {
183 | "version": "2.2.0",
184 | "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz",
185 | "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=",
186 | "dev": true,
187 | "requires": {
188 | "debug": "^2.6.8",
189 | "pkg-dir": "^1.0.0"
190 | },
191 | "dependencies": {
192 | "debug": {
193 | "version": "2.6.9",
194 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
195 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
196 | "dev": true,
197 | "requires": {
198 | "ms": "2.0.0"
199 | }
200 | }
201 | }
202 | },
203 | "eslint-plugin-import": {
204 | "version": "2.12.0",
205 | "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.12.0.tgz",
206 | "integrity": "sha1-2tMXgSktZmSyUxf9BJ0uKy8CIF0=",
207 | "dev": true,
208 | "requires": {
209 | "contains-path": "^0.1.0",
210 | "debug": "^2.6.8",
211 | "doctrine": "1.5.0",
212 | "eslint-import-resolver-node": "^0.3.1",
213 | "eslint-module-utils": "^2.2.0",
214 | "has": "^1.0.1",
215 | "lodash": "^4.17.4",
216 | "minimatch": "^3.0.3",
217 | "read-pkg-up": "^2.0.0",
218 | "resolve": "^1.6.0"
219 | },
220 | "dependencies": {
221 | "debug": {
222 | "version": "2.6.9",
223 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
224 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
225 | "dev": true,
226 | "requires": {
227 | "ms": "2.0.0"
228 | }
229 | }
230 | }
231 | },
232 | "eslint-restricted-globals": {
233 | "version": "0.1.1",
234 | "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz",
235 | "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=",
236 | "dev": true
237 | },
238 | "esutils": {
239 | "version": "2.0.2",
240 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
241 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
242 | "dev": true
243 | },
244 | "find-up": {
245 | "version": "1.1.2",
246 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
247 | "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
248 | "dev": true,
249 | "requires": {
250 | "path-exists": "^2.0.0",
251 | "pinkie-promise": "^2.0.0"
252 | }
253 | },
254 | "flatten-obj": {
255 | "version": "3.1.1",
256 | "resolved": "https://registry.npmjs.org/flatten-obj/-/flatten-obj-3.1.1.tgz",
257 | "integrity": "sha1-pU7+tFryMEYs2IHtWtodHNryRjE=",
258 | "requires": {
259 | "isobj": "^1.0.0"
260 | }
261 | },
262 | "fs.realpath": {
263 | "version": "1.0.0",
264 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
265 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
266 | "dev": true
267 | },
268 | "function-bind": {
269 | "version": "1.1.1",
270 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
271 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
272 | "dev": true
273 | },
274 | "get-func-name": {
275 | "version": "2.0.0",
276 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
277 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
278 | "dev": true
279 | },
280 | "glob": {
281 | "version": "7.1.2",
282 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
283 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
284 | "dev": true,
285 | "requires": {
286 | "fs.realpath": "^1.0.0",
287 | "inflight": "^1.0.4",
288 | "inherits": "2",
289 | "minimatch": "^3.0.4",
290 | "once": "^1.3.0",
291 | "path-is-absolute": "^1.0.0"
292 | }
293 | },
294 | "graceful-fs": {
295 | "version": "4.1.11",
296 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
297 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
298 | "dev": true
299 | },
300 | "growl": {
301 | "version": "1.10.5",
302 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
303 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
304 | "dev": true
305 | },
306 | "has": {
307 | "version": "1.0.3",
308 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
309 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
310 | "dev": true,
311 | "requires": {
312 | "function-bind": "^1.1.1"
313 | }
314 | },
315 | "has-flag": {
316 | "version": "3.0.0",
317 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
318 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
319 | "dev": true
320 | },
321 | "he": {
322 | "version": "1.1.1",
323 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
324 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
325 | "dev": true
326 | },
327 | "hosted-git-info": {
328 | "version": "2.6.0",
329 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz",
330 | "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==",
331 | "dev": true
332 | },
333 | "inflight": {
334 | "version": "1.0.6",
335 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
336 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
337 | "dev": true,
338 | "requires": {
339 | "once": "^1.3.0",
340 | "wrappy": "1"
341 | }
342 | },
343 | "inherits": {
344 | "version": "2.0.3",
345 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
346 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
347 | "dev": true
348 | },
349 | "is-arrayish": {
350 | "version": "0.2.1",
351 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
352 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
353 | "dev": true
354 | },
355 | "is-builtin-module": {
356 | "version": "1.0.0",
357 | "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
358 | "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
359 | "dev": true,
360 | "requires": {
361 | "builtin-modules": "^1.0.0"
362 | }
363 | },
364 | "isarray": {
365 | "version": "1.0.0",
366 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
367 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
368 | "dev": true
369 | },
370 | "isobj": {
371 | "version": "1.0.0",
372 | "resolved": "https://registry.npmjs.org/isobj/-/isobj-1.0.0.tgz",
373 | "integrity": "sha1-Z7oo+cbcmBMOuho3m9y9HZOcc/Q="
374 | },
375 | "load-json-file": {
376 | "version": "2.0.0",
377 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
378 | "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
379 | "dev": true,
380 | "requires": {
381 | "graceful-fs": "^4.1.2",
382 | "parse-json": "^2.2.0",
383 | "pify": "^2.0.0",
384 | "strip-bom": "^3.0.0"
385 | }
386 | },
387 | "locate-path": {
388 | "version": "2.0.0",
389 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
390 | "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
391 | "dev": true,
392 | "requires": {
393 | "p-locate": "^2.0.0",
394 | "path-exists": "^3.0.0"
395 | },
396 | "dependencies": {
397 | "path-exists": {
398 | "version": "3.0.0",
399 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
400 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
401 | "dev": true
402 | }
403 | }
404 | },
405 | "lodash": {
406 | "version": "4.17.10",
407 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
408 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
409 | },
410 | "minimatch": {
411 | "version": "3.0.4",
412 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
413 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
414 | "dev": true,
415 | "requires": {
416 | "brace-expansion": "^1.1.7"
417 | }
418 | },
419 | "minimist": {
420 | "version": "0.0.8",
421 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
422 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
423 | "dev": true
424 | },
425 | "mkdirp": {
426 | "version": "0.5.1",
427 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
428 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
429 | "dev": true,
430 | "requires": {
431 | "minimist": "0.0.8"
432 | }
433 | },
434 | "mocha": {
435 | "version": "5.2.0",
436 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
437 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
438 | "dev": true,
439 | "requires": {
440 | "browser-stdout": "1.3.1",
441 | "commander": "2.15.1",
442 | "debug": "3.1.0",
443 | "diff": "3.5.0",
444 | "escape-string-regexp": "1.0.5",
445 | "glob": "7.1.2",
446 | "growl": "1.10.5",
447 | "he": "1.1.1",
448 | "minimatch": "3.0.4",
449 | "mkdirp": "0.5.1",
450 | "supports-color": "5.4.0"
451 | }
452 | },
453 | "mongo-query-to-postgres-jsonb": {
454 | "version": "0.2.0",
455 | "resolved": "https://registry.npmjs.org/mongo-query-to-postgres-jsonb/-/mongo-query-to-postgres-jsonb-0.2.0.tgz",
456 | "integrity": "sha1-lb8eKMxZjxVt3babTzIeOeSVK7c="
457 | },
458 | "ms": {
459 | "version": "2.0.0",
460 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
461 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
462 | },
463 | "nan": {
464 | "version": "2.10.0",
465 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
466 | "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
467 | },
468 | "normalize-package-data": {
469 | "version": "2.4.0",
470 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
471 | "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
472 | "dev": true,
473 | "requires": {
474 | "hosted-git-info": "^2.1.4",
475 | "is-builtin-module": "^1.0.0",
476 | "semver": "2 || 3 || 4 || 5",
477 | "validate-npm-package-license": "^3.0.1"
478 | }
479 | },
480 | "once": {
481 | "version": "1.4.0",
482 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
483 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
484 | "dev": true,
485 | "requires": {
486 | "wrappy": "1"
487 | }
488 | },
489 | "p-limit": {
490 | "version": "1.3.0",
491 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
492 | "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
493 | "dev": true,
494 | "requires": {
495 | "p-try": "^1.0.0"
496 | }
497 | },
498 | "p-locate": {
499 | "version": "2.0.0",
500 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
501 | "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
502 | "dev": true,
503 | "requires": {
504 | "p-limit": "^1.1.0"
505 | }
506 | },
507 | "p-try": {
508 | "version": "1.0.0",
509 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
510 | "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
511 | "dev": true
512 | },
513 | "packet-reader": {
514 | "version": "0.3.1",
515 | "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz",
516 | "integrity": "sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc="
517 | },
518 | "parse-json": {
519 | "version": "2.2.0",
520 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
521 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
522 | "dev": true,
523 | "requires": {
524 | "error-ex": "^1.2.0"
525 | }
526 | },
527 | "path-exists": {
528 | "version": "2.1.0",
529 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
530 | "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
531 | "dev": true,
532 | "requires": {
533 | "pinkie-promise": "^2.0.0"
534 | }
535 | },
536 | "path-is-absolute": {
537 | "version": "1.0.1",
538 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
539 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
540 | "dev": true
541 | },
542 | "path-parse": {
543 | "version": "1.0.5",
544 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
545 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
546 | "dev": true
547 | },
548 | "path-type": {
549 | "version": "2.0.0",
550 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
551 | "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
552 | "dev": true,
553 | "requires": {
554 | "pify": "^2.0.0"
555 | }
556 | },
557 | "pathval": {
558 | "version": "1.1.0",
559 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
560 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
561 | "dev": true
562 | },
563 | "pg": {
564 | "version": "7.4.3",
565 | "resolved": "https://registry.npmjs.org/pg/-/pg-7.4.3.tgz",
566 | "integrity": "sha1-97b5P1NA7MJZavu5ShPj1rYJg0s=",
567 | "requires": {
568 | "buffer-writer": "1.0.1",
569 | "packet-reader": "0.3.1",
570 | "pg-connection-string": "0.1.3",
571 | "pg-pool": "~2.0.3",
572 | "pg-types": "~1.12.1",
573 | "pgpass": "1.x",
574 | "semver": "4.3.2"
575 | }
576 | },
577 | "pg-connection-string": {
578 | "version": "0.1.3",
579 | "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz",
580 | "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc="
581 | },
582 | "pg-pool": {
583 | "version": "2.0.3",
584 | "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.3.tgz",
585 | "integrity": "sha1-wCIDLIlJ8xKk+R+2QJzgQHa+Mlc="
586 | },
587 | "pg-types": {
588 | "version": "1.12.1",
589 | "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz",
590 | "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=",
591 | "requires": {
592 | "postgres-array": "~1.0.0",
593 | "postgres-bytea": "~1.0.0",
594 | "postgres-date": "~1.0.0",
595 | "postgres-interval": "^1.1.0"
596 | }
597 | },
598 | "pgpass": {
599 | "version": "1.0.2",
600 | "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz",
601 | "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=",
602 | "requires": {
603 | "split": "^1.0.0"
604 | }
605 | },
606 | "pify": {
607 | "version": "2.3.0",
608 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
609 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
610 | "dev": true
611 | },
612 | "pinkie": {
613 | "version": "2.0.4",
614 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
615 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
616 | "dev": true
617 | },
618 | "pinkie-promise": {
619 | "version": "2.0.1",
620 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
621 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
622 | "dev": true,
623 | "requires": {
624 | "pinkie": "^2.0.0"
625 | }
626 | },
627 | "pkg-dir": {
628 | "version": "1.0.0",
629 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
630 | "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
631 | "dev": true,
632 | "requires": {
633 | "find-up": "^1.0.0"
634 | }
635 | },
636 | "postgres-array": {
637 | "version": "1.0.2",
638 | "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz",
639 | "integrity": "sha1-jgsy6wO/d6XAp4UeBEHBaaJWojg="
640 | },
641 | "postgres-bytea": {
642 | "version": "1.0.0",
643 | "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
644 | "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU="
645 | },
646 | "postgres-date": {
647 | "version": "1.0.3",
648 | "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz",
649 | "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g="
650 | },
651 | "postgres-interval": {
652 | "version": "1.1.1",
653 | "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz",
654 | "integrity": "sha512-OkuCi9t/3CZmeQreutGgx/OVNv9MKHGIT5jH8KldQ4NLYXkvmT9nDVxEuCENlNwhlGPE374oA/xMqn05G49pHA==",
655 | "requires": {
656 | "xtend": "^4.0.0"
657 | }
658 | },
659 | "read-pkg": {
660 | "version": "2.0.0",
661 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
662 | "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
663 | "dev": true,
664 | "requires": {
665 | "load-json-file": "^2.0.0",
666 | "normalize-package-data": "^2.3.2",
667 | "path-type": "^2.0.0"
668 | }
669 | },
670 | "read-pkg-up": {
671 | "version": "2.0.0",
672 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
673 | "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
674 | "dev": true,
675 | "requires": {
676 | "find-up": "^2.0.0",
677 | "read-pkg": "^2.0.0"
678 | },
679 | "dependencies": {
680 | "find-up": {
681 | "version": "2.1.0",
682 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
683 | "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
684 | "dev": true,
685 | "requires": {
686 | "locate-path": "^2.0.0"
687 | }
688 | }
689 | }
690 | },
691 | "resolve": {
692 | "version": "1.7.1",
693 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz",
694 | "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
695 | "dev": true,
696 | "requires": {
697 | "path-parse": "^1.0.5"
698 | }
699 | },
700 | "semver": {
701 | "version": "4.3.2",
702 | "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz",
703 | "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c="
704 | },
705 | "spdx-correct": {
706 | "version": "3.0.0",
707 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz",
708 | "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
709 | "dev": true,
710 | "requires": {
711 | "spdx-expression-parse": "^3.0.0",
712 | "spdx-license-ids": "^3.0.0"
713 | }
714 | },
715 | "spdx-exceptions": {
716 | "version": "2.1.0",
717 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz",
718 | "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==",
719 | "dev": true
720 | },
721 | "spdx-expression-parse": {
722 | "version": "3.0.0",
723 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
724 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
725 | "dev": true,
726 | "requires": {
727 | "spdx-exceptions": "^2.1.0",
728 | "spdx-license-ids": "^3.0.0"
729 | }
730 | },
731 | "spdx-license-ids": {
732 | "version": "3.0.0",
733 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz",
734 | "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==",
735 | "dev": true
736 | },
737 | "split": {
738 | "version": "1.0.1",
739 | "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
740 | "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
741 | "requires": {
742 | "through": "2"
743 | }
744 | },
745 | "strip-bom": {
746 | "version": "3.0.0",
747 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
748 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
749 | "dev": true
750 | },
751 | "supports-color": {
752 | "version": "5.4.0",
753 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
754 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
755 | "dev": true,
756 | "requires": {
757 | "has-flag": "^3.0.0"
758 | }
759 | },
760 | "through": {
761 | "version": "2.3.8",
762 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
763 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
764 | },
765 | "type-detect": {
766 | "version": "4.0.8",
767 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
768 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
769 | "dev": true
770 | },
771 | "validate-npm-package-license": {
772 | "version": "3.0.3",
773 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz",
774 | "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==",
775 | "dev": true,
776 | "requires": {
777 | "spdx-correct": "^3.0.0",
778 | "spdx-expression-parse": "^3.0.0"
779 | }
780 | },
781 | "wrappy": {
782 | "version": "1.0.2",
783 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
784 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
785 | "dev": true
786 | },
787 | "xtend": {
788 | "version": "4.0.1",
789 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
790 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
791 | }
792 | }
793 | }
794 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pgmongo",
3 | "version": "0.1.0",
4 | "description": "implements the Mongo wire protocol and adapts queries to use Postgres jsonb.",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js mongotest",
8 | "test": "mocha"
9 | },
10 | "bin": {
11 | "pgmongo": "./index.js"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/thomas4019/postres-mongo-emulator.git"
16 | },
17 | "author": "Thomas Hansen",
18 | "license": "ISC",
19 | "bugs": {
20 | "url": "https://github.com/thomas4019/postres-mongo-emulator/issues"
21 | },
22 | "homepage": "https://github.com/thomas4019/postres-mongo-emulator#readme",
23 | "dependencies": {
24 | "bson-ext": "^2.0.0",
25 | "debug": "^3.1.0",
26 | "flatten-obj": "^3.1.1",
27 | "lodash": "^4.17.10",
28 | "mongo-query-to-postgres-jsonb": "^0.2.0",
29 | "pg": "^7.4.3"
30 | },
31 | "devDependencies": {
32 | "chai": "^4.1.2",
33 | "eslint-config-airbnb-base": "^12.1.0",
34 | "eslint-plugin-import": "^2.12.0",
35 | "mocha": "^5.2.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/util.js:
--------------------------------------------------------------------------------
1 | const assert = require('chai').assert
2 | const BSON = require('bson-ext')
3 | const util = require('../util')
4 | const describeTypes = util.describeTypes
5 | const getArrayPaths = util.getArrayPaths
6 |
7 | describe('util: describeTypes', function() {
8 | it('basic type', function () {
9 | assert.equal(describeTypes(BSON.ObjectID('5b1e1a6fa00fd75c4e6c4c30')), 'ObjectID')
10 | })
11 | it('returns undefined for strings', function () {
12 | assert.equal(describeTypes('123'), undefined)
13 | })
14 | it('object with no special values', function () {
15 | assert.deepEqual(describeTypes({ a: '123', b: 12 }), {})
16 | })
17 | it('arrays', function () {
18 | assert.deepEqual(describeTypes({ arr: [1, 2] }), { arr: [] })
19 | })
20 | it('array of ids', function () {
21 | assert.deepEqual(describeTypes({ arr: [BSON.ObjectID('5b1e1a6fa00fd75c4e6c4c30'), BSON.ObjectID('5b1e1a6fa00fd75c4e6c4c32')] }), { arr: ['ObjectID'] })
22 | })
23 | it('object with mixed', function () {
24 | assert.deepEqual(describeTypes({ a: '123', id2: BSON.ObjectID('5b1e1a6fa00fd75c4e6c4c30') }), { id2: 'ObjectID'})
25 | })
26 | })
27 |
28 | describe('util: getArrayPaths', function() {
29 | it('arrays', function () {
30 | assert.deepEqual(getArrayPaths({ arr: [1, 2] }), ['arr'] )
31 | })
32 | it('multiple arrays', function () {
33 | assert.deepEqual(getArrayPaths({ arr: [1, 2], arr2: ['1'] }), ['arr', 'arr2'] )
34 | })
35 | it('deep array', function () {
36 | assert.deepEqual(getArrayPaths({ deep: { arr: [1, 2] } }), ['deep.arr'] )
37 | })
38 | it('array of ids', function () {
39 | assert.deepEqual(getArrayPaths({ arr: [BSON.ObjectID('5b1e1a6fa00fd75c4e6c4c30'), BSON.ObjectID('5b1e1a6fa00fd75c4e6c4c32')] }), ['arr'])
40 | })
41 | it('object with mixed', function () {
42 | assert.deepEqual(getArrayPaths({ a: '123', id2: BSON.ObjectID('5b1e1a6fa00fd75c4e6c4c30') }), [] )
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/util.js:
--------------------------------------------------------------------------------
1 | const BSON = require('bson-ext')
2 | const Long = BSON.Long
3 | const _ = require('lodash')
4 |
5 | exports.describeTypes = function (doc) {
6 | if (doc._bsontype) {
7 | return doc._bsontype
8 | } else if (Array.isArray(doc)) {
9 | const types = doc.map(exports.describeTypes)
10 | const uniqueTypes = _.uniqWith(types, _.isEqual)
11 | if (uniqueTypes.length > 1) {
12 | //throw new Error('arrays containing multiple data types are not allowed')
13 | }
14 | if (_.isUndefined(uniqueTypes[0])) {
15 | return []
16 | }
17 | return uniqueTypes
18 | } else if (typeof doc === 'object') {
19 | const out = {}
20 | for (const key of Object.keys(doc)) {
21 | const v = exports.describeTypes(doc[key])
22 | if (typeof v !== 'undefined') {
23 | out[key] = v
24 | }
25 | }
26 | return out
27 | }
28 | }
29 |
30 | exports.getArrayPaths = function (doc) {
31 | const arrayPaths = []
32 |
33 | function recursiveHepler(path, doc) {
34 | if (Array.isArray(doc)) {
35 | arrayPaths.push(path)
36 | } else if (doc && typeof doc === 'object') {
37 | for (const key of Object.keys(doc)) {
38 | recursiveHepler(path ? (path + '.' + key) : key, doc[key])
39 | }
40 | }
41 | }
42 |
43 | recursiveHepler('', doc)
44 | return arrayPaths
45 | }
46 |
47 | exports.createCursor = function (ns, firstBatch, id = Long.fromNumber(0)) {
48 | return {
49 | cursor: {
50 | id,
51 | ns,
52 | firstBatch
53 | },
54 | ok: 1
55 | }
56 | }
57 |
58 | exports.listIndicesQuery = function (fieldName, collectionName) {
59 | return `select
60 | t.relname as table_name,
61 | i.relname as index_name,
62 | a.attname as column_name
63 | from
64 | pg_class t,
65 | pg_class i,
66 | pg_index ix,
67 | pg_attribute a
68 | where
69 | t.oid = ix.indrelid
70 | and i.oid = ix.indexrelid
71 | and a.attrelid = t.oid
72 | and t.relkind = 'r'
73 | and t.relname = '${collectionName}'
74 | and a.attname = '${fieldName}'
75 | order by
76 | t.relname,
77 | i.relname;`
78 | }
79 |
--------------------------------------------------------------------------------
/wireprotocol.js:
--------------------------------------------------------------------------------
1 | const BSON = require('bson-ext')
2 | const bson = new BSON([BSON.Binary, BSON.Code, BSON.DBRef, BSON.Decimal128, BSON.Double, BSON.Int32, BSON.Long, BSON.Map, BSON.MaxKey, BSON.MinKey, BSON.ObjectId, BSON.BSONRegExp, BSON.Symbol, BSON.Timestamp])
3 | const debug = require('debug')
4 |
5 | const OP_REPLY = 1
6 | const OP_COMMANDREPLY = 2011
7 |
8 | function getRandomInt(max) {
9 | max = max || Math.pow(2, 31) - 1
10 | return Math.floor(Math.random() * Math.floor(max))
11 | }
12 |
13 | exports.OP_QUERY = 2004
14 | exports.OP_COMMAND = 2010
15 |
16 | exports.parseHeader = function(buffer) {
17 | const opCode = buffer.readInt32LE(12)
18 | debug('pgmongo:opcode')(opCode)
19 | return {
20 | msgLength: buffer.readInt32LE(0),
21 | reqId: buffer.readInt32LE(4),
22 | opCode
23 | }
24 | }
25 |
26 | exports.parseQuery = function(buffer) {
27 | const flags = buffer.readInt32LE(16)
28 | const nameEnd = buffer.indexOf('\0', 20) + 1
29 | const collectionName = buffer.toString('utf8', 20, nameEnd - 1)
30 | const documents = []
31 | try {
32 | bson.deserializeStream(buffer, nameEnd + 8, 1, documents, 0)
33 | } catch (e) {
34 | console.error('error - missing or invalid body')
35 | }
36 | const doc = documents[0]
37 | return {
38 | flags,
39 | collectionName,
40 | doc
41 | }
42 | }
43 |
44 | exports.parseCommand = function(buffer) {
45 | const databaseEnd = buffer.indexOf('\0', 16) + 1
46 | const databaseName = buffer.toString('utf8', 16, databaseEnd - 1)
47 | const commandEnd = buffer.indexOf('\0', databaseEnd) + 1
48 | const commandName = buffer.toString('utf8', databaseEnd, commandEnd - 1).toLowerCase()
49 | const documents = []
50 | bson.deserializeStream(buffer, commandEnd, 2, documents, 0)
51 | const doc = documents[0]
52 |
53 | debug('pgmongo:indoc')(doc)
54 | return {
55 | databaseName,
56 | commandName,
57 | doc
58 | }
59 | }
60 |
61 | exports.createCommandReply = function(reqId, metadata, commandReply) {
62 | debug('pgmongo:reply')(metadata)
63 | if (metadata !== commandReply) {
64 | debug('pgmongo:reply2')(commandReply)
65 | }
66 | const metadataBuffer = bson.serialize(metadata)
67 | const replyBuffer = bson.serialize(commandReply)
68 | const length = 16 + metadataBuffer.length + replyBuffer.length
69 |
70 | const buf = Buffer.alloc(length)
71 | buf.writeInt32LE(length, 0)
72 | buf.writeInt32LE(getRandomInt(), 4)
73 | buf.writeInt32LE(reqId, 8)
74 | buf.writeInt32LE(OP_COMMANDREPLY, 12)
75 |
76 | metadataBuffer.copy(buf, 16)
77 | replyBuffer.copy(buf, 16 + metadataBuffer.length)
78 |
79 | return buf
80 | }
81 |
82 | exports.createResponse = function(reqId, doc) {
83 | debug('pgmongo:replyr')(doc)
84 | const docBuffer = bson.serialize(doc)
85 | const length = 36 + docBuffer.length
86 |
87 | const buf = Buffer.alloc(length)
88 | buf.writeInt32LE(length, 0)
89 | buf.writeInt32LE(0, 4)
90 | buf.writeInt32LE(reqId, 8)
91 | buf.writeInt32LE(OP_REPLY, 12)
92 |
93 | buf.writeInt32LE(0, 16)
94 | buf.writeInt32LE(0, 20)
95 | buf.writeInt32LE(0, 24)
96 | buf.writeInt32LE(0, 28)
97 | buf.writeInt32LE(1, 32)
98 | docBuffer.copy(buf, 36)
99 |
100 | return buf
101 | }
102 |
--------------------------------------------------------------------------------